Implemented build and parameter search CQGI functionality, and updated the CadQuery library.

This commit is contained in:
Jeremy Mack Wright 2017-07-23 00:22:57 -04:00
parent 2d7344e07d
commit 4c06752c39
16 changed files with 216 additions and 110 deletions

View File

@ -9,6 +9,8 @@ import ExportCQ, ImportCQ
import module_locator import module_locator
import Settings import Settings
import Shared import Shared
from cadquery import cqgi
from Helpers import show
# Distinguish python built-in open function from the one declared here # Distinguish python built-in open function from the one declared here
if open.__module__ == '__builtin__': if open.__module__ == '__builtin__':
@ -122,9 +124,34 @@ class CadQueryExecuteScript:
# Clear the old render before re-rendering # Clear the old render before re-rendering
Shared.clearActiveDocument() Shared.clearActiveDocument()
# Check to see if we are executig a CQGI compliant script
scriptText = cqCodePane.toPlainText().encode('utf-8')
if "build_object(" in scriptText and "# build_object(" not in scriptText and "#build_boject(" not in scriptText:
FreeCAD.Console.PrintMessage("Executing CQGI-compliant script.\r\n")
# A repreentation of the CQ script with all the metadata attached
cqModel = cqgi.parse(scriptText)
# Allows us to present parameters to users later that they can alter
parameters = cqModel.metadata.parameters
FreeCAD.Console.PrintMessage("Script Variables:\r\n")
for key, value in parameters.iteritems():
FreeCAD.Console.PrintMessage("variable name: " + key + ", variable value: " + str(value.default_value) + "\r\n")
build_result = cqgi.parse(scriptText).build()
# Make sure that the build was successful
if build_result.success:
# Display all the results that the user requested
for result in build_result.results:
show(result)
else:
FreeCAD.Console.PrintError("Error executing CQGI-compliant script.\r\n")
else:
# Save our code to a tempfile and render it # Save our code to a tempfile and render it
tempFile = tempfile.NamedTemporaryFile(delete=False) tempFile = tempfile.NamedTemporaryFile(delete=False)
tempFile.write(cqCodePane.toPlainText().encode('utf-8')) tempFile.write(scriptText)
tempFile.close() tempFile.close()
# Set some environment variables that may help the user # Set some environment variables that may help the user

File diff suppressed because one or more lines are too long

View File

@ -7,21 +7,22 @@ install:
- gcc --version - gcc --version
- g++ --version - g++ --version
- python ./setup.py install - python ./setup.py install
- pip install coverage - pip install -r requirements-dev.txt
- pip install coveralls
- pip install Sphinx==1.3.2
- pip install travis-sphinx - pip install travis-sphinx
- pip install pyparsing
script: script:
- coverage run --source=cadquery ./runtests.py - coverage run --source=cadquery ./runtests.py
- travis-sphinx --nowarn --source=doc build - travis-sphinx --nowarn --source=doc build
after_success: after_success:
- coveralls - coveralls
- travis-sphinx deploy - travis-sphinx deploy
branches: branches:
except: except:
- pythonocc - pythonocc
- 2_0_branch - 2_0_branch
deploy: deploy:
provider: pypi provider: pypi
user: dcowden user: dcowden

View File

@ -10,9 +10,9 @@ CadQuery is an intuitive, easy-to-use python based language for building paramet
CadQuery has several goals: CadQuery has several goals:
* Build models with scripts that are as close as possible to how you'd describe the object to a human. * Build lD models with scripts that are as close as possible to how you'd describe the object to a human.
* Create parametric models that can be very easily customized by end users * Create parametric models that can be very easily customized by end users
* Output high quality CAD formats like STEP and AMF in addition to traditional STL * Output high quality (loss-less) CAD formats like STEP and AMF in addition to traditional STL
* Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser * Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized. Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized.
@ -21,7 +21,6 @@ Full Documentation
============================ ============================
You can find the full cadquery documentation at http://dcowden.github.io/cadquery You can find the full cadquery documentation at http://dcowden.github.io/cadquery
Getting Started With CadQuery Getting Started With CadQuery
======================================== ========================================
@ -113,6 +112,40 @@ The cadquery script is surprisingly short, and allows easily customizing any of
Thanks go to cadquery contributor hyOzd ( Altu Technology ) for the example! Thanks go to cadquery contributor hyOzd ( Altu Technology ) for the example!
Projects Using CadQuery
=========================
KiCad uses cadquery to build high quality models of electrictronic components. ( https://github.com/KiCad/packages3D )
<p align="center">
<img src="https://forum.freecadweb.org/download/file.php?id=33797&sid=b8584f80928497722e9ee9d582a3fa43" width="350"/>
</p>
This Prusa i3 extruder support uses cadquery to build the model (https://github.com/adam-urbanczyk/cadquery-models) :
<p align="center">
<img src="https://github.com/adam-urbanczyk/cadquery-models/raw/master/extruder_support.png" width="350"/>
</p>
The mach30 project used cadquery to develop a tool that will create a rocket thruster directly from the appropriate equations (https://opendesignengine.net/projects/yavin-thruster/wiki):
<p align="center">
<img src="http://opendesignengine.net/dmsf_files/480?download=" width="700"/>
</p>
This example uses Jupyter notebook to produce a really cool web-based scripting environment ( https://github.com/RustyVermeer/avnb/blob/master/readme.md ) :
<p align="center">
<img src="https://github.com/RustyVermeer/avnb/raw/master/example.gif" width="700"/>
</p>
We would love to link to your cadquery based project. Just let us know and we'll add it here.
Why CadQuery instead of OpenSCAD? Why CadQuery instead of OpenSCAD?
======================================== ========================================

View File

@ -1047,7 +1047,7 @@ class Workplane(CQ):
false, the lower left corner will be at the center of the work plane false, the lower left corner will be at the center of the work plane
""" """
if xSpacing < 1 or ySpacing < 1 or xCount < 1 or yCount < 1: if xSpacing <= 0 or ySpacing <= 0 or xCount < 1 or yCount < 1:
raise ValueError("Spacing and count must be > 0 ") raise ValueError("Spacing and count must be > 0 ")
lpoints = [] # coordinates relative to bottom left point lpoints = [] # coordinates relative to bottom left point

View File

@ -115,11 +115,11 @@ class CQModel(object):
else: else:
raise NoOutputError("Script did not call build_object-- no output available.") raise NoOutputError("Script did not call build_object-- no output available.")
except Exception, ex: except Exception, ex:
print "Error Executing Script:" #print "Error Executing Script:"
result.set_failure_result(ex) result.set_failure_result(ex)
traceback.print_exc() #traceback.print_exc()
print "Full Text of Script:" #print "Full Text of Script:"
print self.script_source #print self.script_source
end = time.clock() end = time.clock()
result.buildTime = end - start result.buildTime = end - start
@ -180,7 +180,7 @@ class ScriptMetadata(object):
self.parameters[p.name] = p self.parameters[p.name] = p
def add_parameter_description(self,name,description): def add_parameter_description(self,name,description):
print 'Adding Parameter name=%s, desc=%s' % ( name, description ) #print 'Adding Parameter name=%s, desc=%s' % ( name, description )
p = self.parameters[name] p = self.parameters[name]
p.desc = description p.desc = description
@ -418,7 +418,7 @@ class ParameterDescriptionFinder(ast.NodeTransformer):
self.cqModel.add_parameter_description(varname,desc) self.cqModel.add_parameter_description(varname,desc)
except: except:
print "Unable to handle function call" #print "Unable to handle function call"
pass pass
return node return node

View File

@ -35,6 +35,7 @@ def _fc_path():
"/usr/lib/freecad/lib", "/usr/lib/freecad/lib",
"/opt/freecad/lib/", "/opt/freecad/lib/",
"/usr/bin/freecad/lib", "/usr/bin/freecad/lib",
"/usr/lib/freecad-daily/lib",
"/usr/lib/freecad", "/usr/lib/freecad",
"/usr/lib64/freecad/lib", "/usr/lib64/freecad/lib",
]: ]:

View File

@ -420,11 +420,17 @@ class Edge(Shape):
# self.endPoint = None # self.endPoint = None
self.edgetypes = { self.edgetypes = {
FreeCADPart.Line: 'LINE',
FreeCADPart.ArcOfCircle: 'ARC', FreeCADPart.ArcOfCircle: 'ARC',
FreeCADPart.Circle: 'CIRCLE' FreeCADPart.Circle: 'CIRCLE'
} }
if hasattr(FreeCADPart,"LineSegment"):
#FreeCAD <= 0.16
self.edgetypes[FreeCADPart.LineSegment] = 'LINE'
else:
#FreeCAD >= 0.17
self.edgetypes[FreeCADPart.Line] = 'LINE'
# Helps identify this solid through the use of an ID # Helps identify this solid through the use of an ID
self.label = "" self.label = ""

View File

@ -20,6 +20,7 @@
import re import re
import math import math
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
from collections import defaultdict
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\ from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\
CaselessLiteral,Group,infixNotation,opAssoc,Forward,\ CaselessLiteral,Group,infixNotation,opAssoc,Forward,\
ZeroOrMore,Keyword ZeroOrMore,Keyword
@ -299,11 +300,6 @@ class DirectionMinMaxSelector(Selector):
def distance(tShape): def distance(tShape):
return tShape.Center().dot(self.vector) return tShape.Center().dot(self.vector)
#if tShape.ShapeType == 'Vertex':
# pnt = tShape.Point
#else:
# pnt = tShape.Center()
#return pnt.dot(self.vector)
# import OrderedDict # import OrderedDict
from collections import OrderedDict from collections import OrderedDict
@ -336,10 +332,7 @@ class DirectionNthSelector(ParallelDirSelector):
self.max = max self.max = max
self.directionMax = directionMax self.directionMax = directionMax
self.TOLERANCE = tolerance self.TOLERANCE = tolerance
if directionMax: self.N = n
self.N = n #do we want indexing from 0 or from 1?
else:
self.N = -n
def filter(self,objectList): def filter(self,objectList):
#select first the objects that are normal/parallel to a given dir #select first the objects that are normal/parallel to a given dir
@ -347,23 +340,22 @@ class DirectionNthSelector(ParallelDirSelector):
def distance(tShape): def distance(tShape):
return tShape.Center().dot(self.direction) return tShape.Center().dot(self.direction)
#if tShape.ShapeType == 'Vertex':
# pnt = tShape.Point
#else:
# pnt = tShape.Center()
#return pnt.dot(self.vector)
#make and distance to object dict
objectDict = {distance(el) : el for el in objectList}
#calculate how many digits of precision do we need #calculate how many digits of precision do we need
digits = int(1/self.TOLERANCE) digits = int(1/self.TOLERANCE)
# create a rounded distance to original distance mapping (implicitly perfroms unique operation)
dist_round_dist = {round(d,digits) : d for d in objectDict.keys()} #make a distance to object dict
#this is one to many mapping so I am using a default dict with list
objectDict = defaultdict(list)
for el in objectList:
objectDict[round(distance(el),digits)].append(el)
# choose the Nth unique rounded distance # choose the Nth unique rounded distance
nth_d = dist_round_dist[sorted(dist_round_dist.keys())[self.N]] nth_distance = sorted(objectDict.keys(),
reverse=not self.directionMax)[self.N]
# map back to original objects and return # map back to original objects and return
return [objectDict[d] for d in objectDict.keys() if abs(d-nth_d) < self.TOLERANCE] return objectDict[nth_distance]
class BinarySelector(Selector): class BinarySelector(Selector):
""" """

View File

@ -157,6 +157,7 @@ as a basis for futher operations.
BaseDirSelector BaseDirSelector
ParallelDirSelector ParallelDirSelector
DirectionSelector DirectionSelector
DirectionNthSelector
PerpendicularDirSelector PerpendicularDirSelector
TypeSelector TypeSelector
DirectionMinMaxSelector DirectionMinMaxSelector

View File

@ -52,6 +52,7 @@ Selector Classes
BaseDirSelector BaseDirSelector
ParallelDirSelector ParallelDirSelector
DirectionSelector DirectionSelector
DirectionNthSelector
PerpendicularDirSelector PerpendicularDirSelector
TypeSelector TypeSelector
DirectionMinMaxSelector DirectionMinMaxSelector

View File

@ -47,11 +47,6 @@ If you prefer to have a GUI available, your best option is to use
Simply extract cadquery-freecad-module into your FreeCAD installation. You'll end up Simply extract cadquery-freecad-module into your FreeCAD installation. You'll end up
with a cadquery workbench that allows you to interactively run scripts, and then see the results in the FreeCAD GUI with a cadquery workbench that allows you to interactively run scripts, and then see the results in the FreeCAD GUI
If you are using Ubuntu, you can also install it via this ppa:
https://code.launchpad.net/~freecad-community/+archive/ubuntu/ppa/+packages
Zero Step Install Zero Step Install
------------------------------------------------- -------------------------------------------------

View File

@ -29,18 +29,6 @@ face.outerWire
Selectors Selectors
-------------------- --------------------
Chained Selectors
Space delimited selectors should be unioned to allow multiple selections. For example ">Z >X"
Ad-hoc axes
for example, >(1,2,1) would select a face with normal in the 1,2,1 direction
logic inversion
! or not to invert logic, such as "!(>Z)" to select faces _other_ than the most z facing
closest to point
support faces, points, or edges closest to a provided point
tagged entities tagged entities
support tagging entities when they are created, so they can be selected later on using that tag. support tagging entities when they are created, so they can be selected later on using that tag.
ideally, tags are propagated to features that are created from these features ( ie, an edge tagged with 'foo' ideally, tags are propagated to features that are created from these features ( ie, an edge tagged with 'foo'

View File

@ -28,26 +28,28 @@ string patterns in, CadQuery will automatically use the associated selector obje
Combining Selectors Combining Selectors
========================== ==========================
Selectors can be combined arithmetically and logically, so that it is possible to do intersection, union, and other Selectors can be combined logically, currently defined operators include **and**, **or**, **not** and **exc[ept]** (set difference). For example:
combinations. For example::
box = cadquery.Workplane("XY").box(10,10,10) .. cq_plot::
s = selectors.StringSyntaxSelector result = cq.Workplane("XY").box(2, 2, 2) \
.edges("|Z and >Y") \
.chamfer(0.2)
### select all edges on right and left faces build_object(result)
#box = box.edges((s("|Z") + s("|Y"))).fillet(1)
### select all edges on top and bottom Much more complex expressions are possible as well:
#box = box.edges(-s("|Z")).fillet(1)
#box = box.edges(s('|X')+s('Y')).fillet(1)
box = box.faces(s('>Z')+s('<Z')).fillet(1)
.. cq_plot::
or for another example:: result = cq.Workplane("XY").box(2, 2, 2) \
.faces(">Z") \
.shell(-0.2) \
.faces(">Z") \
.edges("not(<X or >X or <Y or >Y)") \
.chamfer(0.1)
# select diagonal edges build_object(result)
box = box.faces(s('>Z')+s('<Z')).edges(-s('|X')-s('Y')).fillet(1)
.. _filteringfaces: .. _filteringfaces:
@ -64,17 +66,19 @@ of the face.
The axis used in the listing below are for illustration: any axis would work similarly in each case. The axis used in the listing below are for illustration: any axis would work similarly in each case.
========= ====================================== ======================================================= ========================== ========= ======================================= ======================================================= ==========================
Selector Selects Selector Class # objects returned Selector Selects Selector Class # objects returned
========= ====================================== ======================================================= ========================== ========= ======================================= ======================================================= ==========================
+Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0 or 1 +Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0..many
\|Z Faces parallel to xy plane :py:class:`cadquery.ParallelDirSelector` 0..many \|Z Faces parallel to xy plane :py:class:`cadquery.ParallelDirSelector` 0..many
-X Faces with normal in neg x direction :py:class:`cadquery.DirectionSelector` 0..many -X Faces with normal in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
#Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many #Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
%Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many %Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many
>Y Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 >Y Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
<Y Face farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 <Y Face farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
========= ====================================== ======================================================= ========================== >Y[-2] 2nd Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
<Y[0] 1st closest Fase in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
========= ======================================= ======================================================= ==========================
.. _filteringedges: .. _filteringedges:
@ -92,17 +96,19 @@ Some filter types are not supported for edges. The selector usually refers to t
The axis used in the listing below are for illustration: any axis would work similarly in each case. The axis used in the listing below are for illustration: any axis would work similarly in each case.
========= ==================================== ======================================================= ========================== ========= ======================================= ======================================================= ==========================
Selector Selects Selector Class # objects returned Selector Selects Selector Class # objects returned
========= ==================================== ======================================================= ========================== ========= ======================================= ======================================================= ==========================
+Z Edges aligned in the Z direction :py:class:`cadquery.DirectionSelector` 0..many +Z Edges aligned in the Z direction :py:class:`cadquery.DirectionSelector` 0..many
\|Z Edges parallel to z direction :py:class:`cadquery.ParallelDirSelector` 0..many \|Z Edges parallel to z direction :py:class:`cadquery.ParallelDirSelector` 0..many
-X Edges aligned in neg x direction :py:class:`cadquery.DirectionSelector` 0..many -X Edges aligned in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
#Z Edges perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many #Z Edges perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
%Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many %Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many
>Y Edges farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 >Y Edges farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
<Y Edges farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 <Y Edges farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
========= ==================================== ======================================================= ========================== >Y[1] 2nd closest edge in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
<Y[-2] 2nd farthest edge in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
========= ======================================= ======================================================= ==========================
.. _filteringvertices: .. _filteringvertices:
@ -115,12 +121,20 @@ Only a few of the filter types apply to vertices. The location of the vertex is
========= ======================================= ======================================================= ========================== ========= ======================================= ======================================================= ==========================
Selector Selects Selector Class # objects returned Selector Selects Selector Class # objects returned
========= ======================================= ======================================================= ========================== ========= ======================================= ======================================================= ==========================
>Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 >Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
<Y Vertices farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1 <Y Vertices farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
========= ======================================= ======================================================= ========================== ========= ======================================= ======================================================= ==========================
Future Enhancements User-defined Directions
-------------------- -----------------------
* Support direct vectors inline, such as \|(x,y,z) It is possible to use user defined vectors as a basis for the selectors. For example:
* Support multiple selectors separated by spaces, which unions the results, such as "+Z +Y to select both z and y-most faces
.. cq_plot::
result = cq.Workplane("XY").box(10,10,10)
# chamfer only one edge
result = result.edges('>(-1,1,0)').chamfer(1)
build_object(result)

View File

@ -1,3 +1,6 @@
sphinx-rtd-theme==0.1.9 sphinx-rtd-theme==0.1.9
travis-sphinx==1.1.0 travis-sphinx
Sphinx==1.3.1 Sphinx==1.3.2
coverage
coveralls
pyparsing

View File

@ -194,10 +194,14 @@ class TestCQSelectors(BaseTest):
#2nd face #2nd face
val = c.faces('>(1,0,0)[1]').val() val = c.faces('>(1,0,0)[1]').val()
self.assertAlmostEqual(val.Center().x,-1.5) self.assertAlmostEqual(val.Center().x,-1.5)
val = c.faces('>X[1]').val()
self.assertAlmostEqual(val.Center().x,-1.5)
#2nd face with inversed selection vector #2nd face with inversed selection vector
val = c.faces('>(-1,0,0)[1]').val() val = c.faces('>(-1,0,0)[1]').val()
self.assertAlmostEqual(val.Center().x,1.5) self.assertAlmostEqual(val.Center().x,1.5)
val = c.faces('<X[1]').val()
self.assertAlmostEqual(val.Center().x,1.5)
#2nd last face #2nd last face
val = c.faces('>X[-2]').val() val = c.faces('>X[-2]').val()
@ -210,6 +214,46 @@ class TestCQSelectors(BaseTest):
#check if the selected face if normal to the specified Vector #check if the selected face if normal to the specified Vector
self.assertAlmostEqual(val.normalAt().cross(Vector(1,0,0)).Length,0.0) self.assertAlmostEqual(val.normalAt().cross(Vector(1,0,0)).Length,0.0)
#test selection of multiple faces with the same distance
c = Workplane('XY')\
.box(1,4,1,centered=(False,True,False)).faces('<Z')\
.box(2,2,2,centered=(True,True,False)).faces('>Z')\
.box(1,1,1,centered=(True,True,False))
#select 2nd from the bottom (NB python indexing is 0-based)
vals = c.faces('>Z[1]').vals()
self.assertEqual(len(vals),2)
val = c.faces('>Z[1]').val()
self.assertAlmostEqual(val.Center().z,1)
#do the same but by selecting 3rd from the top
vals = c.faces('<Z[2]').vals()
self.assertEqual(len(vals),2)
val = c.faces('<Z[2]').val()
self.assertAlmostEqual(val.Center().z,1)
#do the same but by selecting 2nd last from the bottom
vals = c.faces('<Z[-2]').vals()
self.assertEqual(len(vals),2)
val = c.faces('<Z[-2]').val()
self.assertAlmostEqual(val.Center().z,1)
#verify that <Z[-1] is equivalent to <Z
val1 = c.faces('<Z[-1]').val()
val2 = c.faces('<Z').val()
self.assertTupleAlmostEquals(val1.Center().toTuple(),
val2.Center().toTuple(),
3)
#verify that >Z[-1] is equivalent to >Z
val1 = c.faces('>Z[-1]').val()
val2 = c.faces('>Z').val()
self.assertTupleAlmostEquals(val1.Center().toTuple(),
val2.Center().toTuple(),
3)
def testNearestTo(self): def testNearestTo(self):
c = CQ(makeUnitCube()) c = CQ(makeUnitCube())