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.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
@ -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
n = self.wire(forConstruction=False)
#attempt to consolidate wires together.
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
#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)
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):

View File

@ -15,9 +15,13 @@
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/>
"""
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
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.
@ -58,13 +127,13 @@ def guessUnitOfMeasure(shape):
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 = [];

View File

@ -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

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(TestCQSelectors.TestCQSelectors))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
unittest.TextTestRunner().run(suite)

View File

@ -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']