eu.mihosoft.vrl.v3d.svg.SVGLoad Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JavaCad Show documentation
Show all versions of JavaCad Show documentation
A Java based CSG Cad library
package eu.mihosoft.vrl.v3d.svg;
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.anim.dom.SVGOMGElement;
import org.apache.batik.anim.dom.SVGOMImageElement;
import org.apache.batik.anim.dom.SVGOMPathElement;
import org.apache.batik.anim.dom.SVGOMPolylineElement;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.vecmath.Matrix4d;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.dom.svg.SVGItem;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGImageElement;
import org.w3c.dom.svg.SVGPathSegList;
import org.w3c.dom.svg.SVGPointList;
import com.piro.bezier.BezierPath;
import eu.mihosoft.vrl.v3d.CSG;
import eu.mihosoft.vrl.v3d.Edge;
import eu.mihosoft.vrl.v3d.Extrude;
import eu.mihosoft.vrl.v3d.Polygon;
import eu.mihosoft.vrl.v3d.Transform;
import eu.mihosoft.vrl.v3d.Vector3d;
import javafx.scene.paint.Color;
// CSG.setDefaultOptType(CSG.OptType.CSG_BOUND);
/**
* Responsible for converting all SVG path elements into MetaPost curves.
*/
public class SVGLoad {
private static final String PATH_ELEMENT_NAME = "path";
private static final String GROUP_ELEMENT_NAME = "g";
private Document svgDocument;
boolean hp = true;
private HashMap> polygonByLayers = null;
private HashMap> csgByLayers = new HashMap>();
private HashMap colors=new HashMap<>();
// private ArrayList sections = null;
// private ArrayList holes = null;
//
// private List polygons = null;
private ISVGLoadProgress progress = null;
private double thickness;
private boolean negativeThickness = false;
private double height = 0;
private double width = 0;
private Double scale=null;
private HashMap units=new HashMap<>();
// static {
// units.put("mm", (1/SVGExporter.Scale));
// units.put("px", 1.0);
// units.put("cm", units.get("mm")/10.0);
// units.put("in", units.get("mm")/25.4);
// units.put("ft", units.get("in")/12.0);
// units.put("m", units.get("mm")/1000.0);
//
// }
private double toPx(String value) {
for(String key : units.keySet()) {
if(value.endsWith(key)) {
String []split = value.split(key);
if(key.contentEquals("m")&& split.length>1) {
// meters but not meters units
break;
}
//System.out.println("Units set to "+key+" for "+value);
return Double.parseDouble(split[0])/ units.get(key);
}
}
return Double.parseDouble(value);
}
private void setScale(double value) {
scale=value;
if(scale.isInfinite()||scale.isNaN())
throw new RuntimeException("Scale must be real number");
units.put("mm", (1/getScale()));
units.put("px", 1.0);
units.put("cm", units.get("mm")/10.0);
units.put("in", units.get("mm")/25.4);
units.put("ft", units.get("in")/12.0);
units.put("m", units.get("mm")/1000.0);
}
private Double getScale() {
return scale.doubleValue();
}
private double toMM(String value) {
Double px= toPx(value);
return px*1/getScale();
}
private static ISVGLoadProgress progressDefault = new ISVGLoadProgress() {
@Override
public void onShape(CSG newShape) {
// TODO Auto-generated method stub
}
};
public void setHolePolarity(boolean p) {
hp = p;
}
/**
* Responsible for converting an SVG path element to MetaPost. This will convert
* just the bezier curve portion of the path element, not its style. Typically
* the SVG path data is provided from the "d" attribute of an SVG path node.
*/
class MetaPostPath2 {
private SVGOMPathElement pathElement;
private String transform;
/**
* Use to create an instance of a class that can parse an SVG path element to
* produce MetaPost code.
*
* @param pathNode
* The path node containing a "d" attribute (output as MetaPost
* code).
*/
public MetaPostPath2(Node pathNode) {
setPathNode(pathNode);
}
/**
* Converts this object's SVG path to a MetaPost draw statement.
*
* @return A string that represents the MetaPost code for a path element.
*/
public String toCode() {
String sb = "";
SVGOMPathElement pathElement = getPathElement();
SVGPathSegList pathList = pathElement.getNormalizedPathSegList();
// String offset = pathElement.getOwnerSVGElement();
int pathObjects = pathList.getNumberOfItems();
/*
* sb.append( "M "+offset .replaceAll("translate", "") .replaceAll("(", "")
* .replaceAll(")", "") +"\n");
*/
// sb.append( "//"+getId()+"\n");
for (int i = 0; i < pathObjects; i++) {
SVGItem item = (SVGItem) pathList.getItem(i);
String itemLine = String.format("%s%n", item.getValueAsString());
sb += itemLine;
}
return sb.toString();
}
/**
* Typecasts the given pathNode to an SVGOMPathElement for later analysis.
*
* @param pathNode
* The path element that contains curves, lines, and other SVG
* instructions.
*/
private void setPathNode(Node pathNode) {
this.pathElement = (SVGOMPathElement) pathNode;
}
/**
* Returns an SVG document element that contains path instructions (usually for
* drawing on a canvas).
*
* @return An object that contains a list of items representing pen movements.
*/
private SVGOMPathElement getPathElement() {
return this.pathElement;
}
}
/**
* Creates an SVG Document given a URI.
*
* @param uri
* Path to the file.
* @throws Exception
* Something went wrong parsing the SVG file.
*/
public SVGLoad(URI uri) throws IOException {
setSVGDocument(createSVGDocument(uri));
}
/**
* Creates an SVG Document given a URI.
*
* @param f
* Path to the file.
* @throws Exception
* Something went wrong parsing the SVG file.
*/
public SVGLoad(File f) throws IOException {
setSVGDocument(createSVGDocument(f.toURI()));
}
/**
* Creates an SVG Document String of SVG data.
*
* @param data
* Contents of an svg file
* @throws Exception
* Something went wrong parsing the SVG file.
*/
public SVGLoad(String data) throws IOException {
File tmpsvg = new File(System.getProperty("java.io.tmpdir") + "/" + Math.random());
tmpsvg.createNewFile();
FileWriter fw = new FileWriter(tmpsvg.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(data);
bw.close();
setSVGDocument(createSVGDocument(tmpsvg.toURI()));
tmpsvg.deleteOnExit();
}
public ArrayList extrude(double thickness) throws IOException {
return extrude(thickness, 0.005);
}
public static ArrayList extrude(File f, double thickness) throws IOException {
return new SVGLoad(f.toURI()).extrude(thickness);
}
/**
* This function will create a list of polygons that can be exported back to an
* SVG
*
* @param f
* the file containing the SVG data
* @return
* @throws IOException
*/
public static HashMap> toPolygons(File f) throws IOException {
return new SVGLoad(f.toURI()).toPolygons();
}
public HashMap> toPolygons(double resolution) {
if(polygonByLayers==null)
try {
loadAllGroups(resolution, new Transform());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return getPolygonByLayers();
}
public HashMap> toPolygons() {
return toPolygons(0.001);
}
public static ArrayList extrude(File f, double thickness, double resolution) throws IOException {
return new SVGLoad(f.toURI()).extrude(thickness, resolution);
}
public static ArrayList extrude(URI uri, double thickness) throws IOException {
return new SVGLoad(uri).extrude(thickness);
}
public static ArrayList extrude(URI uri, double thickness, double resolution) throws IOException {
return new SVGLoad(uri).extrude(thickness, resolution);
}
private void loadAllGroups(double resolution, Transform startingFrame) {
NodeList pn = getSVGDocument().getDocumentElement().getChildNodes();// .getElementsByTagName("g");
try {
String hval = getSVGDocument().getDocumentElement().getAttribute("height");
String wval = getSVGDocument().getDocumentElement().getAttribute("width");
String viewbox = getSVGDocument().getDocumentElement().getAttribute("viewBox");
double viewW = Double.parseDouble(viewbox.split(" ")[2]);
setScale( 1);// use to compute bounds
height = toMM(hval);
width = toMM(wval);
double value =viewW/width;
//System.out.println("Page size height = "+height+" width ="+width+" with scale "+(int)(value*25.4)+" DPI ");
setScale( value);
} catch (Throwable t) {
t.printStackTrace();
height = 0;
width = 0;
setScale( 3.543307); // Assume 90 DPI and mm
}
// println "Loading groups from "+pn.getClass()
int pnCount = pn.getLength();
for (int j = 0; j < pnCount; j++) {
Node item = pn.item(j);
//System.out.println("\tTOP LEVEL :"+item);
if (SVGOMGElement.class.isInstance(item)) {
SVGOMGElement element = (SVGOMGElement) item;
loadGroup(element, resolution, startingFrame,null);
} if(SVGOMPathElement.class.isInstance(item) || SVGOMImageElement.class.isInstance(item)) {
try {
loadPath(item, resolution, startingFrame,"TOP");
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
private void loadGroup(SVGOMGElement element, double resolution, Transform startingFrame, String encapsulatingLayer) {
Node transforms = element.getAttributes().getNamedItem("transform");
Transform newFrame = getNewframe(startingFrame, transforms);
String layername;
try {
layername=element.getAttributeNS("http://www.inkscape.org/namespaces/inkscape", "label");
if(layername==null|| layername.length()==0)
throw new RuntimeException();
}catch (Throwable t) {
layername=null;
}
if(layername==null) {
layername=encapsulatingLayer;
}else {
//System.out.println("Updated to layer "+layername+" from "+encapsulatingLayer);
}
//System.out.println("\tGroup " + element.getAttribute("id") +"\n\t inkscape name: "+layername+ " \n\t root " + newFrame.getX() + " " + newFrame.getY());
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (SVGOMGElement.class.isInstance(n)) {
loadGroup((SVGOMGElement) n, resolution, newFrame,layername);
} else {
//System.out.println("\tNot group:"+n);
try {
loadPath(n, resolution, newFrame,layername);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
private Transform getNewframe(Transform startingFrame, Node transforms) {
if (transforms == null)
return startingFrame;
Transform newFrame = new Transform().apply(startingFrame);
String transformValue = transforms.getNodeValue();
//System.out.println("\tApply " + transformValue + " root " + startingFrame.getX() + " " + startingFrame.getY());
if (transformValue.contains("translate")) {
String[] transformValues = transformValue.replaceAll("translate", "").replaceAll("\\(", "")
.replaceAll("\\)", "").split("\\,");
newFrame.apply(new Transform().translate(toPx(transformValues[0]),
toPx(transformValues[1]), 0));
}else if (transformValue.contains("rotate")) {
String[] rotvals = transformValue.replaceAll("rotate", "").replaceAll("\\(", "")
.replaceAll("\\)", "").split("\\,");
newFrame= startingFrame.inverse()
.apply(new Transform().rotZ(-Double.parseDouble(rotvals[0])))
.apply(startingFrame);
} else if (transformValue.contains("scale")) {
String[] transformValues = transformValue.replaceAll("scale", "").replaceAll("\\(", "")
.replaceAll("\\)", "").split("\\,");
// System.out.println(id.getNodeValue() + " " + transformValues);
double scalex = toPx(transformValues[0]);
double scaley = toPx(transformValues.length==2?transformValues[1]:transformValues[0]);
newFrame.scale(scalex, scaley, 1);
} else if (transformValue.contains("matrix")) {
String[] transformValues = transformValue.replaceAll("matrix", "").replaceAll("\\(", "")
.replaceAll("\\)", "").split("\\,");
// System.out.println("Matrix found " +new
// ArrayList<>(Arrays.asList(transformValues)));
double a = toPx(transformValues[0]);
double b = toPx(transformValues[1]);
double c = toPx(transformValues[2]);
double d = toPx(transformValues[3]);
double e = toPx(transformValues[4]);
double f = toPx(transformValues[5]);
double elemenents[] = { a, c, 0, e, b, d, 0, f, 0, 0, 1, 0, 0, 0, 0, 1 };
newFrame.apply(new Transform(new Matrix4d(elemenents)));
}
return newFrame;
}
// SVGOMGElement
private void loadPath(Node pathNode, double resolution, Transform startingFrame, String encapsulatingLayer) {
Transform newFrame;
// NodeList pathNodes = element.getElementsByTagName("path");
// Node transforms = element.getAttributes().getNamedItem("transform");
if (pathNode != null) {
// System.out.println("\tPath
// "+pathNode.getAttributes().getNamedItem("id").getNodeValue());
//System.out.println("Path loading "+pathNode);
newFrame = startingFrame;
try {
Node transforms = pathNode.getAttributes().getNamedItem("transform");
newFrame = getNewframe(startingFrame, transforms);
}catch(Exception ex) {
// no transform
}
try {
if(SVGOMPathElement.class.isInstance(pathNode)) {
// NamedNodeMap attribs = pathNode.getAttributes();
// for(int i=0;i 1) {
// println "Seperated complex: "
loadSingle(sectionedPart, resolution, startingFrame,encapsulatingLayer, c);
}
}
}
// System.out.println("SVG has this many elements loaded: "+sections.size());
// BowlerStudioController.setCsg(sections,null);
}
public static boolean isCCW(Polygon polygon) {
double runningTotal=0;
List edges = Edge.fromPolygon(polygon);
for(Edge e:edges) {
//runningTotal+=((e.getP1().pos.x-e.getP2().pos.x)*(e.getP1().pos.y-e.getP2().pos.y));
runningTotal+=e.getP1().pos.x*e.getP2().pos.y;
runningTotal-=e.getP2().pos.x*e.getP1().pos.y;
}
return runningTotal<0;
}
private void loadSingle(String code, double resolution, Transform startingFrame, String encapsulatingLayer, Color c) {
// println code
BezierPath path = new BezierPath();
path.parsePathString(code);
ArrayList p = path.evaluate();
for (Vector3d point : p) {
point.transform(startingFrame);
point.transform(new Transform().scale((1.0 / getScale())));
point.transform(new Transform().translate(0, -height, 0));
point.transform(new Transform().rotZ(-180));
point.transform(new Transform().rotY(180));
}
// System.out.println(" Path " + code);
Polygon poly = Polygon.fromPoints(p);
if(getPolygonByLayers()==null)
setPolygonByLayers(new HashMap>());
if (getPolygonByLayers().get(encapsulatingLayer) == null)
getPolygonByLayers().put(encapsulatingLayer, new ArrayList());
List list = getPolygonByLayers().get(encapsulatingLayer);
poly = Polygon.fromPoints(Extrude.toCCW(poly.getPoints()));
if(c!=null)
colors.put(poly, c);
list.add(poly);
}
public List getLayers(){
ArrayList layers= new ArrayList();
if(getPolygonByLayers()==null) {
toPolygons();
}
for(Object key:getPolygonByLayers().keySet().stream().sorted().toArray() )
layers.add((String) key);
return layers;
}
public CSG extrudeLayerToCSG(double t,String layer){
CSG unionAll = CSG.unionAll(extrudeLayers(t,0.01,layer).get(layer));
unionAll.setName(layer);
return unionAll;
}
public ArrayList extrudeLayer(double t,String layer){
return extrudeLayers(t,0.01,layer).get(layer);
}
public HashMap> extrudeLayers(double t){
return extrudeLayers(t,0.01,null);
}
public HashMap> extrudeLayers(double t, double resolution, String targetLayer){
this.thickness = t;
if (thickness < 0) {
thickness = -thickness;
negativeThickness = true;
} else {
negativeThickness = false;
}
toPolygons(0.001);
for(String key: getPolygonByLayers().keySet()) {
if(targetLayer!=null)
if(!targetLayer.contentEquals(key))
continue;
if(csgByLayers.get(key)==null) {
csgByLayers.put(key, new ArrayList());
}
ArrayList parts = csgByLayers.get(key);
parts.clear();
for(Polygon p:getPolygonByLayers().get(key)) {
CSG newbit;
newbit = Extrude.getExtrusionEngine().extrude(new Vector3d(0, 0, thickness), p);
if (negativeThickness) {
newbit = newbit.toZMax();
}
if(colors.get(p)!=null) {
newbit.setColor(colors.get(p));
}
parts.add(newbit);
}
}
return csgByLayers;
}
public ArrayList extrude(double t, double resolution) throws IOException {
HashMap> layers =extrudeLayers( t, resolution,null);
ArrayList all = new ArrayList();
for(String key:layers.keySet()) {
all.addAll(layers.get(key));
}
return all;
}
/**
* This will set the document to parse. This method also initializes the SVG DOM
* enhancements, which are necessary to perform SVG and CSS manipulations. The
* initialization is also required to extract information from the SVG path
* elements.
*
* @param document
* The document that contains SVG content.
*/
public void setSVGDocument(Document document) {
initSVGDOM(document);
this.svgDocument = document;
}
/**
* Returns the SVG document parsed upon instantiating this class.
*
* @return A valid, parsed, non-null SVG document instance.
*/
public Document getSVGDocument() {
return this.svgDocument;
}
/**
* Enhance the SVG DOM for the given document to provide CSS- and SVG-specific
* DOM interfaces.
*
* @param document
* The document to enhance.
* @link http://wiki.apache.org/xmlgraphics-batik/BootSvgAndCssDom
*/
private void initSVGDOM(Document document) {
UserAgent userAgent = new UserAgentAdapter();
DocumentLoader loader = new DocumentLoader(userAgent);
BridgeContext bridgeContext = new BridgeContext(userAgent, loader);
bridgeContext.setDynamicState(BridgeContext.DYNAMIC);
// Enable CSS- and SVG-specific enhancements.
(new GVTBuilder()).build(bridgeContext, document);
}
/**
* Use the SAXSVGDocumentFactory to parse the given URI into a DOM.
*
* @param uri
* The path to the SVG file to read.
* @return A Document instance that represents the SVG file.
* @throws Exception
* The file could not be read.
*/
private Document createSVGDocument(URI uri) throws IOException {
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser);
return factory.createDocument(uri.toString());
}
public ISVGLoadProgress getProgress() {
return progress;
}
public void setProgress(ISVGLoadProgress progress) {
this.progress = progress;
}
public static ISVGLoadProgress getProgressDefault() {
return progressDefault;
}
private HashMap> getPolygonByLayers() {
return polygonByLayers;
}
private void setPolygonByLayers(HashMap> polygonByLayers) {
this.polygonByLayers = polygonByLayers;
}
}