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 Settings
import Shared
from cadquery import cqgi
from Helpers import show
# Distinguish python built-in open function from the one declared here
if open.__module__ == '__builtin__':
@ -122,17 +124,42 @@ class CadQueryExecuteScript:
# Clear the old render before re-rendering
Shared.clearActiveDocument()
# Save our code to a tempfile and render it
tempFile = tempfile.NamedTemporaryFile(delete=False)
tempFile.write(cqCodePane.toPlainText().encode('utf-8'))
tempFile.close()
# 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")
# Set some environment variables that may help the user
os.environ["MYSCRIPT_FULL_PATH"] = cqCodePane.file.path
os.environ["MYSCRIPT_DIR"] = os.path.dirname(os.path.abspath(cqCodePane.file.path))
# A repreentation of the CQ script with all the metadata attached
cqModel = cqgi.parse(scriptText)
# We import this way because using execfile() causes non-standard script execution in some situations
imp.load_source('temp_module', tempFile.name)
# 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
tempFile = tempfile.NamedTemporaryFile(delete=False)
tempFile.write(scriptText)
tempFile.close()
# Set some environment variables that may help the user
os.environ["MYSCRIPT_FULL_PATH"] = cqCodePane.file.path
os.environ["MYSCRIPT_DIR"] = os.path.dirname(os.path.abspath(cqCodePane.file.path))
# We import this way because using execfile() causes non-standard script execution in some situations
imp.load_source('temp_module', tempFile.name)
msg = QtGui.QApplication.translate(
"cqCodeWidget",

File diff suppressed because one or more lines are too long

View File

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

View File

@ -10,9 +10,9 @@ CadQuery is an intuitive, easy-to-use python based language for building paramet
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
* 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
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
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!
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?
========================================

View File

@ -1047,7 +1047,7 @@ class Workplane(CQ):
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 ")
lpoints = [] # coordinates relative to bottom left point

View File

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

View File

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

View File

@ -420,11 +420,17 @@ class Edge(Shape):
# self.endPoint = None
self.edgetypes = {
FreeCADPart.Line: 'LINE',
FreeCADPart.ArcOfCircle: 'ARC',
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
self.label = ""

View File

@ -20,6 +20,7 @@
import re
import math
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
from collections import defaultdict
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\
CaselessLiteral,Group,infixNotation,opAssoc,Forward,\
ZeroOrMore,Keyword
@ -299,11 +300,6 @@ class DirectionMinMaxSelector(Selector):
def distance(tShape):
return tShape.Center().dot(self.vector)
#if tShape.ShapeType == 'Vertex':
# pnt = tShape.Point
#else:
# pnt = tShape.Center()
#return pnt.dot(self.vector)
# import OrderedDict
from collections import OrderedDict
@ -336,34 +332,30 @@ class DirectionNthSelector(ParallelDirSelector):
self.max = max
self.directionMax = directionMax
self.TOLERANCE = tolerance
if directionMax:
self.N = n #do we want indexing from 0 or from 1?
else:
self.N = -n
self.N = n
def filter(self,objectList):
#select first the objects that are normal/parallel to a given dir
objectList = super(DirectionNthSelector,self).filter(objectList)
def distance(tShape):
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
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
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
return [objectDict[d] for d in objectDict.keys() if abs(d-nth_d) < self.TOLERANCE]
return objectDict[nth_distance]
class BinarySelector(Selector):
"""

View File

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

View File

@ -52,6 +52,7 @@ Selector Classes
BaseDirSelector
ParallelDirSelector
DirectionSelector
DirectionNthSelector
PerpendicularDirSelector
TypeSelector
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
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
-------------------------------------------------

View File

@ -29,18 +29,6 @@ face.outerWire
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
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'

View File

@ -28,26 +28,28 @@ string patterns in, CadQuery will automatically use the associated selector obje
Combining Selectors
==========================
Selectors can be combined arithmetically and logically, so that it is possible to do intersection, union, and other
combinations. For example::
Selectors can be combined logically, currently defined operators include **and**, **or**, **not** and **exc[ept]** (set difference). 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)
build_object(result)
### select all edges on right and left faces
#box = box.edges((s("|Z") + s("|Y"))).fillet(1)
Much more complex expressions are possible as well:
### select all edges on top and bottom
#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::
# select diagonal edges
box = box.faces(s('>Z')+s('<Z')).edges(-s('|X')-s('Y')).fillet(1)
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)
build_object(result)
.. _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.
========= ====================================== ======================================================= ==========================
Selector Selects Selector Class # objects returned
========= ====================================== ======================================================= ==========================
+Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0 or 1
\|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
#Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 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 negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
========= ====================================== ======================================================= ==========================
========= ======================================= ======================================================= ==========================
Selector Selects Selector Class # objects returned
========= ======================================= ======================================================= ==========================
+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
-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
%Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many
>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..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:
@ -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.
========= ==================================== ======================================================= ==========================
Selector Selects Selector Class # objects returned
========= ==================================== ======================================================= ==========================
+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
-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
%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 negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
========= ==================================== ======================================================= ==========================
========= ======================================= ======================================================= ==========================
Selector Selects Selector Class # objects returned
========= ======================================= ======================================================= ==========================
+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
-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
%Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many
>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..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:
@ -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
========= ======================================= ======================================================= ==========================
>Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
<Y Vertices farthest in the negative 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..many
========= ======================================= ======================================================= ==========================
Future Enhancements
--------------------
User-defined Directions
-----------------------
* Support direct vectors inline, such as \|(x,y,z)
* Support multiple selectors separated by spaces, which unions the results, such as "+Z +Y to select both z and y-most faces
It is possible to use user defined vectors as a basis for the selectors. For example:
.. 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
travis-sphinx==1.1.0
Sphinx==1.3.1
travis-sphinx
Sphinx==1.3.2
coverage
coveralls
pyparsing

View File

@ -194,10 +194,14 @@ class TestCQSelectors(BaseTest):
#2nd face
val = c.faces('>(1,0,0)[1]').val()
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
val = c.faces('>(-1,0,0)[1]').val()
self.assertAlmostEqual(val.Center().x,1.5)
val = c.faces('<X[1]').val()
self.assertAlmostEqual(val.Center().x,1.5)
#2nd last face
val = c.faces('>X[-2]').val()
@ -210,6 +214,46 @@ class TestCQSelectors(BaseTest):
#check if the selected face if normal to the specified Vector
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):
c = CQ(makeUnitCube())