All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.vrl.v3d.CSG Maven / Gradle / Ivy

/**
 * CSG.java
 *
 * Copyright 2014-2014 Michael Hoffer [email protected]. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY Michael Hoffer [email protected] "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL Michael Hoffer [email protected] OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of Michael Hoffer
 * [email protected].
 */
package eu.mihosoft.vrl.v3d;


import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil;
import eu.mihosoft.vrl.v3d.parametrics.CSGDatabase;
import eu.mihosoft.vrl.v3d.parametrics.IParametric;
import eu.mihosoft.vrl.v3d.parametrics.IRegenerate;
import eu.mihosoft.vrl.v3d.parametrics.LengthParameter;
import eu.mihosoft.vrl.v3d.parametrics.Parameter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.neuronrobotics.interaction.CadInteractionEvent;

import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Affine;

// TODO: Auto-generated Javadoc
/**
 * Constructive Solid Geometry (CSG).
 *
 * This implementation is a Java port of
 * 
 * href="https://github.com/evanw/csg.js/" https://github.com/evanw/csg.js/ with
 * some additional features like polygon extrude, transformations etc. Thanks to
 * the author for creating the CSG.js library.
*
* * Implementation Details * * All CSG operations are implemented in terms of two functions, * {@link Node#clipTo(eu.mihosoft.vrl.v3d.Node)} and {@link Node#invert()}, * which remove parts of a BSP tree inside another BSP tree and swap solid and * empty space, respectively. To find the union of {@code a} and {@code b}, we * want to remove everything in {@code a} inside {@code b} and everything in * {@code b} inside {@code a}, then combine polygons from {@code a} and * {@code b} into one solid: * * * a.clipTo(b); b.clipTo(a); a.build(b.allPolygons()); * * * The only tricky part is handling overlapping coplanar polygons in both trees. * The code above keeps both copies, but we need to keep them in one tree and * remove them in the other tree. To remove them from {@code b} we can clip the * inverse of {@code b} against {@code a}. The code for union now looks like * this: * * * a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); * a.build(b.allPolygons()); * * * Subtraction and intersection naturally follow from set operations. If union * is {@code A | B}, differenceion is {@code A - B = ~(~A | B)} and intersection * is {@code A & B = * ~(~A | ~B)} where {@code ~} is the complement operator. */ @SuppressWarnings("restriction") public class CSG implements IuserAPI{ private static int numFacesInOffset = 15; /** The polygons. */ private List polygons; /** The default opt type. */ private static OptType defaultOptType = OptType.CSG_BOUND; /** The opt type. */ private OptType optType = null; /** The storage. */ private PropertyStorage storage; /** The current. */ private MeshView current; private static Color defaultcolor=Color.web("#007956"); /** The color. */ private Color color=getDefaultColor(); /** The manipulator. */ private Affine manipulator; private Bounds bounds; public static final int INDEX_OF_PARAMETRIC_DEFAULT = 0; public static final int INDEX_OF_PARAMETRIC_LOWER = 1; public static final int INDEX_OF_PARAMETRIC_UPPER = 2; private ArrayList groovyFileLines = new ArrayList<>(); private PrepForManufacturing manufactuing = null; private HashMap mapOfparametrics = null; private IRegenerate regenerate = null; private boolean markForRegeneration = false; private String name = ""; private ArrayList slicePlanes=null; private ArrayList exportFormats=null; private static ICSGProgress progressMoniter=new ICSGProgress() { @Override public void progressUpdate(int currentIndex, int finalIndex, String type, CSG intermediateShape) { System.out.println(type+"ing "+currentIndex+" of "+finalIndex); } }; /** * Instantiates a new csg. */ public CSG() { this(true); } public CSG(boolean makeException) { storage = new PropertyStorage(); if (makeException) { // This is the trace for where this csg was created addStackTrace(new Exception()); } } public CSG prepForManufacturing() { if (getManufacturing() == null) return this; CSG ret = getManufacturing().prep(this); if(ret == null) return null; ret.setName(getName()); ret.setColor(getColor()); ret.slicePlanes=slicePlanes; ret.mapOfparametrics=mapOfparametrics; ret.exportFormats=exportFormats; return ret; } /** * Gets the color. * * @return the color */ public Color getColor() { return color; } /** * Sets the color. * * @param color * the new color */ public CSG setColor(Color color) { this.color = color; if (current != null) { PhongMaterial m = new PhongMaterial(getColor()); current.setMaterial(m); } return this; } /** * Sets the manipulator. * * @param manipulator * the manipulator * @return the affine */ public CSG setManipulator(Affine manipulator) { if (manipulator == null) return this; Affine old = manipulator; this.manipulator = manipulator; if (current != null) { current.getTransforms().clear(); current.getTransforms().add(manipulator); } return this; } /** * Gets the mesh. * * @return the mesh */ public MeshView getMesh() { if (current != null) return current; MeshContainer meshContainer = toJavaFXMesh(null); current = meshContainer.getAsMeshViews().get(0); PhongMaterial m = new PhongMaterial(getColor()); current.setMaterial(m); if (getManipulator() != null) { current.getTransforms().clear(); current.getTransforms().add(getManipulator()); } current.setCullFace(CullFace.NONE); return current; } /** * To z min. * * @param target * the target * @return the csg */ public CSG toZMin(CSG target) { return this.transformed(new Transform().translateZ(-target.getBounds().getMin().z)); } /** * To z max. * * @param target * the target * @return the csg */ public CSG toZMax(CSG target) { return this.transformed(new Transform().translateZ(-target.getBounds().getMax().z)); } /** * To x min. * * @param target * the target * @return the csg */ public CSG toXMin(CSG target) { return this.transformed(new Transform().translateX(-target.getBounds().getMin().x)); } /** * To x max. * * @param target * the target * @return the csg */ public CSG toXMax(CSG target) { return this.transformed(new Transform().translateX(-target.getBounds().getMax().x)); } /** * To y min. * * @param target * the target * @return the csg */ public CSG toYMin(CSG target) { return this.transformed(new Transform().translateY(-target.getBounds().getMin().y)); } /** * To y max. * * @param target * the target * @return the csg */ public CSG toYMax(CSG target) { return this.transformed(new Transform().translateY(-target.getBounds().getMax().y)); } /** * To z min. * * @return the csg */ public CSG toZMin() { return toZMin(this); } /** * To z max. * * @return the csg */ public CSG toZMax() { return toZMax(this); } /** * To x min. * * @return the csg */ public CSG toXMin() { return toXMin(this); } /** * To x max. * * @return the csg */ public CSG toXMax() { return toXMax(this); } /** * To y min. * * @return the csg */ public CSG toYMin() { return toYMin(this); } /** * To y max. * * @return the csg */ public CSG toYMax() { return toYMax(this); } public CSG move(Number x, Number y, Number z) { return transformed(new Transform().translate(x.doubleValue(),y.doubleValue(),z.doubleValue())); } public CSG move(Vertex v) { return transformed(new Transform().translate(v.getX(),v.getY(),v.getZ())); } public CSG move(Vector3d v) { return transformed(new Transform().translate(v.x,v.y,v.z)); } public CSG move(Number[] posVector) { return move(posVector[0], posVector[1], posVector[2]); } /** * Movey. * * @param howFarToMove * the how far to move * @return the csg */ // Helper/wrapper functions for movement public CSG movey(Number howFarToMove) { return this.transformed(Transform.unity().translateY(howFarToMove.doubleValue())); } /** * Movez. * * @param howFarToMove * the how far to move * @return the csg */ public CSG movez(Number howFarToMove) { return this.transformed(Transform.unity().translateZ(howFarToMove.doubleValue())); } /** * Movex. * * @param howFarToMove * the how far to move * @return the csg */ public CSG movex(Number howFarToMove) { return this.transformed(Transform.unity().translateX(howFarToMove.doubleValue())); } public ArrayList move( ArrayList p) { ArrayList bits = new ArrayList(); for (Transform t : p) { bits.add(this.clone()); } return move(bits, p); } public static ArrayList move(ArrayList slice, ArrayList p) { ArrayList s = new ArrayList(); // s.add(slice.get(0)); for (int i = 0; i < slice.size() && i < p.size(); i++) { s.add(slice.get(i).transformed(p.get(i))); } return s; } /** * mirror about y axis. * * @return the csg */ // Helper/wrapper functions for movement public CSG mirrory() { return this.scaley(-1); } /** * mirror about z axis. * * @return the csg */ public CSG mirrorz() { return this.scalez(-1); } /** * mirror about x axis. * * @return the csg */ public CSG mirrorx() { return this.scalex(-1); } public CSG rot(Number x, Number y, Number z) { return rotx(x.doubleValue()).roty(y.doubleValue()).rotz(z.doubleValue()); } public CSG rot(Number[] posVector) { return rot(posVector[0], posVector[1], posVector[2]); } /** * Rotz. * * @param degreesToRotate * the degrees to rotate * @return the csg */ // Rotation function, rotates the object public CSG rotz(Number degreesToRotate) { return this.transformed(new Transform().rotZ(degreesToRotate.doubleValue())); } /** * Roty. * * @param degreesToRotate * the degrees to rotate * @return the csg */ public CSG roty(Number degreesToRotate) { return this.transformed(new Transform().rotY(degreesToRotate.doubleValue())); } /** * Rotx. * * @param degreesToRotate * the degrees to rotate * @return the csg */ public CSG rotx(Number degreesToRotate) { return this.transformed(new Transform().rotX(degreesToRotate.doubleValue())); } /** * Scalez. * * @param scaleValue * the scale value * @return the csg */ // Scale function, scales the object public CSG scalez(Number scaleValue) { return this.transformed(new Transform().scaleZ(scaleValue.doubleValue())); } /** * Scaley. * * @param scaleValue * the scale value * @return the csg */ public CSG scaley(Number scaleValue) { return this.transformed(new Transform().scaleY(scaleValue.doubleValue())); } /** * Scalex. * * @param scaleValue * the scale value * @return the csg */ public CSG scalex(Number scaleValue) { return this.transformed(new Transform().scaleX(scaleValue.doubleValue())); } /** * Scale. * * @param scaleValue * the scale value * @return the csg */ public CSG scale(Number scaleValue) { return this.transformed(new Transform().scale(scaleValue.doubleValue())); } /** * Constructs a CSG from a list of {@link Polygon} instances. * * @param polygons * polygons * @return a CSG instance */ public static CSG fromPolygons(List polygons) { CSG csg = new CSG(); csg.setPolygons(polygons); return csg; } /** * Constructs a CSG from the specified {@link Polygon} instances. * * @param polygons * polygons * @return a CSG instance */ public static CSG fromPolygons(Polygon... polygons) { return fromPolygons(Arrays.asList(polygons)); } /** * Constructs a CSG from a list of {@link Polygon} instances. * * @param storage * shared storage * @param polygons * polygons * @return a CSG instance */ public static CSG fromPolygons(PropertyStorage storage, List polygons) { CSG csg = new CSG(); csg.setPolygons(polygons); csg.storage = storage; for (Polygon polygon : polygons) { polygon.setStorage(storage); } return csg; } /** * Constructs a CSG from the specified {@link Polygon} instances. * * @param storage * shared storage * @param polygons * polygons * @return a CSG instance */ public static CSG fromPolygons(PropertyStorage storage, Polygon... polygons) { return fromPolygons(storage, Arrays.asList(polygons)); } /* * (non-Javadoc) * * @see java.lang.Object#clone() */ @Override public CSG clone() { CSG csg = new CSG(); csg.setOptType(this.getOptType()); // sequential code // csg.polygons = new ArrayList<>(); // polygons.forEach((polygon) -> { // csg.polygons.add(polygon.clone()); // }); Stream polygonStream; if (getPolygons().size() > 200) { polygonStream = getPolygons().parallelStream(); } else { polygonStream = getPolygons().stream(); } csg.setPolygons(polygonStream.map((Polygon p) -> p.clone()).collect(Collectors.toList())); return csg.historySync(this); } /** * Gets the polygons. * * @return the polygons of this CSG */ public List getPolygons() { return polygons; } /** * Defines the CSg optimization type. * * @param type * optimization type * @return this CSG */ public CSG optimization(OptType type) { this.setOptType(type); return this; } /** * Return a new CSG solid representing the union of this csg and the * specified csg. * * Note: Neither this csg nor the specified csg are weighted. * * * A.union(B) * * +-------+ +-------+ | | | | | A | | | | +--+----+ = | +----+ +----+--+ | * +----+ | | B | | | | | | | +-------+ +-------+ * * * * @param csg * other csg * * @return union of this csg and the specified csg */ public CSG union(CSG csg) { switch (getOptType()) { case CSG_BOUND: return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); case POLYGON_BOUND: return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); default: // return _unionIntersectOpt(csg); return _unionNoOpt(csg).historySync(this).historySync(csg); } } /** * Returns a csg consisting of the polygons of this csg and the specified * csg. * * The purpose of this method is to allow fast union operations for objects * that do not intersect. * * WARNING: this method does not apply the csg algorithms. Therefore, please * ensure that this csg and the specified csg do not intersect. * * @param csg * csg * * @return a csg consisting of the polygons of this csg and the specified * csg */ public CSG dumbUnion(CSG csg) { CSG result = this.clone(); CSG other = csg.clone(); result.getPolygons().addAll(other.getPolygons()); bounds = null; return result.historySync(other); } /** * Return a new CSG solid representing the union of this csg and the * specified csgs. * * Note: Neither this csg nor the specified csg are weighted. * * * A.union(B) * * +-------+ +-------+ | | | | | A | | | | +--+----+ = | +----+ +----+--+ | * +----+ | | B | | | | | | | +-------+ +-------+ * * * * @param csgs * other csgs * * @return union of this csg and the specified csgs */ public CSG union(List csgs) { CSG result = this; for (int i=0;i csgs){ CSG first = csgs.remove(0); return first.union(csgs); } public static CSG hullAll(CSG... csgs){ return hullAll(Arrays.asList(csgs)); } public static CSG hullAll(List csgs){ //CSG first = csgs.remove(0); return HullUtil.hull(csgs);// first.hull(csgs); } /** * Returns the convex hull of this csg and the union of the specified csgs. * * @param csgs * csgs * @return the convex hull of this csg and the specified csgs */ public CSG hull(List csgs) { CSG csgsUnion = new CSG(); csgsUnion.storage = storage; csgsUnion.optType = optType; csgsUnion.setPolygons(this.clone().getPolygons()); csgs.stream().forEach((csg) -> { csgsUnion.getPolygons().addAll(csg.clone().getPolygons()); csgsUnion.historySync(csg); }); csgsUnion.getPolygons().forEach(p -> p.setStorage(storage)); bounds = null; return csgsUnion.hull(); // CSG csgsUnion = this; // // for (CSG csg : csgs) { // csgsUnion = csgsUnion.union(csg); // } // // return csgsUnion.hull(); } /** * Returns the convex hull of this csg and the union of the specified csgs. * * @param csgs * csgs * @return the convex hull of this csg and the specified csgs */ public CSG hull(CSG... csgs) { return hull(Arrays.asList(csgs)); } /** * _union csg bounds opt. * * @param csg * the csg * @return the csg */ private CSG _unionCSGBoundsOpt(CSG csg) { // System.err.println("WARNING: using " + CSG.OptType.NONE // + " since other optimization types missing for union operation."); return _unionIntersectOpt(csg); } /** * _union polygon bounds opt. * * @param csg * the csg * @return the csg */ private CSG _unionPolygonBoundsOpt(CSG csg) { List inner = new ArrayList<>(); List outer = new ArrayList<>(); Bounds b = csg.getBounds(); this.getPolygons().stream().forEach((p) -> { if (b.intersects(p.getBounds())) { inner.add(p); } else { outer.add(p); } }); List allPolygons = new ArrayList<>(); if (!inner.isEmpty()) { CSG innerCSG = CSG.fromPolygons(inner); allPolygons.addAll(outer); allPolygons.addAll(innerCSG._unionNoOpt(csg).getPolygons()); } else { allPolygons.addAll(this.getPolygons()); allPolygons.addAll(csg.getPolygons()); } bounds = null; return CSG.fromPolygons(allPolygons).optimization(getOptType()); } /** * Optimizes for intersection. If csgs do not intersect create a new csg * that consists of the polygon lists of this csg and the specified csg. In * this case no further space partitioning is performed. * * @param csg * csg * @return the union of this csg and the specified csg */ private CSG _unionIntersectOpt(CSG csg) { boolean intersects = false; Bounds bounds = csg.getBounds(); for (Polygon p : getPolygons()) { if (bounds.intersects(p.getBounds())) { intersects = true; break; } } List allPolygons = new ArrayList<>(); if (intersects) { return _unionNoOpt(csg); } else { allPolygons.addAll(this.getPolygons()); allPolygons.addAll(csg.getPolygons()); } return CSG.fromPolygons(allPolygons).optimization(getOptType()); } /** * _union no opt. * * @param csg * the csg * @return the csg */ private CSG _unionNoOpt(CSG csg) { Node a = new Node(this.clone().getPolygons()); Node b = new Node(csg.clone().getPolygons()); a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); a.build(b.allPolygons()); return CSG.fromPolygons(a.allPolygons()).optimization(getOptType()); } /** * Return a new CSG solid representing the difference of this csg and the * specified csgs. * * Note: Neither this csg nor the specified csgs are weighted. * * * A.difference(B) * * +-------+ +-------+ | | | | | A | | | | +--+----+ = | +--+ +----+--+ | * +----+ | B | | | +-------+ * * * @param csgs * other csgs * @return difference of this csg and the specified csgs */ public CSG difference(List csgs) { if (csgs.isEmpty()) { return this.clone(); } CSG csgsUnion = csgs.get(0); for (int i = 1; i < csgs.size(); i++) { csgsUnion = csgsUnion.union(csgs.get(i)); progressMoniter.progressUpdate(i, csgs.size(), "Difference", csgsUnion); csgsUnion.historySync(csgs.get(i)); } return difference(csgsUnion); } /** * Return a new CSG solid representing the difference of this csg and the * specified csgs. * * Note: Neither this csg nor the specified csgs are weighted. * * * A.difference(B) * * +-------+ +-------+ | | | | | A | | | | +--+----+ = | +--+ +----+--+ | * +----+ | B | | | +-------+ * * * @param csgs * other csgs * @return difference of this csg and the specified csgs */ public CSG difference(CSG... csgs) { return difference(Arrays.asList(csgs)); } /** * Return a new CSG solid representing the difference of this csg and the * specified csg. * * Note: Neither this csg nor the specified csg are weighted. * * * A.difference(B) * * +-------+ +-------+ | | | | | A | | | | +--+----+ = | +--+ +----+--+ | * +----+ | B | | | +-------+ * * * @param csg * other csg * @return difference of this csg and the specified csg */ public CSG difference(CSG csg) { try { // Check to see if a CSG operation is attempting to difference with // no // polygons if (this.getPolygons().size() > 0 && csg.getPolygons().size() > 0) { switch (getOptType()) { case CSG_BOUND: return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); case POLYGON_BOUND: return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); default: return _differenceNoOpt(csg).historySync(this).historySync(csg); } } else return this; } catch (Exception ex) { try { System.err.println("CSG difference failed, performing workaround"); //ex.printStackTrace(); CSG intersectingParts = csg .intersect(this); if (intersectingParts.getPolygons().size() > 0) { switch (getOptType()) { case CSG_BOUND: return _differenceCSGBoundsOpt(intersectingParts).historySync(this).historySync(intersectingParts); case POLYGON_BOUND: return _differencePolygonBoundsOpt(intersectingParts).historySync(this).historySync(intersectingParts); default: return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); } } else return this; }catch(Exception e) { return this; } } } /** * _difference csg bounds opt. * * @param csg * the csg * @return the csg */ private CSG _differenceCSGBoundsOpt(CSG csg) { CSG b = csg; CSG a1 = this._differenceNoOpt(csg.getBounds().toCSG()); CSG a2 = this.intersect(csg.getBounds().toCSG()); return a2._differenceNoOpt(b)._unionIntersectOpt(a1).optimization(getOptType()); } /** * _difference polygon bounds opt. * * @param csg * the csg * @return the csg */ private CSG _differencePolygonBoundsOpt(CSG csg) { List inner = new ArrayList<>(); List outer = new ArrayList<>(); Bounds bounds = csg.getBounds(); this.getPolygons().stream().forEach((p) -> { if (bounds.intersects(p.getBounds())) { inner.add(p); } else { outer.add(p); } }); CSG innerCSG = CSG.fromPolygons(inner); List allPolygons = new ArrayList<>(); allPolygons.addAll(outer); allPolygons.addAll(innerCSG._differenceNoOpt(csg).getPolygons()); return CSG.fromPolygons(allPolygons).optimization(getOptType()); } /** * _difference no opt. * * @param csg * the csg * @return the csg */ private CSG _differenceNoOpt(CSG csg) { Node a = new Node(this.clone().getPolygons()); Node b = new Node(csg.clone().getPolygons()); a.invert(); a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); a.build(b.allPolygons()); a.invert(); CSG csgA = CSG.fromPolygons(a.allPolygons()).optimization(getOptType()); return csgA; } /** * Return a new CSG solid representing the intersection of this csg and the * specified csg. * * Note: Neither this csg nor the specified csg are weighted. * * * A.intersect(B) * * +-------+ | | | A | | +--+----+ = +--+ +----+--+ | +--+ | B | | | * +-------+ } * * * @param csg * other csg * @return intersection of this csg and the specified csg */ public CSG intersect(CSG csg) { Node a = new Node(this.clone().getPolygons()); Node b = new Node(csg.clone().getPolygons()); a.invert(); b.clipTo(a); b.invert(); a.clipTo(b); b.clipTo(a); a.build(b.allPolygons()); a.invert(); return CSG.fromPolygons(a.allPolygons()).optimization(getOptType()).historySync(csg).historySync(this); } /** * Return a new CSG solid representing the intersection of this csg and the * specified csgs. * * Note: Neither this csg nor the specified csgs are weighted. * * * A.intersect(B) * * +-------+ | | | A | | +--+----+ = +--+ +----+--+ | +--+ | B | | | * +-------+ } * * * @param csgs * other csgs * @return intersection of this csg and the specified csgs */ public CSG intersect(List csgs) { if (csgs.isEmpty()) { return this.clone(); } CSG csgsUnion = csgs.get(0); for (int i = 1; i < csgs.size(); i++) { csgsUnion = csgsUnion.union(csgs.get(i)); progressMoniter.progressUpdate(i, csgs.size(), "Intersect", csgsUnion); csgsUnion.historySync(csgs.get(i)); } return intersect(csgsUnion); } /** * Return a new CSG solid representing the intersection of this csg and the * specified csgs. * * Note: Neither this csg nor the specified csgs are weighted. * * * A.intersect(B) * * +-------+ | | | A | | +--+----+ = +--+ +----+--+ | +--+ | B | | | * +-------+ } * * * @param csgs * other csgs * @return intersection of this csg and the specified csgs */ public CSG intersect(CSG... csgs) { return intersect(Arrays.asList(csgs)); } /** * Returns this csg in STL string format. * * @return this csg in STL string format */ public String toStlString() { StringBuilder sb = new StringBuilder(); toStlString(sb); return sb.toString(); } /** * Returns this csg in STL string format. * * @param sb * string builder * * @return the specified string builder */ public StringBuilder toStlString(StringBuilder sb) { sb.append("solid v3d.csg\n"); this.getPolygons().stream().forEach((Polygon p) -> { p.toStlString(sb); }); sb.append("endsolid v3d.csg\n"); return sb; } /** * Color. * * @param c * the c * @return the csg */ public CSG color(Color c) { storage.set("material:color", "" + c.getRed() + " " + c.getGreen() + " " + c.getBlue()); return this; } /** * To obj. * * @return the obj file */ public ObjFile toObj() { StringBuilder objSb = new StringBuilder(); objSb.append("mtllib " + ObjFile.MTL_NAME); objSb.append("# Group").append("\n"); objSb.append("g v3d.csg\n"); class PolygonStruct { PropertyStorage storage; List indices; String materialName; public PolygonStruct(PropertyStorage storage, List indices, String materialName) { this.storage = storage; this.indices = indices; this.materialName = materialName; } } List vertices = new ArrayList<>(); List indices = new ArrayList<>(); objSb.append("\n# Vertices\n"); Map materialNames = new HashMap<>(); int materialIndex = 0; for (Polygon p : getPolygons()) { List polyIndices = new ArrayList<>(); p.vertices.stream().forEach((v) -> { if (!vertices.contains(v)) { vertices.add(v); v.toObjString(objSb); polyIndices.add(vertices.size()); } else { polyIndices.add(vertices.indexOf(v) + 1); } }); if (!materialNames.containsKey(p.getStorage())) { materialIndex++; materialNames.put(p.getStorage(), materialIndex); p.getStorage().set("material:name", materialIndex); } indices.add( new PolygonStruct(p.getStorage(), polyIndices, "material-" + materialNames.get(p.getStorage()))); } objSb.append("\n# Faces").append("\n"); for (PolygonStruct ps : indices) { // add mtl info ps.storage.getValue("material:color") .ifPresent((v) -> objSb.append("usemtl ").append(ps.materialName).append("\n")); // we triangulate the polygon to ensure // compatibility with 3d printer software List pVerts = ps.indices; int index1 = pVerts.get(0); for (int i = 0; i < pVerts.size() - 2; i++) { int index2 = pVerts.get(i + 1); int index3 = pVerts.get(i + 2); objSb.append("f ").append(index1).append(" ").append(index2).append(" ").append(index3).append("\n"); } } objSb.append("\n# End Group v3d.csg").append("\n"); StringBuilder mtlSb = new StringBuilder(); materialNames.keySet().forEach(s -> { if (s.contains("material:color")) { mtlSb.append("newmtl material-").append(s.getValue("material:name").get()).append("\n"); mtlSb.append("Kd ").append(s.getValue("material:color").get()).append("\n"); } }); return new ObjFile(objSb.toString(), mtlSb.toString()); } /** * Returns this csg in OBJ string format. * * @param sb * string builder * @return the specified string builder */ public StringBuilder toObjString(StringBuilder sb) { sb.append("# Group").append("\n"); sb.append("g v3d.csg\n"); class PolygonStruct { PropertyStorage storage; List indices; String materialName; public PolygonStruct(PropertyStorage storage, List indices, String materialName) { this.storage = storage; this.indices = indices; this.materialName = materialName; } } List vertices = new ArrayList<>(); List indices = new ArrayList<>(); sb.append("\n# Vertices\n"); for (Polygon p : getPolygons()) { List polyIndices = new ArrayList<>(); p.vertices.stream().forEach((v) -> { if (!vertices.contains(v)) { vertices.add(v); v.toObjString(sb); polyIndices.add(vertices.size()); } else { polyIndices.add(vertices.indexOf(v) + 1); } }); indices.add(new PolygonStruct(storage, polyIndices, " ")); } sb.append("\n# Faces").append("\n"); for (PolygonStruct ps : indices) { // we triangulate the polygon to ensure // compatibility with 3d printer software List pVerts = ps.indices; int index1 = pVerts.get(0); for (int i = 0; i < pVerts.size() - 2; i++) { int index2 = pVerts.get(i + 1); int index3 = pVerts.get(i + 2); sb.append("f ").append(index1).append(" ").append(index2).append(" ").append(index3).append("\n"); } } sb.append("\n# End Group v3d.csg").append("\n"); return sb; } /** * Returns this csg in OBJ string format. * * @return this csg in OBJ string format */ public String toObjString() { StringBuilder sb = new StringBuilder(); return toObjString(sb).toString(); } /** * Weighted. * * @param f * the f * @return the csg */ public CSG weighted(WeightFunction f) { return new Modifier(f).modified(this); } /** * Returns a transformed copy of this CSG. * * @param transform * the transform to apply * * @return a transformed copy of this CSG */ public CSG transformed(Transform transform) { if (getPolygons().isEmpty()) { return clone(); } List newpolygons = this.getPolygons().stream().map(p -> p.transformed(transform)) .collect(Collectors.toList()); CSG result = CSG.fromPolygons(newpolygons).optimization(getOptType()); result.storage = storage; return result.historySync(this); } /** * To java fx mesh. * * @param interact * the interact * @return the mesh container */ // TODO finish experiment (20.7.2014) public MeshContainer toJavaFXMesh(CadInteractionEvent interact) { return toJavaFXMeshSimple(interact); // TODO test obj approach with multiple materials // try { // ObjImporter importer = new ObjImporter(toObj()); // // List meshes = new ArrayList<>(importer.getMeshCollection()); // return new MeshContainer(getBounds().getMin(), getBounds().getMax(), // meshes, new ArrayList<>(importer.getMaterialCollection())); // } catch (IOException ex) { // Logger.getLogger(CSG.class.getName()).log(Level.SEVERE, null, ex); // } // // we have no backup strategy for broken streams :( // return null; } /** * Returns the CSG as JavaFX triangle mesh. * * @param interact * the interact * @return the CSG as JavaFX triangle mesh */ public MeshContainer toJavaFXMeshSimple(CadInteractionEvent interact) { TriangleMesh mesh = new TriangleMesh(); double minX = Double.POSITIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; double minZ = Double.POSITIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; double maxZ = Double.NEGATIVE_INFINITY; int counter = 0; for (Polygon p : getPolygons()) { if (p.vertices.size() >= 3) { // TODO: improve the triangulation? // // JavaOne requires triangular polygons. // If our polygon has more vertices, create // multiple triangles: Vertex firstVertex = p.vertices.get(0); for (int i = 0; i < p.vertices.size() - 2; i++) { if (firstVertex.pos.x < minX) { minX = firstVertex.pos.x; } if (firstVertex.pos.y < minY) { minY = firstVertex.pos.y; } if (firstVertex.pos.z < minZ) { minZ = firstVertex.pos.z; } if (firstVertex.pos.x > maxX) { maxX = firstVertex.pos.x; } if (firstVertex.pos.y > maxY) { maxY = firstVertex.pos.y; } if (firstVertex.pos.z > maxZ) { maxZ = firstVertex.pos.z; } mesh.getPoints().addAll((float) firstVertex.pos.x, (float) firstVertex.pos.y, (float) firstVertex.pos.z); mesh.getTexCoords().addAll(0); // texture (not covered) mesh.getTexCoords().addAll(0); Vertex secondVertex = p.vertices.get(i + 1); if (secondVertex.pos.x < minX) { minX = secondVertex.pos.x; } if (secondVertex.pos.y < minY) { minY = secondVertex.pos.y; } if (secondVertex.pos.z < minZ) { minZ = secondVertex.pos.z; } if (secondVertex.pos.x > maxX) { maxX = firstVertex.pos.x; } if (secondVertex.pos.y > maxY) { maxY = firstVertex.pos.y; } if (secondVertex.pos.z > maxZ) { maxZ = firstVertex.pos.z; } mesh.getPoints().addAll((float) secondVertex.pos.x, (float) secondVertex.pos.y, (float) secondVertex.pos.z); mesh.getTexCoords().addAll(0); // texture (not covered) mesh.getTexCoords().addAll(0); Vertex thirdVertex = p.vertices.get(i + 2); mesh.getPoints().addAll((float) thirdVertex.pos.x, (float) thirdVertex.pos.y, (float) thirdVertex.pos.z); if (thirdVertex.pos.x < minX) { minX = thirdVertex.pos.x; } if (thirdVertex.pos.y < minY) { minY = thirdVertex.pos.y; } if (thirdVertex.pos.z < minZ) { minZ = thirdVertex.pos.z; } if (thirdVertex.pos.x > maxX) { maxX = firstVertex.pos.x; } if (thirdVertex.pos.y > maxY) { maxY = firstVertex.pos.y; } if (thirdVertex.pos.z > maxZ) { maxZ = firstVertex.pos.z; } mesh.getTexCoords().addAll(0); // texture (not covered) mesh.getTexCoords().addAll(0); mesh.getFaces().addAll(counter, // first vertex 0, // texture (not covered) counter + 1, // second vertex 0, // texture (not covered) counter + 2, // third vertex 0 // texture (not covered) ); counter += 3; } // end for } // end if #verts >= 3 } // end for polygon return new MeshContainer(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ), mesh); } /** * Returns the bounds of this csg. SIDE EFFECT bounds is created and simply * returned if existing * * @return bouds of this csg */ public Bounds getBounds() { if (bounds != null) return bounds; if (getPolygons().isEmpty()) { bounds = new Bounds(Vector3d.ZERO, Vector3d.ZERO); return bounds; } double minX = Double.POSITIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; double minZ = Double.POSITIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; double maxZ = Double.NEGATIVE_INFINITY; for (Polygon p : getPolygons()) { for (int i = 0; i < p.vertices.size(); i++) { Vertex vert = p.vertices.get(i); if (vert.pos.x < minX) { minX = vert.pos.x; } if (vert.pos.y < minY) { minY = vert.pos.y; } if (vert.pos.z < minZ) { minZ = vert.pos.z; } if (vert.pos.x > maxX) { maxX = vert.pos.x; } if (vert.pos.y > maxY) { maxY = vert.pos.y; } if (vert.pos.z > maxZ) { maxZ = vert.pos.z; } } // end for vertices } // end for polygon bounds = new Bounds(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ)); return bounds; } public Vector3d getCenter(){ return new Vector3d( getCenterX(), getCenterY(), getCenterZ()); } /** * Helper function wrapping bounding box values * * @return CenterX */ public double getCenterX() { return ((getMinX()/2)+(getMaxX()/2)); } /** * Helper function wrapping bounding box values * * @return CenterY */ public double getCenterY() { return ((getMinY()/2)+(getMaxY()/2)); } /** * Helper function wrapping bounding box values * * @return CenterZ */ public double getCenterZ() { return ((getMinZ()/2)+(getMaxZ()/2)); } /** * Helper function wrapping bounding box values * * @return MaxX */ public double getMaxX() { return getBounds().getMax().x; } /** * Helper function wrapping bounding box values * * @return MaxY */ public double getMaxY() { return getBounds().getMax().y; } /** * Helper function wrapping bounding box values * * @return MaxZ */ public double getMaxZ() { return getBounds().getMax().z; } /** * Helper function wrapping bounding box values * * @return MinX */ public double getMinX() { return getBounds().getMin().x; } /** * Helper function wrapping bounding box values * * @return MinY */ public double getMinY() { return getBounds().getMin().y; } /** * Helper function wrapping bounding box values * * @return tMinZ */ public double getMinZ() { return getBounds().getMin().z; } /** * Helper function wrapping bounding box values * * @return MinX */ public double getTotalX() { return (-this.getMinX()+this.getMaxX()); } /** * Helper function wrapping bounding box values * * @return MinY */ public double getTotalY() { return (-this.getMinY()+this.getMaxY()); } /** * Helper function wrapping bounding box values * * @return tMinZ */ public double getTotalZ() { return (-this.getMinZ()+this.getMaxZ()); } /** * Gets the opt type. * * @return the optType */ private OptType getOptType() { return optType != null ? optType : defaultOptType; } /** * Sets the default opt type. * * @param optType * the optType to set */ public static void setDefaultOptType(OptType optType) { defaultOptType = optType; } /** * Sets the opt type. * * @param optType * the optType to set */ public void setOptType(OptType optType) { this.optType = optType; } /** * Sets the polygons. * * @param polygons * the new polygons */ public void setPolygons(List polygons) { bounds = null; this.polygons = polygons; } /** * The Enum OptType. */ public static enum OptType { /** The csg bound. */ CSG_BOUND, /** The polygon bound. */ POLYGON_BOUND, /** The none. */ NONE } /** * Hail Zeon! In case you forget the name of minkowski and are a Gundam fan * @param travelingShape * @return */ @Deprecated public ArrayList minovsky( CSG travelingShape){ System.out.println("Hail Zeon!"); return minkowski(travelingShape); } /** * Shortened name In case you forget the name of minkowski * @param travelingShape * @return */ public ArrayList mink( CSG travelingShape){ return minkowski(travelingShape); } /** * This is a simplified version of a minkowski transform using convex hull and the internal list of convex polygons * The shape is placed at the vertex of each point on a polygon, and the result is convex hulled together. * This collection is returned. * To make a normal insets, difference this collection * To make an outset by the normals, union this collection with this object. * * @param travelingShape a shape to sweep around * @return */ public ArrayList minkowski( CSG travelingShape){ HashMap map= new HashMap<>(); for(Polygon p: travelingShape.getPolygons()){ for(Vertex v:p.vertices){ if(map.get(v)==null)// use hashmap to avoid duplicate locations map.put(v,this.move(v)); } } return new ArrayList(map.values()); } /** * minkowskiDifference performs an efficient difference of the minkowski transform * of the intersection of an object. if you have 2 objects and need them to fit with a * specific tolerance as described as the distance from he normal of the surface, then * this function will effectinatly compute that value. * @param itemToDifference the object that needs to fit * @param minkowskiObject the object to represent the offset * @return */ public CSG minkowskiDifference(CSG itemToDifference, CSG minkowskiObject) { CSG intersection = this.intersect(itemToDifference); ArrayList csgDiff = intersection.mink(minkowskiObject); CSG result = this; for (int i=0;ithis.getTotalZ()/2) z=this.getTotalZ()/2; CSG printNozzel = new Sphere(z/2.0,getNumFacesForOffsets()/2,4).toCSG(); if(cut){ ArrayList mikObjs = minkowski(printNozzel); CSG remaining = this; for(CSG bit: mikObjs){ remaining=remaining.intersect(bit); } return remaining; } return union(minkowski(printNozzel)); } private int getNumFacesForOffsets() { return getNumfacesinoffset(); } public CSG makeKeepaway(Number sn) { double shellThickness =sn.doubleValue(); double x = Math.abs(this.getBounds().getMax().x) + Math.abs(this.getBounds().getMin().x); double y = Math.abs(this.getBounds().getMax().y) + Math.abs(this.getBounds().getMin().y); double z = Math.abs(this.getBounds().getMax().z) + Math.abs(this.getBounds().getMin().z); double xtol = (x + shellThickness) / x; double ytol = (y + shellThickness) / y; double ztol = (z + shellThickness) / z; double xPer = -(Math.abs(this.getBounds().getMax().x) - Math.abs(this.getBounds().getMin().x)) / x; double yPer = -(Math.abs(this.getBounds().getMax().y) - Math.abs(this.getBounds().getMin().y)) / y; double zPer = -(Math.abs(this.getBounds().getMax().z) - Math.abs(this.getBounds().getMin().z)) / z; // println " Keep away x = "+y+" new = "+ytol return this.transformed(new Transform().scale(xtol, ytol, ztol)) .transformed(new Transform().translateX(shellThickness * xPer)) .transformed(new Transform().translateY(shellThickness * yPer)) .transformed(new Transform().translateZ(shellThickness * zPer)).historySync(this); } public Affine getManipulator() { if (manipulator == null) manipulator = new Affine(); return manipulator; } public CSG addCreationEventStackTraceList(ArrayList incoming) { for (Exception ex : incoming) { addStackTrace(ex); } return this; } private void addStackTrace(Exception creationEventStackTrace2) { for (StackTraceElement el : creationEventStackTrace2.getStackTrace()) { try { if (!el.getFileName().contains(".java") && el.getLineNumber() > 0) { boolean dupLine = false; String thisline = el.getFileName() + ":" + el.getLineNumber(); for (String s : groovyFileLines) { if (s.contentEquals(thisline)) { dupLine = true; // System.err.println("Dupe: "+thisline); break; } } if (dupLine == false) { groovyFileLines.add(thisline); // System.err.println("Line: "+thisline); // for(String s:groovyFileLines){ // //System.err.println("\t\t "+s); // creationEventStackTrace2.printStackTrace(); // } } } } catch (NullPointerException ex) { } } } public CSG historySync(CSG dyingCSG) { this.addCreationEventStringList(dyingCSG.getCreationEventStackTraceList()); Set params = dyingCSG.getParameters(); for (String param : params) { boolean existing = false; for (String s : this.getParameters()) { if (s.contentEquals(param)) existing = true; } if (!existing) { Parameter vals = CSGDatabase.get(param); if (vals != null) this.setParameter(vals, dyingCSG.getMapOfparametrics().get(param)); } } this.setColor(dyingCSG.getColor()); return this; } public CSG addCreationEventStringList(ArrayList incoming) { for (String s : incoming) { addCreationEventString(s); } return this; } public CSG addCreationEventString(String thisline) { boolean dupLine = false; for (String s : groovyFileLines) { if (s.contentEquals(thisline)) { dupLine = true; break; } } if (!dupLine) { groovyFileLines.add(thisline); } return this; } public ArrayList getCreationEventStackTraceList() { return groovyFileLines; } public CSG prepMfg() { return prepForManufacturing(); } public PrepForManufacturing getManufacturing() { return manufactuing; } public PrepForManufacturing getMfg() { return getManufacturing(); } public CSG setMfg(PrepForManufacturing manufactuing) { return setManufacturing(manufactuing); } public CSG setManufacturing(PrepForManufacturing manufactuing) { this.manufactuing = manufactuing; return this; } @Deprecated public PrepForManufacturing getManufactuing() { return getManufacturing(); } @Deprecated public CSG setManufactuing(PrepForManufacturing manufactuing) { return setManufacturing(manufactuing); } public CSG setParameter(Parameter w, IParametric function) { if (w == null) return this; if (CSGDatabase.get(w.getName()) == null) CSGDatabase.set(w.getName(), w); if (getMapOfparametrics().get(w.getName()) == null) getMapOfparametrics().put(w.getName(), function); return this; } public CSG setParameter(Parameter w) { setParameter(w, new IParametric() { @Override public CSG change(CSG oldCSG, String parameterKey, Long newValue) { if (parameterKey.contentEquals(w.getName())) CSGDatabase.get(w.getName()).setValue(newValue); return oldCSG; } }); return this; } public CSG setParameter(String key, double defaultValue, double upperBound, double lowerBound, IParametric function) { ArrayList vals = new ArrayList(); vals.add(upperBound); vals.add(lowerBound); setParameter(new LengthParameter(key, defaultValue, vals), function); return this; } public CSG setParameterIfNull(String key) { if (getMapOfparametrics().get(key) == null) getMapOfparametrics().put(key, new IParametric() { @Override public CSG change(CSG oldCSG, String parameterKey, Long newValue) { CSGDatabase.get(key).setValue(newValue); return oldCSG; } }); return this; } public Set getParameters() { return getMapOfparametrics().keySet(); } public CSG setParameterNewValue(String key, double newValue) { IParametric function = getMapOfparametrics().get(key); if (function != null) return function.change(this, key, new Long((long) (newValue * 1000))).setManipulator(this.getManipulator()) .setColor(this.getColor()); return this; } public CSG setRegenerate(IRegenerate function) { regenerate = function; return this; } public CSG regenerate() { this.markForRegeneration = false; if (regenerate == null) return this; return regenerate.regenerate(this).setManipulator(this.getManipulator()).setColor(this.getColor()); } public HashMap getMapOfparametrics() { if (mapOfparametrics == null) { mapOfparametrics = new HashMap<>(); } return mapOfparametrics; } public boolean isMarkedForRegeneration() { return markForRegeneration; } public void markForRegeneration() { this.markForRegeneration = true; } /** * A test to see if 2 CSG's are touching. The fast-return is a bounding box * check If bounding boxes overlap, then an intersection is performed and * the existance of an interscting object is returned * * @param incoming * @return */ public boolean touching(CSG incoming) { // Fast bounding box overlap check, quick fail if not intersecting // bounding boxes if (this.getMaxX() > incoming.getMinX() && this.getMinX() < incoming.getMaxX() && this.getMaxY() > incoming.getMinY() && this.getMinY() < incoming.getMaxY() && this.getMaxZ() > incoming.getMinZ() && this.getMinZ() < incoming.getMaxZ()) { // Run a full intersection CSG inter = this.intersect(incoming); if (inter.getPolygons().size() > 0) { // intersection success return true; } } return false; } public static ICSGProgress getProgressMoniter() { return progressMoniter; } public static void setProgressMoniter(ICSGProgress progressMoniter) { CSG.progressMoniter = progressMoniter; } public static Color getDefaultColor() { return defaultcolor; } public static void setDefaultColor(Color defaultcolor) { CSG.defaultcolor = defaultcolor; } /** * Get Bounding box * @return A CSG that completely encapsulates the base CSG, centered around it */ public CSG getBoundingBox(){ return new Cube( (-this.getMinX()+this.getMaxX()), (-this.getMinY()+this.getMaxY()), (-this.getMinZ()+this.getMaxZ())) .toCSG() .toXMax() .movex(this.getMaxX()) .toYMax() .movey(this.getMaxY()) .toZMax() .movez(this.getMaxZ()); } public String getName() { return name; } public CSG setName(String name) { this.name = name; return this; } @Override public String toString(){ if(name==null) return getColor().toString(); return getName()+" "+getColor().toString(); } public ArrayList getSlicePlanes() { return slicePlanes; } public void addSlicePlane(Transform slicePlane) { if(slicePlanes==null) slicePlanes=new ArrayList<>(); this.slicePlanes.add( slicePlane); } /** * @return the exportFormats */ public ArrayList getExportFormats() { return exportFormats; } /** * @return the exportFormats */ public void clearExportFormats() { if(exportFormats!=null) exportFormats.clear(); } /** * @param exportFormat the exportFormat to add */ public void addExportFormat(String exportFormat) { if(this.exportFormats==null) this.exportFormats= new ArrayList<>(); for(String f:exportFormats){ if(f.toLowerCase().contains(exportFormat.toLowerCase())){ return; } } this.exportFormats.add(exportFormat.toLowerCase()); } public static int getNumfacesinoffset() { return getNumFacesInOffset(); } public static int getNumFacesInOffset() { return numFacesInOffset; } public static void setNumFacesInOffset(int numFacesInOffset) { CSG.numFacesInOffset = numFacesInOffset; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy