added export test cases

This commit is contained in:
Dave Cowden 2013-04-21 17:45:29 -04:00
parent 1848b1226a
commit 59d2af7a6c
5 changed files with 164 additions and 74 deletions

View File

@ -834,18 +834,26 @@ class Workplane(CQ):
self.parent = None self.parent = None
self.ctx = CQContext() 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. 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 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 coordinates local to the current plane
The new plane is rotated through the angles specified by the components of the rotation vector 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 rotate: 3-tuple 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 offset: 3-tuple to offset the new plane, in local work plane coordinates
:return: a new work plane, transformed as requested :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 = self.plane.rotated(rotate)
p.setOrigin3d(self.plane.toWorldCoords(offset.toTuple() )) p.setOrigin3d(self.plane.toWorldCoords(offset ))
ns = self.newObject([p.origin]) ns = self.newObject([p.origin])
ns.plane = p ns.plane = p
@ -1219,35 +1227,20 @@ class Workplane(CQ):
""" """
#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 #convert edges to a wire, if there are pending edges
n = self.wire(forConstruction=False) n = self.wire(forConstruction=False)
#attempt to consolidate wires together. #attempt to consolidate wires together.
consolidated = n.consolidateWires() consolidated = n.consolidateWires()
#ok, mirror all the wires. rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(),matrix)
#There might be a better way, but to do this rotation takes 3 steps for w in rotatedWires:
#transform geometry to local coordinates consolidated.objects.append(w)
#then rotate about x consolidated._addPendingWire(w)
#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)
#attempt again to consolidate all of the wires #attempt again to consolidate all of the wires
c = consolidated.consolidateWires() c = consolidated.consolidateWires()
#c = consolidated
return c return c
def mirrorY(self): def mirrorY(self):

View File

@ -15,9 +15,13 @@
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/> License along with this library; If not, see <http://www.gnu.org/licenses/>
"""
import FreeCAD An exporter should provide functionality to accept a shape, and return
a string containing the model content.
"""
import cadquery
import FreeCAD,tempfile,os
from FreeCAD import Drawing from FreeCAD import Drawing
try: try:
@ -25,17 +29,82 @@ try:
except ImportError: except ImportError:
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
class ExportFormats: class ExportTypes:
STL = "STL" STL = "STL"
BREP = "BREP"
STEP = "STEP" STEP = "STEP"
AMF = "AMF" AMF = "AMF"
IGES = "IGES" SVG = "SVG"
TJS = "TJS"
class UNITS: class UNITS:
MM = "mm" MM = "mm"
IN = "in" 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): def guessUnitOfMeasure(shape):
""" """
Guess the unit of measure of a shape. Guess the unit of measure of a shape.
@ -58,13 +127,13 @@ def guessUnitOfMeasure(shape):
return UNITS.MM return UNITS.MM
class AmfExporter(object): class AmfWriter(object):
def __init__(self,tessellation): def __init__(self,tessellation):
self.units = "mm" self.units = "mm"
self.tessellation = tessellation self.tessellation = tessellation
def writeAmf(self,outFileName): def writeAmf(self,outFile):
amf = ET.Element('amf',units=self.units) amf = ET.Element('amf',units=self.units)
#TODO: if result is a compound, we need to loop through them #TODO: if result is a compound, we need to loop through them
object = ET.SubElement(amf,'object',id="0") object = ET.SubElement(amf,'object',id="0")
@ -94,14 +163,14 @@ class AmfExporter(object):
v3.text = str(t[2]) 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 Objects that represent
three.js JSON object notation three.js JSON object notation
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0 https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
""" """
class JsonExporter(object): class JsonMesh(object):
def __init__(self): def __init__(self):
self.vertices = []; self.vertices = [];

View File

@ -428,7 +428,7 @@ class Plane:
return Vector(self.rG.multiply(v.wrapped)) 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 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() rotations are done in order x,y,z. if you need a different order, manually chain together multiple .rotate()
commands commands
:param roate: Vector [xDegrees,yDegrees,zDegrees] :param rotate: Vector [xDegrees,yDegrees,zDegrees]
:return: a copy of this plane rotated as requested :return: a copy of this plane rotated as requested
""" """
if rotate.__class__.__name__ != 'Vector':
rotate = Vector(rotate)
#convert to radians #convert to radians
rotate = rotate.multiply(math.pi / 180.0 ) rotate = rotate.multiply(math.pi / 180.0 )
@ -460,6 +462,32 @@ class Plane:
newP= Plane(self.origin,newXdir,newZdir) newP= Plane(self.origin,newXdir,newZdir)
return newP 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): def _calcTransforms(self):
""" """
Computes transformation martrices to convert betwene local and global coordinates Computes transformation martrices to convert betwene local and global coordinates

View File

@ -12,5 +12,5 @@ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadObjects.TestCa
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
unittest.TextTestRunner().run(suite) unittest.TextTestRunner().run(suite)

View File

@ -46,4 +46,4 @@ class BaseTest(unittest.TestCase):
for i,j in zip(actual,expected): for i,j in zip(actual,expected):
self.assertAlmostEquals(i,j,places) self.assertAlmostEquals(i,j,places)
__all__ = [ 'TestCadObjects','TestCadQuery','TestCQSelectors','TestWorkplanes'] __all__ = [ 'TestCadObjects','TestCadQuery','TestCQSelectors','TestWorkplanes','TestExporters','TestCQSelectors']