Updated to work with 0.13

- This is _totally_ experimental, and I haven't done exhaustive
   tests
 - 88 unit tests do pass though, and I added a new import test.
 - 0.12 and 0.13 seem to structure stuff a bit differently,
   probably due to changes in the FreeCAD lib wrappers.
 - Not tested on windows (but should work) or 0.12. Need some
   help there.
This commit is contained in:
Derek Anderson 2013-04-27 23:49:41 -07:00
parent 8089ef1e04
commit c3290fca92
9 changed files with 265 additions and 95 deletions

View File

@ -21,8 +21,19 @@
"""
import cadquery
import FreeCAD,tempfile,os,StringIO
from FreeCAD import Drawing
from .verutil import fc_import
FreeCAD = fc_import("FreeCAD")
import tempfile,os,StringIO
Drawing = fc_import("FreeCAD.Drawing")
#_FCVER = freecad_version()
#if _FCVER>=(0,13):
#import Drawing as FreeCADDrawing #It's in FreeCAD lib path
#elif _FCVER>=(0,12):
#import FreeCAD.Drawing as FreeCADDrawing
#else:
#raise RuntimeError, "Invalid freecad version: %s" % str(".".join(_FCVER))
try:
import xml.etree.cElementTree as ET

View File

@ -18,8 +18,10 @@
"""
import math,sys
import FreeCAD
import FreeCAD.Part
#import FreeCAD
from .verutil import fc_import
FreeCAD = fc_import("FreeCAD")
#Turns out we don't need the Part module here.
def sortWiresByBuildOrder(wireList,plane,result=[]):
"""

View File

@ -49,7 +49,9 @@
"""
from cadquery import Vector,BoundBox
import FreeCAD
import FreeCAD.Part
from .verutil import fc_import
FreeCADPart = fc_import("FreeCAD.Part")
class Shape(object):
"""
@ -316,9 +318,9 @@ class Edge(Shape):
#self.endPoint = None
self.edgetypes= {
FreeCAD.Part.Line : 'LINE',
FreeCAD.Part.ArcOfCircle : 'ARC',
FreeCAD.Part.Circle : 'CIRCLE'
FreeCADPart.Line : 'LINE',
FreeCADPart.ArcOfCircle : 'ARC',
FreeCADPart.Circle : 'CIRCLE'
}
def geomType(self):
@ -368,7 +370,7 @@ class Edge(Shape):
@classmethod
def makeCircle(cls,radius,pnt=(0,0,0),dir=(0,0,1),angle1=360.0,angle2=360):
return Edge(FreeCAD.Part.makeCircle(radius,toVector(pnt),toVector(dir),angle1,angle2))
return Edge(FreeCADPart.makeCircle(radius,toVector(pnt),toVector(dir),angle1,angle2))
@classmethod
def makeSpline(cls,listOfVector):
@ -380,7 +382,7 @@ class Edge(Shape):
"""
vecs = [v.wrapped for v in listOfVector]
spline = FreeCAD.Part.BSplineCurve()
spline = FreeCADPart.BSplineCurve()
spline.interpolate(vecs,False)
return Edge(spline.toShape())
@ -394,7 +396,7 @@ class Edge(Shape):
:param v3: end vector
:return: an edge object through the three points
"""
arc = FreeCAD.Part.Arc(v1.wrapped,v2.wrapped,v3.wrapped)
arc = FreeCADPart.Arc(v1.wrapped,v2.wrapped,v3.wrapped)
e = Edge(arc.toShape())
return e #arcane and undocumented, this creates an Edge object
@ -406,7 +408,7 @@ class Edge(Shape):
:param v2: Vector that represents the second point
:return: A linear edge between the two provided points
"""
return Edge(FreeCAD.Part.makeLine(v1.toTuple(),v2.toTuple() ))
return Edge(FreeCADPart.makeLine(v1.toTuple(),v2.toTuple() ))
class Wire(Shape):
@ -425,7 +427,7 @@ class Wire(Shape):
:param listOfWires:
:return:
"""
return Shape.cast(FreeCAD.Part.Wire([w.wrapped for w in listOfWires]))
return Shape.cast(FreeCADPart.Wire([w.wrapped for w in listOfWires]))
@classmethod
def assembleEdges(cls,listOfEdges):
@ -437,7 +439,7 @@ class Wire(Shape):
"""
fCEdges = [a.wrapped for a in listOfEdges]
wa = Wire( FreeCAD.Part.Wire(fCEdges) )
wa = Wire( FreeCADPart.Wire(fCEdges) )
return wa
@classmethod
@ -449,13 +451,13 @@ class Wire(Shape):
:param normal: vector representing the direction of the plane the circle should lie in
:return:
"""
w = Wire(FreeCAD.Part.Wire([FreeCAD.Part.makeCircle(radius,center.wrapped,normal.wrapped)]))
w = Wire(FreeCADPart.Wire([FreeCADPart.makeCircle(radius,center.wrapped,normal.wrapped)]))
return w
@classmethod
def makePolygon(cls,listOfVertices,forConstruction=False):
#convert list of tuples into Vectors.
w = Wire(FreeCAD.Part.makePolygon([i.wrapped for i in listOfVertices]))
w = Wire(FreeCADPart.makePolygon([i.wrapped for i in listOfVertices]))
w.forConstruction = forConstruction
return w
@ -466,7 +468,7 @@ class Wire(Shape):
By default a cylindrical surface is used to create the helix. If
the fourth parameter is set (the apex given in degree) a conical surface is used instead'
"""
return Wire(FreeCAD.Part.makeHelix(pitch,height,radius,angle))
return Wire(FreeCADPart.makeHelix(pitch,height,radius,angle))
class Face(Shape):
@ -478,9 +480,9 @@ class Face(Shape):
self.facetypes = {
#TODO: bezier,bspline etc
FreeCAD.Part.Plane : 'PLANE',
FreeCAD.Part.Sphere : 'SPHERE',
FreeCAD.Part.Cone : 'CONE'
FreeCADPart.Plane : 'PLANE',
FreeCADPart.Sphere : 'SPHERE',
FreeCADPart.Cone : 'CONE'
}
def geomType(self):
@ -506,7 +508,7 @@ class Face(Shape):
@classmethod
def makePlane(cls,length,width,basePnt=None,dir=None):
return Face(FreeCAD.Part.makePlan(length,width,toVector(basePnt),toVector(dir)))
return Face(FreeCADPart.makePlan(length,width,toVector(basePnt),toVector(dir)))
@classmethod
def makeRuledSurface(cls,edgeOrWire1,edgeOrWire2,dist=None):
@ -515,7 +517,7 @@ class Face(Shape):
Create a ruled surface out of two edges or wires. If wires are used then
these must have the same
"""
return Shape.cast(FreeCAD.Part.makeRuledSurface(edgeOrWire1.obj,edgeOrWire2.obj,dist))
return Shape.cast(FreeCADPart.makeRuledSurface(edgeOrWire1.obj,edgeOrWire2.obj,dist))
def cut(self,faceToCut):
"Remove a face from another one"
@ -541,7 +543,7 @@ class Shell(Shape):
@classmethod
def makeShell(cls,listOfFaces):
return Shell(FreeCAD.Part.makeShell([i.obj for i in listOfFaces]))
return Shell(FreeCADPart.makeShell([i.obj for i in listOfFaces]))
class Solid(Shape):
@ -568,7 +570,7 @@ class Solid(Shape):
makeBox(length,width,height,[pnt,dir]) -- Make a box located\nin pnt with the d
imensions (length,width,height)\nBy default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
"""
return Shape.cast(FreeCAD.Part.makeBox(length,width,height,pnt.wrapped,dir.wrapped))
return Shape.cast(FreeCADPart.makeBox(length,width,height,pnt.wrapped,dir.wrapped))
@classmethod
def makeCone(cls,radius1,radius2,height,pnt=Vector(0,0,0),dir=Vector(0,0,1),angleDegrees=360):
@ -577,7 +579,7 @@ class Solid(Shape):
Make a cone with given radii and height\nBy default pnt=Vector(0,0,0),
dir=Vector(0,0,1) and angle=360'
"""
return Shape.cast(FreeCAD.Part.makeCone(radius1,radius2,height,pnt.wrapped,dir.wrapped,angleDegrees))
return Shape.cast(FreeCADPart.makeCone(radius1,radius2,height,pnt.wrapped,dir.wrapped,angleDegrees))
@classmethod
def makeCylinder(cls,radius,height,pnt=Vector(0,0,0),dir=Vector(0,0,1),angleDegrees=360):
@ -586,7 +588,7 @@ class Solid(Shape):
Make a cylinder with a given radius and height
By default pnt=Vector(0,0,0),dir=Vector(0,0,1) and angle=360'
"""
return Shape.cast(FreeCAD.Part.makeCylinder(radius,height,pnt.wrapped,dir.wrapped,angleDegrees))
return Shape.cast(FreeCADPart.makeCylinder(radius,height,pnt.wrapped,dir.wrapped,angleDegrees))
@classmethod
def makeTorus(cls,radius1,radius2,pnt=None,dir=None,angleDegrees1=None,angleDegrees2=None):
@ -596,7 +598,7 @@ class Solid(Shape):
By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0
,angle1=360 and angle=360'
"""
return Shape.cast(FreeCAD.Part.makeTorus(radius1,radius2,pnt,dir,angleDegrees1,angleDegrees2))
return Shape.cast(FreeCADPart.makeTorus(radius1,radius2,pnt,dir,angleDegrees1,angleDegrees2))
@classmethod
def sweep(cls,profileWire,pathWire):
@ -615,11 +617,11 @@ class Solid(Shape):
"""
makes a loft from a list of wires
The wires will be converted into faces when possible-- it is presumed that nobody ever actually
wants to make an infinitely thin shell for a real FreeCAD.Part.
wants to make an infinitely thin shell for a real FreeCADPart.
"""
#the True flag requests building a solid instead of a shell.
return Shape.cast(FreeCAD.Part.makeLoft([i.wrapped for i in listOfWire],True))
return Shape.cast(FreeCADPart.makeLoft([i.wrapped for i in listOfWire],True))
@classmethod
def makeWedge(cls,xmin,ymin,zmin,z2min,x2min,xmax,ymax,zmax,z2max,x2max,pnt=None,dir=None):
@ -629,7 +631,7 @@ class Solid(Shape):
Make a wedge located in pnt\nBy default pnt=Vector(0,0,0) and dir=Vec
tor(0,0,1)'
"""
return Shape.cast(FreeCAD.Part.makeWedge(xmin,ymin,zmin,z2min,x2min,xmax,ymax,zmax,z2max,x2max,pnt,dir))
return Shape.cast(FreeCADPart.makeWedge(xmin,ymin,zmin,z2min,x2min,xmax,ymax,zmax,z2max,x2max,pnt,dir))
@classmethod
def makeSphere(cls,radius,pnt=None,angleDegrees1=None,angleDegrees2=None,angleDegrees3=None):
@ -638,7 +640,7 @@ class Solid(Shape):
Make a sphere with a giv
en radius\nBy default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360'
"""
return Solid(FreeCAD.Part.makeSphere(radius,pnt,angleDegrees1,angleDegrees2,angleDegrees3))
return Solid(FreeCADPart.makeSphere(radius,pnt,angleDegrees1,angleDegrees2,angleDegrees3))
@classmethod
def extrudeLinearWithRotation(cls,outerWire,innerWires,vecCenter, vecNormal,angleDegrees):
@ -679,12 +681,12 @@ class Solid(Shape):
#make a ruled surface for each set of wires
sides = []
for w1,w2 in zip(startWires,endWires):
rs = FreeCAD.Part.makeRuledSurface(w1,w2)
rs = FreeCADPart.makeRuledSurface(w1,w2)
sides.append(rs)
#make faces for the top and bottom
startFace = FreeCAD.Part.Face(startWires)
endFace = FreeCAD.Part.Face(endWires)
startFace = FreeCADPart.Face(startWires)
endFace = FreeCADPart.Face(endWires)
#collect all the faces from the sides
faceList = [ startFace]
@ -692,8 +694,8 @@ class Solid(Shape):
faceList.extend(s.Faces)
faceList.append(endFace)
shell = FreeCAD.Part.makeShell(faceList)
solid = FreeCAD.Part.makeSolid(shell)
shell = FreeCADPart.makeShell(faceList)
solid = FreeCADPart.makeSolid(shell)
return Shape.cast(solid)
@classmethod
@ -730,7 +732,7 @@ class Solid(Shape):
for w in innerWires:
freeCADWires.append(w.wrapped)
f = FreeCAD.Part.Face(freeCADWires)
f = FreeCADPart.Face(freeCADWires)
result = f.extrude(vecNormal.wrapped)
return Shape.cast(result)
@ -794,7 +796,7 @@ class Compound(Shape):
Create a compound out of a list of shapes
"""
solids = [s.wrapped for s in listOfShapes]
c = FreeCAD.Part.Compound(solids)
c = FreeCADPart.Compound(solids)
return Shape.cast( c)
def fuse(self,toJoin):

View File

@ -0,0 +1,113 @@
"""
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.
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/>
An exporter should provide functionality to accept a shape, and return
a string containing the model content.
"""
import re
from importlib import import_module
import os
import sys
MEMO_VERSION = None
SUBMODULES = None
_PATH = None
def _figure_out_version(freecadversion):
"""Break this out for testability."""
return tuple(
((int(re.sub("([0-9]*).*", "\\1", part) or 0))
for part in freecadversion[:3]))
def _fc_path():
"""Find FreeCAD"""
global _PATH
if _PATH:
return _PATH
if sys.platform.startswith('linux'):
#Make some dangerous assumptions...
for _PATH in [
os.path.join(os.path.expanduser("~"), "lib/freecad/lib"),
"/usr/local/lib/freecad/lib",
"/usr/lib/freecad/lib",
]:
if os.path.exists(_PATH):
return _PATH
elif sys.platform.startswith('win'):
for _PATH in [
"c:/apps/FreeCAD0.12/bin",
"c:/apps/FreeCAD0.13/bin",
]:
if os.path.exists(_PATH):
return _PATH
def freecad_version():
"""Determine the freecad version and return it as a simple
comparable tuple"""
#If we cannot find freecad, we append it to the path if possible
_pthtmp = _fc_path()
if not _pthtmp in sys.path:
sys.path.append(_pthtmp)
import FreeCAD
global MEMO_VERSION
if not MEMO_VERSION:
MEMO_VERSION = _figure_out_version(FreeCAD.Version())
return MEMO_VERSION
def _find_submodules():
"""Find the list of allowable submodules in fc13"""
global SUBMODULES
searchpath = _fc_path()
if not SUBMODULES:
SUBMODULES = [
re.sub("(.*)\\.(py|so)","\\1", filename)
for filename in os.listdir(searchpath)
if (
filename.endswith(".so") or
filename.endswith(".py") or
filename.endswith(".dll") )] #Yes, complex. Sorry.
return SUBMODULES
def fc_import(modulename):
"""Intelligent import of freecad components.
If we are in 0.12, we can import FreeCAD.Drawing
If we are in 0.13, we need to set sys.path and import Drawing as toplevel.
This may or may not be a FreeCAD bug though.
This is ludicrously complex and feels awful. Kinda like a lot of OCC.
"""
#Note that this also sets the path as a side effect.
_fcver = freecad_version()
if _fcver >= (0, 13):
if modulename in _find_submodules():
return import_module(modulename)
elif re.sub("^FreeCAD\\.", "", modulename) in _find_submodules():
return import_module(re.sub("^FreeCAD\\.", "", modulename))
else:
raise ImportError, "Module %s not found/allowed in %s" % (
modulename, _PATH)
elif _fcver >= (0, 12):
return import_module(modulename)
else:
raise RuntimeError, "Invalid freecad version: %s" % \
str(".".join(_fcver))
__ALL__ = ['fc_import', 'freecad_version']

View File

@ -1,4 +1,4 @@
from distutils.core import setup
from setuptools import setup
setup(
name='cadquery',

View File

@ -3,7 +3,13 @@ import sys
import unittest
from tests import BaseTest
import FreeCAD
from cadquery.freecad_impl.verutil import fc_import
FreeCAD = fc_import("FreeCAD")
if not hasattr(FreeCAD, 'Part'):
FreeCAD.Part = fc_import("FreeCAD.Part")
from cadquery import *
class TestCadObjects(BaseTest):

View File

@ -11,7 +11,11 @@ from cadquery import exporters
from tests import BaseTest,writeStringToFile,makeUnitCube,readFileAsString,makeUnitSquareWire,makeCube
#where unit test output will be saved
OUTDIR = "c:/temp"
import sys
if sys.platform.startswith("win"):
OUTDIR = "c:/temp"
else:
OUTDIR = "/tmp"
SUMMARY_FILE = os.path.join(OUTDIR,"testSummary.html")
SUMMARY_TEMPLATE="""<html>
@ -422,13 +426,14 @@ class TestCadQuery(BaseTest):
def testBasicLines(self):
"Make a triangluar boss"
global OUTDIR
s = Workplane(Plane.XY())
#TODO: extrude() should imply wire() if not done already
#most users dont understand what a wire is, they are just drawing
r = s.lineTo(1.0,0).lineTo(0,1.0).close().wire().extrude(0.25)
r.val().exportStep('c:/temp/testBasicLinesStep1.STEP')
r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesStep1.STEP'))
self.assertEqual(0,s.faces().size()) #no faces on the original workplane
self.assertEqual(5,r.faces().size() ) # 5 faces on newly created object
@ -436,12 +441,12 @@ class TestCadQuery(BaseTest):
#now add a circle through a side face
r.faces("+XY").workplane().circle(0.08).cutThruAll()
self.assertEqual(6,r.faces().size())
r.val().exportStep('c:/temp/testBasicLinesXY.STEP')
r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesXY.STEP'))
#now add a circle through a top
r.faces("+Z").workplane().circle(0.08).cutThruAll()
self.assertEqual(9,r.faces().size())
r.val().exportStep('c:/temp/testBasicLinesZ.STEP')
r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesZ.STEP'))
self.saveModel(r)

30
tests/TestImports.py Normal file
View File

@ -0,0 +1,30 @@
"""
Tests basic workplane functionality
"""
#core modules
#my modules
from cadquery.freecad_impl import verutil
from tests import BaseTest
class TestVersionsForImport(BaseTest):
"""Test version checks."""
def test_013_version(self):
"""Make sure various 0.13 Version calls work correctly"""
self.assertEquals(verutil._figure_out_version(
['0', '13', '2055 (Git)',
'git://git.code.sf.net/p/free-cad/code',
'2013/04/18 13:48:49', 'master',
'3511a807a30cf41909aaf12a1efe1db6c53db577']),
(0,13,2055))
self.assertEquals(verutil._figure_out_version(
['0', '13', '12345']),
(0,13,12345))
self.assertEquals(verutil._figure_out_version(
['0', '13', 'SOMETAGTHATBREAKSSTUFF']),
(0,13,0))

View File

@ -1,12 +1,13 @@
from cadquery import *
import unittest
import sys
FREECAD_LIB = "c:/apps/FreeCAD0.12/bin";
sys.path.append(FREECAD_LIB);
import FreeCAD
import os
P = FreeCAD.Part
V = FreeCAD.Base.Vector
from cadquery.freecad_impl.verutil import fc_import
FreeCAD = fc_import("FreeCAD")
P = fc_import("FreeCAD.Part")
V = fc_import("FreeCAD").Base.Vector
def readFileAsString(fileName):
f= open(fileName,'r')