-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #415 from CadQuery/dxf-export
DXF export (2D) and exporters cleanup
- Loading branch information
Showing
14 changed files
with
612 additions
and
347 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import tempfile | ||
import os | ||
import io as StringIO | ||
|
||
from typing import IO, Optional, Union, cast | ||
from typing_extensions import Literal | ||
|
||
from ...cq import Workplane | ||
from ...utils import deprecate | ||
from ..shapes import Shape | ||
|
||
from .svg import getSVG | ||
from .json import JsonMesh | ||
from .amf import AmfWriter | ||
from .dxf import exportDXF | ||
from .utils import toCompound | ||
|
||
|
||
class ExportTypes: | ||
STL = "STL" | ||
STEP = "STEP" | ||
AMF = "AMF" | ||
SVG = "SVG" | ||
TJS = "TJS" | ||
DXF = "DXF" | ||
|
||
|
||
ExportLiterals = Literal["STL", "STEP", "AMF", "SVG", "TJS", "DXF"] | ||
|
||
|
||
def export( | ||
w: Union[Shape, Workplane], | ||
fname: str, | ||
exportType: Optional[ExportLiterals] = None, | ||
tolerance: float = 0.1, | ||
): | ||
|
||
""" | ||
Export Wokrplane or Shape to file. Multiple entities are converted to compound. | ||
:param w: Shape or Wokrplane to be exported. | ||
:param fname: output filename. | ||
:param exportType: the exportFormat to use. If None will be inferred from the extension. Default: None. | ||
:param tolerance: the tolerance, in model units. Default 0.1. | ||
""" | ||
|
||
shape: Shape | ||
f: IO | ||
|
||
if isinstance(w, Workplane): | ||
shape = toCompound(w) | ||
else: | ||
shape = w | ||
|
||
if exportType is None: | ||
t = fname.split(".")[-1].upper() | ||
if t in ExportTypes.__dict__.values(): | ||
exportType = cast(ExportLiterals, t) | ||
else: | ||
raise ValueError("Unknown extensions, specify export type explicitly") | ||
|
||
if exportType == ExportTypes.TJS: | ||
tess = shape.tessellate(tolerance) | ||
mesher = JsonMesh() | ||
|
||
# add vertices | ||
for v in tess[0]: | ||
mesher.addVertex(v.x, v.y, v.z) | ||
|
||
# add triangles | ||
for ixs in tess[1]: | ||
mesher.addTriangleFace(*ixs) | ||
|
||
with open(fname, "w") as f: | ||
f.write(mesher.toJson()) | ||
|
||
elif exportType == ExportTypes.SVG: | ||
with open(fname, "w") as f: | ||
f.write(getSVG(shape)) | ||
|
||
elif exportType == ExportTypes.AMF: | ||
tess = shape.tessellate(tolerance) | ||
aw = AmfWriter(tess) | ||
with open(fname, "wb") as f: | ||
aw.writeAmf(f) | ||
|
||
elif exportType == ExportTypes.DXF: | ||
if isinstance(w, Workplane): | ||
exportDXF(w, fname) | ||
else: | ||
raise ValueError("Only Workplanes can be exported as DXF") | ||
|
||
elif exportType == ExportTypes.STEP: | ||
shape.exportStep(fname) | ||
|
||
elif exportType == ExportTypes.STL: | ||
shape.exportStl(fname, tolerance) | ||
|
||
else: | ||
raise ValueError("Unknown export type") | ||
|
||
|
||
@deprecate() | ||
def toString(shape, exportType, tolerance=0.1): | ||
s = StringIO.StringIO() | ||
exportShape(shape, exportType, s, tolerance) | ||
return s.getvalue() | ||
|
||
|
||
@deprecate() | ||
def exportShape( | ||
w: Union[Shape, Workplane], | ||
exportType: ExportLiterals, | ||
fileLike: IO, | ||
tolerance: float = 0.1, | ||
): | ||
""" | ||
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery | ||
object, the first value is exported | ||
:param exportType: the exportFormat to use | ||
:param tolerance: the tolerance, in model units | ||
:param fileLike: a file like object to which the content will be written. | ||
The object should be already open and ready to write. The caller is responsible | ||
for closing the object | ||
""" | ||
|
||
def tessellate(shape): | ||
|
||
return shape.tessellate(tolerance) | ||
|
||
shape: Shape | ||
if isinstance(w, Workplane): | ||
shape = toCompound(w) | ||
else: | ||
shape = w | ||
|
||
if exportType == ExportTypes.TJS: | ||
tess = tessellate(shape) | ||
mesher = JsonMesh() | ||
|
||
# add vertices | ||
for v in tess[0]: | ||
mesher.addVertex(v.x, v.y, v.z) | ||
|
||
# add triangles | ||
for t in tess[1]: | ||
mesher.addTriangleFace(*t) | ||
|
||
fileLike.write(mesher.toJson()) | ||
|
||
elif exportType == ExportTypes.SVG: | ||
fileLike.write(getSVG(shape)) | ||
elif exportType == ExportTypes.AMF: | ||
tess = tessellate(shape) | ||
aw = AmfWriter(tess) | ||
aw.writeAmf(fileLike) | ||
else: | ||
|
||
# all these types required writing to a file and then | ||
# re-reading. this is due to the fact that FreeCAD writes these | ||
(h, outFileName) = tempfile.mkstemp() | ||
# weird, but we need to close this file. the next step is going to write to | ||
# it from c code, so it needs to be closed. | ||
os.close(h) | ||
|
||
if exportType == ExportTypes.STEP: | ||
shape.exportStep(outFileName) | ||
elif exportType == ExportTypes.STL: | ||
shape.exportStl(outFileName, tolerance) | ||
else: | ||
raise ValueError("No idea how i got here") | ||
|
||
res = readAndDeleteFile(outFileName) | ||
fileLike.write(res) | ||
|
||
|
||
@deprecate() | ||
def readAndDeleteFile(fileName): | ||
""" | ||
read data from file provided, and delete it when done | ||
return the contents as a string | ||
""" | ||
res = "" | ||
with open(fileName, "r") as f: | ||
res = "{}".format(f.read()) | ||
|
||
os.remove(fileName) | ||
return res |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import xml.etree.cElementTree as ET | ||
|
||
|
||
class AmfWriter(object): | ||
def __init__(self, tessellation): | ||
|
||
self.units = "mm" | ||
self.tessellation = tessellation | ||
|
||
def writeAmf(self, outFile): | ||
amf = ET.Element("amf", units=self.units) | ||
# TODO: if result is a compound, we need to loop through them | ||
object = ET.SubElement(amf, "object", id="0") | ||
mesh = ET.SubElement(object, "mesh") | ||
vertices = ET.SubElement(mesh, "vertices") | ||
volume = ET.SubElement(mesh, "volume") | ||
|
||
# add vertices | ||
for v in self.tessellation[0]: | ||
vtx = ET.SubElement(vertices, "vertex") | ||
coord = ET.SubElement(vtx, "coordinates") | ||
x = ET.SubElement(coord, "x") | ||
x.text = str(v.x) | ||
y = ET.SubElement(coord, "y") | ||
y.text = str(v.y) | ||
z = ET.SubElement(coord, "z") | ||
z.text = str(v.z) | ||
|
||
# add triangles | ||
for t in self.tessellation[1]: | ||
triangle = ET.SubElement(volume, "triangle") | ||
v1 = ET.SubElement(triangle, "v1") | ||
v1.text = str(t[0]) | ||
v2 = ET.SubElement(triangle, "v2") | ||
v2.text = str(t[1]) | ||
v3 = ET.SubElement(triangle, "v3") | ||
v3.text = str(t[2]) | ||
|
||
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True) |
Oops, something went wrong.