diff --git a/cadquery/CQ.py b/cadquery/CQ.py
index d63ba04..26f5370 100644
--- a/cadquery/CQ.py
+++ b/cadquery/CQ.py
@@ -1,20 +1,20 @@
"""
- Copyright (C) 2011-2013 Parametric Products Intellectual Holdings, LLC
+ Copyright (C) 2011-2013 Parametric Products Intellectual Holdings, LLC
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
+ This file is part of CadQuery.
+
+ CadQuery is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
+ CadQuery is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; If not, see
"""
import time,math
@@ -834,18 +834,26 @@ class Workplane(CQ):
self.parent = None
self.ctx = CQContext()
- def transformed(self,rotate=Vector(0,0,0),offset=Vector(0,0,0)):
+ def transformed(self,rotate=(0,0,0),offset=(0,0,0)):
"""
Create a new workplane based on the current one.
The origin of the new plane is located at the existing origin+offset vector, where offset is given in
coordinates local to the current plane
The new plane is rotated through the angles specified by the components of the rotation vector
- :param rotate: vector of angles to rotate, in degrees relative to work plane coordinates
- :param offset: vector to offset the new plane, in local work plane coordinates
+ :param rotate: 3-tuple of angles to rotate, in degrees relative to work plane coordinates
+ :param offset: 3-tuple to offset the new plane, in local work plane coordinates
:return: a new work plane, transformed as requested
"""
+
+ #old api accepted a vector, so we'll check for that.
+ if rotate.__class__.__name__ == 'Vector':
+ rotate = rotate.toTuple()
+
+ if offset.__class__.__name__ == 'Vector':
+ offset = offset.toTuple()
+
p = self.plane.rotated(rotate)
- p.setOrigin3d(self.plane.toWorldCoords(offset.toTuple() ))
+ p.setOrigin3d(self.plane.toWorldCoords(offset ))
ns = self.newObject([p.origin])
ns.plane = p
@@ -1215,39 +1223,24 @@ class Workplane(CQ):
Future Enhancements:
faster implementation: this one transforms 3 times to accomplish the result
-
-
+
+
"""
-
- #compute rotation matrix ( global --> local --> rotate --> global )
- #rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
- rm = self.plane.computeTransform(matrix)
-
+
#convert edges to a wire, if there are pending edges
n = self.wire(forConstruction=False)
#attempt to consolidate wires together.
consolidated = n.consolidateWires()
- #ok, mirror all the wires.
-
- #There might be a better way, but to do this rotation takes 3 steps
- #transform geometry to local coordinates
- #then rotate about x
- #then transform back to global coordiante
-
- #!!!TODO: needs refactoring. rm.wrapped is a hack, w.transformGeometry is needing a FreeCAD matrix,
- #so this code is dependent on a freecad matrix even when we dont explicitly import it.
- #
- originalWires = consolidated.wires().vals()
- for w in originalWires:
- mirrored = w.transformGeometry(rm.wrapped)
- consolidated.objects.append(mirrored)
- consolidated._addPendingWire(mirrored)
+ rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(),matrix)
+
+ for w in rotatedWires:
+ consolidated.objects.append(w)
+ consolidated._addPendingWire(w)
#attempt again to consolidate all of the wires
c = consolidated.consolidateWires()
- #c = consolidated
return c
def mirrorY(self):
@@ -2174,4 +2167,4 @@ class Workplane(CQ):
else:
#combine everything
return self.union(boxes)
-
+
diff --git a/cadquery/freecad_impl/exporters.py b/cadquery/freecad_impl/exporters.py
index be2da12..78c27b8 100644
--- a/cadquery/freecad_impl/exporters.py
+++ b/cadquery/freecad_impl/exporters.py
@@ -1,23 +1,27 @@
"""
- Copyright (C) 2011-2013 Parametric Products Intellectual Holdings, LLC
+ Copyright (C) 2011-2013 Parametric Products Intellectual Holdings, LLC
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
+ This file is part of CadQuery.
+
+ CadQuery is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
+ CadQuery is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; If not, see
+
+ An exporter should provide functionality to accept a shape, and return
+ a string containing the model content.
"""
+import cadquery
-import FreeCAD
+import FreeCAD,tempfile,os
from FreeCAD import Drawing
try:
@@ -25,17 +29,82 @@ try:
except ImportError:
import xml.etree.ElementTree as ET
-class ExportFormats:
+class ExportTypes:
STL = "STL"
- BREP = "BREP"
STEP = "STEP"
AMF = "AMF"
- IGES = "IGES"
-
+ SVG = "SVG"
+ TJS = "TJS"
+
class UNITS:
MM = "mm"
IN = "in"
+
+def exportShape(shape,exportType,fileLike,tolerance=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 exportFormat: 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
+ """
+
+ if isinstance(shape,cadquery.CQ):
+ shape = shape.val()
+
+ if exportType == ExportTypes.TJS:
+ #tessellate the model
+ tess = shape.tessellate(tolerance)
+
+ mesher = JsonMesh() #warning: needs to be changed to remove buildTime and exportTime!!!
+ #add vertices
+ for vec in tess[0]:
+ mesher.addVertex(vec.x, vec.y, vec.z)
+
+ #add faces
+ for f in tess[1]:
+ mesher.addTriangleFace(f[0],f[1], f[2])
+ fileLike.write( mesher.toJson())
+ elif exportType == ExportTypes.SVG:
+ fileLike.write(getSVG(shape.wrapped))
+ elif exportType == ExportTypes.AMF:
+ tess = shape.tessellate(tolerance)
+ aw = AmfWriter(tess).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.wrapped.exportStl(outFileName)
+ else:
+ raise ValueError("No idea how i got here")
+
+ res = readAndDeleteFile(outFileName)
+ fileLike.write(res)
+
+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 = f.read()
+
+ os.remove(fileName)
+ return res
+
+
def guessUnitOfMeasure(shape):
"""
Guess the unit of measure of a shape.
@@ -55,16 +124,16 @@ def guessUnitOfMeasure(shape):
if sum(dimList) < 10:
return UNITS.IN
- return UNITS.MM
+ return UNITS.MM
+
-
-class AmfExporter(object):
+class AmfWriter(object):
def __init__(self,tessellation):
self.units = "mm"
self.tessellation = tessellation
- def writeAmf(self,outFileName):
+ 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")
@@ -94,14 +163,14 @@ class AmfExporter(object):
v3.text = str(t[2])
- ET.ElementTree(amf).write(outFileName,encoding='ISO-8859-1')
+ ET.ElementTree(amf).write(outFile,encoding='ISO-8859-1')
"""
Objects that represent
three.js JSON object notation
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
"""
-class JsonExporter(object):
+class JsonMesh(object):
def __init__(self):
self.vertices = [];
@@ -174,7 +243,7 @@ def getSVG(shape,opts=None):
"""
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
-
+
if opts:
d.update(opts)
@@ -235,11 +304,11 @@ def getSVG(shape,opts=None):
def exportSVG(shape, fileName):
"""
- accept a cadquery shape, and export it to the provided file
- TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
+ accept a cadquery shape, and export it to the provided file
+ TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
export a view of a part to svg
"""
-
+
svg = getSVG(shape.val().wrapped)
f = open(fileName,'w')
f.write(svg)
diff --git a/cadquery/freecad_impl/geom.py b/cadquery/freecad_impl/geom.py
index 4c193ac..ae9ed51 100644
--- a/cadquery/freecad_impl/geom.py
+++ b/cadquery/freecad_impl/geom.py
@@ -428,7 +428,7 @@ class Plane:
return Vector(self.rG.multiply(v.wrapped))
- def rotated(self,rotate=Vector(0,0,0)):
+ def rotated(self,rotate=(0,0,0)):
"""
returns a copy of this plane, rotated about the specified axes, as measured from horizontal
@@ -440,10 +440,12 @@ class Plane:
rotations are done in order x,y,z. if you need a different order, manually chain together multiple .rotate()
commands
- :param roate: Vector [xDegrees,yDegrees,zDegrees]
+ :param rotate: Vector [xDegrees,yDegrees,zDegrees]
:return: a copy of this plane rotated as requested
"""
+ if rotate.__class__.__name__ != 'Vector':
+ rotate = Vector(rotate)
#convert to radians
rotate = rotate.multiply(math.pi / 180.0 )
@@ -460,6 +462,32 @@ class Plane:
newP= Plane(self.origin,newXdir,newZdir)
return newP
+ def rotateShapes(self,listOfShapes,rotationMatrix):
+ """
+ rotate the listOfShapes by the rotationMatrix supplied.
+ @param listOfShapes is a list of shape objects
+ @param rotationMatrix is a geom.Matrix object.
+ returns a list of shape objects rotated according to the rotationMatrix
+ """
+
+ #compute rotation matrix ( global --> local --> rotate --> global )
+ #rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
+ rm = self.computeTransform(rotationMatrix)
+
+
+ #There might be a better way, but to do this rotation takes 3 steps
+ #transform geometry to local coordinates
+ #then rotate about x
+ #then transform back to global coordiante
+
+ resultWires = []
+ for w in listOfShapes:
+ mirrored = w.transformGeometry(rotationMatrix.wrapped)
+ resultWires.append(mirrored)
+
+ return resultWires
+
+
def _calcTransforms(self):
"""
Computes transformation martrices to convert betwene local and global coordinates
@@ -484,7 +512,7 @@ class Plane:
"""
Computes the 2-d projection of the supplied matrix
"""
-
+
rm = self.fG.multiply(tMatrix.wrapped).multiply(self.rG)
return Matrix(rm)
diff --git a/runtests.py b/runtests.py
index f358a5c..7020ef4 100644
--- a/runtests.py
+++ b/runtests.py
@@ -12,5 +12,5 @@ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadObjects.TestCa
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
-
+suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
unittest.TextTestRunner().run(suite)
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
index faff2a3..7c6e821 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -46,4 +46,4 @@ class BaseTest(unittest.TestCase):
for i,j in zip(actual,expected):
self.assertAlmostEquals(i,j,places)
-__all__ = [ 'TestCadObjects','TestCadQuery','TestCQSelectors','TestWorkplanes']
+__all__ = [ 'TestCadObjects','TestCadQuery','TestCQSelectors','TestWorkplanes','TestExporters','TestCQSelectors']