added export test cases
This commit is contained in:
parent
1848b1226a
commit
59d2af7a6c
|
@ -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.
|
This file is part of CadQuery.
|
||||||
|
|
||||||
CadQuery is free software; you can redistribute it and/or
|
CadQuery is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
License as published by the Free Software Foundation; either
|
License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
CadQuery is distributed in the hope that it will be useful,
|
CadQuery is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
Lesser General Public License for more details.
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
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 time,math
|
import time,math
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -1215,39 +1223,24 @@ class Workplane(CQ):
|
||||||
|
|
||||||
Future Enhancements:
|
Future Enhancements:
|
||||||
faster implementation: this one transforms 3 times to accomplish the result
|
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
|
#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):
|
||||||
|
@ -2174,4 +2167,4 @@ class Workplane(CQ):
|
||||||
else:
|
else:
|
||||||
#combine everything
|
#combine everything
|
||||||
return self.union(boxes)
|
return self.union(boxes)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
This file is part of CadQuery.
|
||||||
|
|
||||||
CadQuery is free software; you can redistribute it and/or
|
CadQuery is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
License as published by the Free Software Foundation; either
|
License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
CadQuery is distributed in the hope that it will be useful,
|
CadQuery is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
Lesser General Public License for more details.
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
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/>
|
||||||
|
|
||||||
|
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
|
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.
|
||||||
|
@ -55,16 +124,16 @@ def guessUnitOfMeasure(shape):
|
||||||
if sum(dimList) < 10:
|
if sum(dimList) < 10:
|
||||||
return UNITS.IN
|
return UNITS.IN
|
||||||
|
|
||||||
return UNITS.MM
|
return UNITS.MM
|
||||||
|
|
||||||
|
|
||||||
|
class AmfWriter(object):
|
||||||
class AmfExporter(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 = [];
|
||||||
|
@ -174,7 +243,7 @@ def getSVG(shape,opts=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
|
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
|
||||||
|
|
||||||
if opts:
|
if opts:
|
||||||
d.update(opts)
|
d.update(opts)
|
||||||
|
|
||||||
|
@ -235,11 +304,11 @@ def getSVG(shape,opts=None):
|
||||||
|
|
||||||
def exportSVG(shape, fileName):
|
def exportSVG(shape, fileName):
|
||||||
"""
|
"""
|
||||||
accept a cadquery shape, and export it to the provided file
|
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
|
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
|
export a view of a part to svg
|
||||||
"""
|
"""
|
||||||
|
|
||||||
svg = getSVG(shape.val().wrapped)
|
svg = getSVG(shape.val().wrapped)
|
||||||
f = open(fileName,'w')
|
f = open(fileName,'w')
|
||||||
f.write(svg)
|
f.write(svg)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -484,7 +512,7 @@ class Plane:
|
||||||
"""
|
"""
|
||||||
Computes the 2-d projection of the supplied matrix
|
Computes the 2-d projection of the supplied matrix
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rm = self.fG.multiply(tMatrix.wrapped).multiply(self.rG)
|
rm = self.fG.multiply(tMatrix.wrapped).multiply(self.rG)
|
||||||
return Matrix(rm)
|
return Matrix(rm)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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']
|
||||||
|
|
Loading…
Reference in New Issue
Block a user