+
+This Prusa i3 extruder support uses cadquery to build the model (https://github.com/adam-urbanczyk/cadquery-models) :
+
+
+
+
+
+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):
+
+
+
+
+This example uses Jupyter notebook to produce a really cool web-based scripting environment ( https://github.com/RustyVermeer/avnb/blob/master/readme.md ) :
+
+
+
+
+
+
+
+
+
+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?
========================================
diff --git a/Libs/cadquery-lib/cadquery/cq.py b/Libs/cadquery-lib/cadquery/cq.py
index b2d3a50..97f135b 100644
--- a/Libs/cadquery-lib/cadquery/cq.py
+++ b/Libs/cadquery-lib/cadquery/cq.py
@@ -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
diff --git a/Libs/cadquery-lib/cadquery/cqgi.py b/Libs/cadquery-lib/cadquery/cqgi.py
index 01701db..329074b 100644
--- a/Libs/cadquery-lib/cadquery/cqgi.py
+++ b/Libs/cadquery-lib/cadquery/cqgi.py
@@ -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
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py b/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
index 05ed957..3a82a2f 100644
--- a/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
+++ b/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
@@ -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",
]:
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py b/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
index 12567e9..6f65ccc 100644
--- a/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
+++ b/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
@@ -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 = ""
diff --git a/Libs/cadquery-lib/cadquery/selectors.py b/Libs/cadquery-lib/cadquery/selectors.py
index 72174e1..a2d476e 100644
--- a/Libs/cadquery-lib/cadquery/selectors.py
+++ b/Libs/cadquery-lib/cadquery/selectors.py
@@ -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):
"""
diff --git a/Libs/cadquery-lib/doc/apireference.rst b/Libs/cadquery-lib/doc/apireference.rst
index 55aea7d..59b04c6 100644
--- a/Libs/cadquery-lib/doc/apireference.rst
+++ b/Libs/cadquery-lib/doc/apireference.rst
@@ -157,6 +157,7 @@ as a basis for futher operations.
BaseDirSelector
ParallelDirSelector
DirectionSelector
+ DirectionNthSelector
PerpendicularDirSelector
TypeSelector
DirectionMinMaxSelector
diff --git a/Libs/cadquery-lib/doc/classreference.rst b/Libs/cadquery-lib/doc/classreference.rst
index b072b39..ce88a2a 100644
--- a/Libs/cadquery-lib/doc/classreference.rst
+++ b/Libs/cadquery-lib/doc/classreference.rst
@@ -52,6 +52,7 @@ Selector Classes
BaseDirSelector
ParallelDirSelector
DirectionSelector
+ DirectionNthSelector
PerpendicularDirSelector
TypeSelector
DirectionMinMaxSelector
diff --git a/Libs/cadquery-lib/doc/installation.rst b/Libs/cadquery-lib/doc/installation.rst
index 8633a9d..b5160b0 100644
--- a/Libs/cadquery-lib/doc/installation.rst
+++ b/Libs/cadquery-lib/doc/installation.rst
@@ -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
-------------------------------------------------
diff --git a/Libs/cadquery-lib/doc/roadmap.rst b/Libs/cadquery-lib/doc/roadmap.rst
index 3337ff2..a5b7e28 100644
--- a/Libs/cadquery-lib/doc/roadmap.rst
+++ b/Libs/cadquery-lib/doc/roadmap.rst
@@ -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'
diff --git a/Libs/cadquery-lib/doc/selectors.rst b/Libs/cadquery-lib/doc/selectors.rst
index b14a02a..c40c547 100644
--- a/Libs/cadquery-lib/doc/selectors.rst
+++ b/Libs/cadquery-lib/doc/selectors.rst
@@ -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')+s('Z") \
+ .shell(-0.2) \
+ .faces(">Z") \
+ .edges("not(X 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 positive 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 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[1] 2nd closest edge in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
+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
+(-1,1,0)').chamfer(1)
+
+ build_object(result)
diff --git a/Libs/cadquery-lib/requirements-dev.txt b/Libs/cadquery-lib/requirements-dev.txt
index 872a66d..14429e3 100644
--- a/Libs/cadquery-lib/requirements-dev.txt
+++ b/Libs/cadquery-lib/requirements-dev.txt
@@ -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
diff --git a/Libs/cadquery-lib/tests/TestCQSelectors.py b/Libs/cadquery-lib/tests/TestCQSelectors.py
index 9b5e12f..1bc411b 100644
--- a/Libs/cadquery-lib/tests/TestCQSelectors.py
+++ b/Libs/cadquery-lib/tests/TestCQSelectors.py
@@ -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[-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(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[-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())
From ecefa3d707cda3285af25862679c5d740248ee32 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Mon, 24 Jul 2017 16:51:18 -0400
Subject: [PATCH 02/19] Added a toggle for the variable editor and started
stubbing in that and a script validation function.
---
Gui/Command.py | 78 ++++++++++++++++++++++++++++--
InitGui.py | 4 +-
Libs/cadquery-lib/cadquery/cqgi.py | 2 +-
3 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index 31ef1e3..e0db8a1 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -4,7 +4,7 @@
import imp, os, sys, tempfile
import FreeCAD, FreeCADGui
-from PySide import QtGui
+from PySide import QtGui, QtCore
import ExportCQ, ImportCQ
import module_locator
import Settings
@@ -22,7 +22,9 @@ class CadQueryClearOutput:
def GetResources(self):
return {"MenuText": "Clear Output",
- "ToolTip": "Clears the script output from the Reports view"}
+ "Accel": "Shift+Alt+C",
+ "ToolTip": "Clears the script output from the Reports view",
+ "Pixmap": ":/icons/button_invalid.svg"}
def IsActive(self):
return True
@@ -42,7 +44,8 @@ class CadQueryCloseScript:
def GetResources(self):
return {"MenuText": "Close Script",
- "ToolTip": "Closes the CadQuery script"}
+ "ToolTip": "Closes the CadQuery script",
+ "Pixmap": ":/icons/edit_Cancel.svg"}
def IsActive(self):
return True
@@ -311,3 +314,72 @@ class CadQuerySaveAsScript:
# Save the file before closing the original and the re-rendering the new one
ExportCQ.save(filename[0])
CadQueryExecuteScript().Activated()
+
+
+class ToggleVariablesEditor:
+ """If the user is running a CQGI-compliant script, they can edit variables through this edistor"""
+
+ def GetResources(self):
+ return {"MenuText": "Toggle Variables Editor",
+ "Accel": "Shift+Alt+E",
+ "ToolTip": "Opens a live variables editor editor",
+ "Pixmap": ":/icons/edit-edit.svg"}
+
+ def IsActive(self):
+ return True
+
+ def Activated(self):
+ mw = FreeCADGui.getMainWindow()
+
+ # Tracks whether or not we have already added the variables editor
+ isPresent = False
+
+ # If the widget is open, we need to close it
+ dockWidgets = mw.findChildren(QtGui.QDockWidget)
+ for widget in dockWidgets:
+ if widget.objectName() == "cqVarsEditor":
+ # Toggle the visibility of the widget
+ if widget.visibleRegion().isEmpty():
+ widget.setVisible(True)
+ else:
+ widget.setVisible(False)
+
+ isPresent = True
+
+ if not isPresent:
+ cqVariablesEditor = QtGui.QDockWidget("CadQuery Variables Editor")
+ cqVariablesEditor.setObjectName("cqVarsEditor")
+ mw.addDockWidget(QtCore.Qt.LeftDockWidgetArea, cqVariablesEditor)
+
+
+class CadQueryValidateScript:
+ """Checks the script for the user without executing it and populates the variable editor, if needed"""
+
+ def GetResources(self):
+ return {"MenuText": "Validate Script",
+ "Accel": "F4",
+ "ToolTip": "Validates a CadQuery script",
+ "Pixmap": ":/icons/edit_OK.svg"}
+
+ def IsActive(self):
+ return True
+
+ def Activated(self):
+ mw = FreeCADGui.getMainWindow()
+
+ # Tracks whether or not we have already added the variables editor
+ isPresent = False
+
+ # If the widget is open, we need to close it
+ dockWidgets = mw.findChildren(QtGui.QDockWidget)
+ for widget in dockWidgets:
+ if widget.objectName() == "cqVarsEditor":
+ # TODO: Clear and then populate the controls in the widget based on the variables
+
+ # Toggle the visibility of the widget
+ # if widget.visibleRegion().isEmpty():
+ # widget.setVisible(True)
+ # else:
+ # widget.setVisible(False)
+
+ isPresent = True
\ No newline at end of file
diff --git a/InitGui.py b/InitGui.py
index 87d012c..14c0616 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -34,7 +34,7 @@ class CadQueryWorkbench (Workbench):
self.appendMenu('CadQuery', ['CadQueryNewScript', 'CadQueryOpenScript', 'CadQuerySaveScript',
'CadQuerySaveAsScript', 'CadQueryCloseScript'])
self.appendMenu(['CadQuery', 'Examples'], submenu)
- self.appendMenu('CadQuery', ['Separator', 'CadQueryExecuteScript', 'CadQueryClearOutput'])
+ self.appendMenu('CadQuery', ['Separator', 'CadQueryExecuteScript', 'CadQueryValidateScript', 'ToggleVariablesEditor', 'CadQueryClearOutput'])
def Activated(self):
import os
@@ -111,7 +111,9 @@ FreeCADGui.addCommand('CadQueryOpenScript', CadQueryOpenScript())
FreeCADGui.addCommand('CadQuerySaveScript', CadQuerySaveScript())
FreeCADGui.addCommand('CadQuerySaveAsScript', CadQuerySaveAsScript())
FreeCADGui.addCommand('CadQueryExecuteScript', CadQueryExecuteScript())
+FreeCADGui.addCommand('CadQueryValidateScript', CadQueryValidateScript())
FreeCADGui.addCommand('CadQueryCloseScript', CadQueryCloseScript())
+FreeCADGui.addCommand('ToggleVariablesEditor', ToggleVariablesEditor())
FreeCADGui.addCommand('CadQueryClearOutput', CadQueryClearOutput())
# Step through and add an Examples submenu item for each example
diff --git a/Libs/cadquery-lib/cadquery/cqgi.py b/Libs/cadquery-lib/cadquery/cqgi.py
index 329074b..8fa7b0c 100644
--- a/Libs/cadquery-lib/cadquery/cqgi.py
+++ b/Libs/cadquery-lib/cadquery/cqgi.py
@@ -48,7 +48,7 @@ class CQModel(object):
# TODO: pick up other scirpt metadata:
# describe
# pick up validation methods
- self._find_descriptions()
+ self._find_descriptions()
def _find_vars(self):
"""
From 896bcca7c97b33709096e52d8d13681a3fa05966 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sat, 29 Jul 2017 08:20:53 -0400
Subject: [PATCH 03/19] Worked through script validation and started to
implement the logic to populate the parameters editor.
---
Gui/Command.py | 47 +++++++++++++++++++++++++----------------------
InitGui.py | 2 +-
Shared.py | 34 ++++++++++++++++++++++++++++++++--
3 files changed, 58 insertions(+), 25 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index e0db8a1..338ddb6 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -127,8 +127,9 @@ class CadQueryExecuteScript:
# Clear the old render before re-rendering
Shared.clearActiveDocument()
- # Check to see if we are executig a CQGI compliant script
scriptText = cqCodePane.toPlainText().encode('utf-8')
+
+ # Check to see if we are executig a CQGI compliant script
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")
@@ -138,11 +139,7 @@ class CadQueryExecuteScript:
# 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()
+ build_result = cqModel.build()
# Make sure that the build was successful
if build_result.success:
@@ -316,11 +313,11 @@ class CadQuerySaveAsScript:
CadQueryExecuteScript().Activated()
-class ToggleVariablesEditor:
+class ToggleParametersEditor:
"""If the user is running a CQGI-compliant script, they can edit variables through this edistor"""
def GetResources(self):
- return {"MenuText": "Toggle Variables Editor",
+ return {"MenuText": "Toggle Parameters Editor",
"Accel": "Shift+Alt+E",
"ToolTip": "Opens a live variables editor editor",
"Pixmap": ":/icons/edit-edit.svg"}
@@ -365,21 +362,27 @@ class CadQueryValidateScript:
return True
def Activated(self):
- mw = FreeCADGui.getMainWindow()
+ # Grab our code editor so we can interact with it
+ cqCodePane = Shared.getActiveCodePane()
- # Tracks whether or not we have already added the variables editor
- isPresent = False
+ # If there is no script to check, ignore this command
+ if cqCodePane is None:
+ FreeCAD.Console.PrintMessage("There is no script to validate.")
+ return
- # If the widget is open, we need to close it
- dockWidgets = mw.findChildren(QtGui.QDockWidget)
- for widget in dockWidgets:
- if widget.objectName() == "cqVarsEditor":
- # TODO: Clear and then populate the controls in the widget based on the variables
+ # Clear the old render before re-rendering
+ Shared.clearActiveDocument()
- # Toggle the visibility of the widget
- # if widget.visibleRegion().isEmpty():
- # widget.setVisible(True)
- # else:
- # widget.setVisible(False)
+ scriptText = cqCodePane.toPlainText().encode('utf-8')
- isPresent = True
\ No newline at end of file
+ if "build_object(" not in scriptText or "# build_object(" in scriptText or "#build_boject(" in scriptText:
+ FreeCAD.Console.PrintError("Script did not call build_object, no output available. Script must be CQGI compliant to get build output, variable editing and validation.\r\n")
+ return
+
+ # 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
+
+ Shared.populateParameterEditor(parameters)
\ No newline at end of file
diff --git a/InitGui.py b/InitGui.py
index 14c0616..705843d 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -113,7 +113,7 @@ FreeCADGui.addCommand('CadQuerySaveAsScript', CadQuerySaveAsScript())
FreeCADGui.addCommand('CadQueryExecuteScript', CadQueryExecuteScript())
FreeCADGui.addCommand('CadQueryValidateScript', CadQueryValidateScript())
FreeCADGui.addCommand('CadQueryCloseScript', CadQueryCloseScript())
-FreeCADGui.addCommand('ToggleVariablesEditor', ToggleVariablesEditor())
+FreeCADGui.addCommand('ToggleVariablesEditor', ToggleParametersEditor())
FreeCADGui.addCommand('CadQueryClearOutput', CadQueryClearOutput())
# Step through and add an Examples submenu item for each example
diff --git a/Shared.py b/Shared.py
index 24b5ca9..7354c54 100644
--- a/Shared.py
+++ b/Shared.py
@@ -9,7 +9,10 @@ def clearActiveDocument():
# Grab our code editor so we can interact with it
mw = FreeCADGui.getMainWindow()
mdi = mw.findChild(QtGui.QMdiArea)
- winName = mdi.currentSubWindow().windowTitle().split(" ")[0].split('.')[0]
+ currentWin = mdi.currentSubWindow()
+ if currentWin == None:
+ return
+ winName = currentWin.windowTitle().split(" ")[0].split('.')[0]
try:
doc = FreeCAD.getDocument(winName)
@@ -84,4 +87,31 @@ def setActiveWindowTitle(title):
mdiWin.setWindowTitle(title)
cqCodePane = mdiWin.findChild(QtGui.QPlainTextEdit)
- cqCodePane.setObjectName("cqCodePane_" + title.split('.')[0])
\ No newline at end of file
+ cqCodePane.setObjectName("cqCodePane_" + title.split('.')[0])
+
+
+def populateParameterEditor(parameters):
+ """Puts the proper controls in the script variable editor pane based on the parameters found"""
+
+ 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")
+
+ mw = FreeCADGui.getMainWindow()
+
+ # Tracks whether or not we have already added the variables editor
+ isPresent = False
+
+ # If the widget is open, we need to close it
+ dockWidgets = mw.findChildren(QtGui.QDockWidget)
+ for widget in dockWidgets:
+ if widget.objectName() == "cqVarsEditor":
+ # TODO: Clear and then populate the controls in the widget based on the variables
+
+ # Toggle the visibility of the widget
+ # if widget.visibleRegion().isEmpty():
+ # widget.setVisible(True)
+ # else:
+ # widget.setVisible(False)
+
+ isPresent = True
\ No newline at end of file
From 81d1f4e8508616739c5f67ceafb0cc2669eafe0d Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sun, 13 Aug 2017 08:46:29 -0400
Subject: [PATCH 04/19] Got the variables editor to populate with controls for
each of the variables in the script.
---
Gui/Command.py | 6 +++++-
Shared.py | 26 +++++++++++++++++++++++---
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index 338ddb6..a81699a 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -346,6 +346,10 @@ class ToggleParametersEditor:
if not isPresent:
cqVariablesEditor = QtGui.QDockWidget("CadQuery Variables Editor")
cqVariablesEditor.setObjectName("cqVarsEditor")
+ # cqVariablesEditor.setAutoFillBackground(True)
+ # p = cqVariablesEditor.palette()
+ # p.setColor(cqVariablesEditor.backgroundRole(), '#FF0000')
+ # cqVariablesEditor.setPalette(p)
mw.addDockWidget(QtCore.Qt.LeftDockWidgetArea, cqVariablesEditor)
@@ -378,7 +382,7 @@ class CadQueryValidateScript:
if "build_object(" not in scriptText or "# build_object(" in scriptText or "#build_boject(" in scriptText:
FreeCAD.Console.PrintError("Script did not call build_object, no output available. Script must be CQGI compliant to get build output, variable editing and validation.\r\n")
return
-
+
# A repreentation of the CQ script with all the metadata attached
cqModel = cqgi.parse(scriptText)
diff --git a/Shared.py b/Shared.py
index 7354c54..a6ff2e7 100644
--- a/Shared.py
+++ b/Shared.py
@@ -94,19 +94,39 @@ def populateParameterEditor(parameters):
"""Puts the proper controls in the script variable editor pane based on the parameters found"""
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")
mw = FreeCADGui.getMainWindow()
# Tracks whether or not we have already added the variables editor
isPresent = False
+ # TODO: Clear and then populate the controls in the widget based on the variables
+ # https://stackoverflow.com/questions/3940409/how-to-clear-all-the-widgets-in-parent-widgets
+
# If the widget is open, we need to close it
dockWidgets = mw.findChildren(QtGui.QDockWidget)
for widget in dockWidgets:
if widget.objectName() == "cqVarsEditor":
- # TODO: Clear and then populate the controls in the widget based on the variables
+ gridLayout = QtGui.QGridLayout()
+
+ line = 1
+ for pKey, pVal in parameters.iteritems():
+ FreeCAD.Console.PrintMessage("variable name: " + pKey + ", variable value: " + str(pVal.default_value) + "\r\n")
+ label = QtGui.QLabel(pKey)
+ value = QtGui.QLineEdit()
+ value.setText(str(pVal.default_value))
+ btn = QtGui.QPushButton("OK")
+ gridLayout.addWidget(label, line, 0)
+ gridLayout.addWidget(value, line, 1)
+ gridLayout.addWidget(btn, line, 2)
+
+ line += 1
+
+ # Create a widget we can put the layout in
+ newWidget = QtGui.QWidget()
+ newWidget.setLayout(gridLayout)
+
+ widget.setWidget(newWidget)
# Toggle the visibility of the widget
# if widget.visibleRegion().isEmpty():
From 84c0a9b24911989ce6d3408759e0554b971094d6 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Thu, 17 Aug 2017 11:20:50 -0400
Subject: [PATCH 05/19] Added a scroll bar to the variables editor and made
sure that the widget was being replaced each time validation is complete.
---
Shared.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Shared.py b/Shared.py
index a6ff2e7..ae490bc 100644
--- a/Shared.py
+++ b/Shared.py
@@ -100,9 +100,6 @@ def populateParameterEditor(parameters):
# Tracks whether or not we have already added the variables editor
isPresent = False
- # TODO: Clear and then populate the controls in the widget based on the variables
- # https://stackoverflow.com/questions/3940409/how-to-clear-all-the-widgets-in-parent-widgets
-
# If the widget is open, we need to close it
dockWidgets = mw.findChildren(QtGui.QDockWidget)
for widget in dockWidgets:
@@ -122,11 +119,15 @@ def populateParameterEditor(parameters):
line += 1
- # Create a widget we can put the layout in
+ # Create a widget we can put the layout in and add a scrollbar
newWidget = QtGui.QWidget()
newWidget.setLayout(gridLayout)
+ scrollArea = QtGui.QScrollArea()
+ scrollArea.setBackgroundRole(QtGui.QPalette.Light)
+ scrollArea.setStyleSheet("QLabel { color : black; }");
+ scrollArea.setWidget(newWidget)
- widget.setWidget(newWidget)
+ widget.setWidget(scrollArea)
# Toggle the visibility of the widget
# if widget.visibleRegion().isEmpty():
From 68907ba9d7c9477e9a10f4349000ad2efd2b349b Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sat, 19 Aug 2017 17:22:20 -0400
Subject: [PATCH 06/19] Got the parameters editor working so that it can modify
the model, but it still needs a lot of clean-up.
---
Gui/Command.py | 36 +++++++++++++++++++++++++++++++-----
Shared.py | 5 +++--
2 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index a81699a..868a298 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -138,8 +138,34 @@ class CadQueryExecuteScript:
# Allows us to present parameters to users later that they can alter
parameters = cqModel.metadata.parameters
+ build_parameters = {}
- build_result = cqModel.build()
+ # Collect the build parameters from the Parameters Editor view, if they exist
+ mw = FreeCADGui.getMainWindow()
+
+ # Tracks whether or not we have already added the variables editor
+ isPresent = False
+
+ # If the widget is open, we need to close it
+ dockWidgets = mw.findChildren(QtGui.QDockWidget)
+ for widget in dockWidgets:
+ if widget.objectName() == "cqVarsEditor":
+ # Toggle the visibility of the widget
+ if not widget.visibleRegion().isEmpty():
+ # build_parameters = {'param': 2}
+
+ # Find all of the controls that will have parameter values in them
+ valueControls = mw.findChildren(QtGui.QLineEdit)
+ for valueControl in valueControls:
+ objectName = valueControl.objectName()
+ FreeCAD.Console.PrintMessage(objectName + "\r\n")
+ if objectName != None and objectName != '' and objectName[0] == 'p':
+ FreeCAD.Console.PrintMessage(objectName.split('_')[1] + "\r\n")
+ build_parameters[objectName.split('_')[1]] = valueControl.text()
+
+ FreeCAD.Console.PrintMessage(build_parameters[objectName.split('_')[1]] + "\r\n")
+
+ build_result = cqModel.build(build_parameters=build_parameters)
# Make sure that the build was successful
if build_result.success:
@@ -346,12 +372,12 @@ class ToggleParametersEditor:
if not isPresent:
cqVariablesEditor = QtGui.QDockWidget("CadQuery Variables Editor")
cqVariablesEditor.setObjectName("cqVarsEditor")
- # cqVariablesEditor.setAutoFillBackground(True)
- # p = cqVariablesEditor.palette()
- # p.setColor(cqVariablesEditor.backgroundRole(), '#FF0000')
- # cqVariablesEditor.setPalette(p)
+
mw.addDockWidget(QtCore.Qt.LeftDockWidgetArea, cqVariablesEditor)
+ # Go ahead and populate the view if there are variables in the script
+ CadQueryValidateScript().Activated()
+
class CadQueryValidateScript:
"""Checks the script for the user without executing it and populates the variable editor, if needed"""
diff --git a/Shared.py b/Shared.py
index ae490bc..9726cca 100644
--- a/Shared.py
+++ b/Shared.py
@@ -112,10 +112,11 @@ def populateParameterEditor(parameters):
label = QtGui.QLabel(pKey)
value = QtGui.QLineEdit()
value.setText(str(pVal.default_value))
- btn = QtGui.QPushButton("OK")
+ value.setObjectName("p_" + pKey)
+ # btn = QtGui.QPushButton("OK")
gridLayout.addWidget(label, line, 0)
gridLayout.addWidget(value, line, 1)
- gridLayout.addWidget(btn, line, 2)
+ # gridLayout.addWidget(btn, line, 2)
line += 1
From 44e8533f26b89487cb6aa10b0a14e9cbc10f8032 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sat, 19 Aug 2017 23:12:14 -0400
Subject: [PATCH 07/19] Cleaned up the CQGI execution code and made it a little
more robust.
---
Gui/Command.py | 11 ++++-------
Shared.py | 19 +++++++++----------
2 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index 868a298..6b12fba 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -152,18 +152,15 @@ class CadQueryExecuteScript:
if widget.objectName() == "cqVarsEditor":
# Toggle the visibility of the widget
if not widget.visibleRegion().isEmpty():
- # build_parameters = {'param': 2}
-
# Find all of the controls that will have parameter values in them
valueControls = mw.findChildren(QtGui.QLineEdit)
for valueControl in valueControls:
objectName = valueControl.objectName()
- FreeCAD.Console.PrintMessage(objectName + "\r\n")
- if objectName != None and objectName != '' and objectName[0] == 'p':
- FreeCAD.Console.PrintMessage(objectName.split('_')[1] + "\r\n")
- build_parameters[objectName.split('_')[1]] = valueControl.text()
- FreeCAD.Console.PrintMessage(build_parameters[objectName.split('_')[1]] + "\r\n")
+ # We only want text fields that will have parameter values in them
+ if objectName != None and objectName != '' and objectName.find('pcontrol_') >= 0:
+ # Associate the value in the text field with the variable name in the script
+ build_parameters[objectName.replace('pcontrol_', '')] = valueControl.text()
build_result = cqModel.build(build_parameters=build_parameters)
diff --git a/Shared.py b/Shared.py
index 9726cca..ed271ea 100644
--- a/Shared.py
+++ b/Shared.py
@@ -107,22 +107,27 @@ def populateParameterEditor(parameters):
gridLayout = QtGui.QGridLayout()
line = 1
+
+ # Add controls for all the parameters so that they can be edited from the GUI
for pKey, pVal in parameters.iteritems():
- FreeCAD.Console.PrintMessage("variable name: " + pKey + ", variable value: " + str(pVal.default_value) + "\r\n")
label = QtGui.QLabel(pKey)
+
+ # We want to keep track of this parameter value field so that we can pull its value later when executing
value = QtGui.QLineEdit()
value.setText(str(pVal.default_value))
- value.setObjectName("p_" + pKey)
- # btn = QtGui.QPushButton("OK")
+ value.setObjectName("pcontrol_" + pKey)
+
+ # Add the parameter control sets, one set per line
gridLayout.addWidget(label, line, 0)
gridLayout.addWidget(value, line, 1)
- # gridLayout.addWidget(btn, line, 2)
line += 1
# Create a widget we can put the layout in and add a scrollbar
newWidget = QtGui.QWidget()
newWidget.setLayout(gridLayout)
+
+ # Add a scroll bar in case there are a lot of variables in the script
scrollArea = QtGui.QScrollArea()
scrollArea.setBackgroundRole(QtGui.QPalette.Light)
scrollArea.setStyleSheet("QLabel { color : black; }");
@@ -130,10 +135,4 @@ def populateParameterEditor(parameters):
widget.setWidget(scrollArea)
- # Toggle the visibility of the widget
- # if widget.visibleRegion().isEmpty():
- # widget.setVisible(True)
- # else:
- # widget.setVisible(False)
-
isPresent = True
\ No newline at end of file
From 9cb1e58f3f0c2cdf03c498f25831246460ab7cc1 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Fri, 1 Sep 2017 16:33:32 -0400
Subject: [PATCH 08/19] Made the variables area scrollable.
---
Shared.py | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/Shared.py b/Shared.py
index ed271ea..96fb214 100644
--- a/Shared.py
+++ b/Shared.py
@@ -97,9 +97,6 @@ def populateParameterEditor(parameters):
mw = FreeCADGui.getMainWindow()
- # Tracks whether or not we have already added the variables editor
- isPresent = False
-
# If the widget is open, we need to close it
dockWidgets = mw.findChildren(QtGui.QDockWidget)
for widget in dockWidgets:
@@ -133,6 +130,4 @@ def populateParameterEditor(parameters):
scrollArea.setStyleSheet("QLabel { color : black; }");
scrollArea.setWidget(newWidget)
- widget.setWidget(scrollArea)
-
- isPresent = True
\ No newline at end of file
+ widget.setWidget(scrollArea)
\ No newline at end of file
From 9689664aa189a09001d76034b077e97fcad396f0 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sat, 2 Sep 2017 00:03:00 -0400
Subject: [PATCH 09/19] Removed deprecated UTF8 QTApplication references.
---
Gui/Command.py | 3 +--
Gui/ExportCQ.py | 3 +--
Gui/ImportCQ.py | 6 ++----
InitGui.py | 3 +--
4 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index 6b12fba..959432a 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -187,8 +187,7 @@ class CadQueryExecuteScript:
msg = QtGui.QApplication.translate(
"cqCodeWidget",
"Executed ",
- None,
- QtGui.QApplication.UnicodeUTF8)
+ None)
FreeCAD.Console.PrintMessage(msg + cqCodePane.file.path + "\r\n")
diff --git a/Gui/ExportCQ.py b/Gui/ExportCQ.py
index cba98f8..ca73d16 100644
--- a/Gui/ExportCQ.py
+++ b/Gui/ExportCQ.py
@@ -26,6 +26,5 @@ def save(filename=None):
msg = QtGui.QApplication.translate(
"cqCodeWidget",
"Saved ",
- None,
- QtGui.QApplication.UnicodeUTF8)
+ None)
FreeCAD.Console.PrintMessage(msg + cqCodePane.file.path + "\r\n")
diff --git a/Gui/ImportCQ.py b/Gui/ImportCQ.py
index 00625c9..10a53d4 100644
--- a/Gui/ImportCQ.py
+++ b/Gui/ImportCQ.py
@@ -36,8 +36,7 @@ def open(filename):
msg = QtGui.QApplication.translate(
"cqCodeWidget",
"Please install Python 2.7",
- None,
- QtGui.QApplication.UnicodeUTF8)
+ None)
FreeCAD.Console.PrintError(msg + "\r\n")
# The extra version numbers won't work on Windows
@@ -98,8 +97,7 @@ def open(filename):
msg = QtGui.QApplication.translate(
"cqCodeWidget",
"Opened ",
- None,
- QtGui.QApplication.UnicodeUTF8)
+ None)
FreeCAD.Console.PrintMessage(msg + filename + "\r\n")
return
diff --git a/InitGui.py b/InitGui.py
index 705843d..25425b9 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -57,8 +57,7 @@ class CadQueryWorkbench (Workbench):
"Author: David Cowden\r\n"
"License: Apache-2.0\r\n"
"Website: https://github.com/dcowden/cadquery\r\n",
- None,
- QtGui.QApplication.UnicodeUTF8)
+ None)
FreeCAD.Console.PrintMessage(msg)
#Getting the main window will allow us to start setting things up the way we want
From 2cdd504888b9ac0925a8d5c2dcb9e813d494ae5b Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Sun, 3 Sep 2017 18:01:39 -0400
Subject: [PATCH 10/19] Added options handling to build_object function.
---
Gui/Command.py | 8 +-
Libs/cadquery-lib/.coverage | 1 -
Libs/cadquery-lib/.gitignore | 7 -
Libs/cadquery-lib/.travis.yml | 32 ----
Libs/cadquery-lib/README.md | 8 +-
Libs/cadquery-lib/cadquery.egg-info/PKG-INFO | 154 ++++++++++++++++++
.../cadquery.egg-info/SOURCES.txt | 29 ++++
.../cadquery.egg-info/dependency_links.txt | 1 +
.../cadquery.egg-info/not-zip-safe | 1 +
.../cadquery.egg-info/top_level.txt | 2 +
Libs/cadquery-lib/cadquery/cqgi.py | 40 +++--
Libs/cadquery-lib/doc/cqgi.rst | 5 +-
Libs/cadquery-lib/tests/TestCQGI.py | 25 +--
13 files changed, 239 insertions(+), 74 deletions(-)
delete mode 100644 Libs/cadquery-lib/.coverage
delete mode 100644 Libs/cadquery-lib/.gitignore
delete mode 100644 Libs/cadquery-lib/.travis.yml
create mode 100644 Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
create mode 100644 Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
create mode 100644 Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
create mode 100644 Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
create mode 100644 Libs/cadquery-lib/cadquery.egg-info/top_level.txt
diff --git a/Gui/Command.py b/Gui/Command.py
index 959432a..329496b 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -168,9 +168,13 @@ class CadQueryExecuteScript:
if build_result.success:
# Display all the results that the user requested
for result in build_result.results:
- show(result)
+ # Apply options to the show function if any were provided
+ if result.options and result.options["rgba"]:
+ show(result.shape, result.options["rgba"])
+ else:
+ show(result.shape)
else:
- FreeCAD.Console.PrintError("Error executing CQGI-compliant script.\r\n")
+ FreeCAD.Console.PrintError("Error executing CQGI-compliant script. " + str(build_result.exception) + "\r\n")
else:
# Save our code to a tempfile and render it
tempFile = tempfile.NamedTemporaryFile(delete=False)
diff --git a/Libs/cadquery-lib/.coverage b/Libs/cadquery-lib/.coverage
deleted file mode 100644
index 349220a..0000000
--- a/Libs/cadquery-lib/.coverage
+++ /dev/null
@@ -1 +0,0 @@
-!coverage.py: This is a private format, don't read it directly!{"lines": {"/home/jwright/Downloads/cadquery/cadquery/cq_directive.py": [], "/home/jwright/Downloads/cadquery/cadquery/cq.py": [2049, 2052, 2053, 2054, 2056, 2057, 2058, 2060, 18, 20, 21, 22, 23, 24, 27, 2076, 2078, 33, 34, 35, 36, 2085, 2086, 39, 40, 2089, 43, 2093, 49, 51, 2102, 2103, 2104, 58, 59, 60, 2109, 62, 2111, 65, 2126, 2127, 2128, 81, 2130, 2131, 84, 2133, 2134, 2140, 2141, 2142, 2143, 96, 2145, 2147, 2149, 102, 2151, 104, 106, 107, 108, 110, 112, 2166, 2168, 2170, 2171, 2172, 2173, 2174, 2178, 363, 2180, 133, 2182, 2183, 136, 2185, 138, 2187, 140, 141, 2255, 143, 145, 149, 150, 151, 153, 154, 156, 2207, 368, 2211, 2213, 2215, 2217, 2218, 2220, 174, 175, 176, 178, 182, 184, 185, 2235, 188, 189, 190, 2239, 192, 193, 195, 374, 2246, 2247, 2249, 2251, 2252, 205, 2254, 207, 2257, 211, 213, 222, 224, 2427, 2276, 2277, 2279, 2282, 2087, 238, 239, 240, 2088, 243, 244, 246, 383, 253, 255, 2307, 2308, 2309, 2310, 2433, 2312, 265, 2314, 2316, 2318, 2333, 2336, 2339, 2340, 2341, 2342, 2344, 2346, 306, 2075, 308, 309, 310, 311, 2361, 314, 2363, 2364, 2366, 2368, 322, 329, 330, 332, 333, 335, 337, 341, 344, 345, 349, 350, 353, 355, 356, 357, 360, 361, 2411, 364, 365, 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2422, 375, 2424, 378, 379, 2428, 382, 2431, 384, 385, 2434, 388, 390, 396, 398, 404, 406, 411, 413, 2471, 2472, 2474, 427, 428, 432, 2482, 2483, 2485, 2486, 2488, 2489, 2491, 2494, 2497, 2498, 2500, 454, 455, 456, 457, 458, 461, 462, 464, 466, 78, 79, 2524, 479, 80, 482, 483, 485, 486, 488, 490, 1447, 82, 2132, 519, 521, 2480, 551, 553, 582, 97, 584, 605, 607, 103, 839, 631, 633, 651, 653, 669, 671, 2502, 681, 683, 692, 694, 720, 722, 723, 724, 725, 727, 729, 2070, 742, 743, 745, 758, 2071, 766, 769, 131, 2072, 2523, 805, 807, 808, 811, 812, 813, 815, 137, 2527, 836, 838, 481, 842, 843, 844, 846, 369, 874, 876, 877, 880, 882, 883, 886, 910, 912, 914, 936, 937, 938, 939, 943, 947, 948, 949, 951, 952, 953, 955, 968, 969, 971, 972, 974, 975, 976, 977, 979, 981, 994, 995, 996, 997, 998, 999, 1001, 1022, 1024, 1025, 1026, 1027, 1031, 1032, 172, 1034, 1036, 1050, 1053, 1054, 1055, 1056, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1067, 1069, 1089, 1090, 1091, 1092, 1094, 1096, 1123, 1124, 1125, 1127, 2236, 2237, 1137, 1139, 1141, 1143, 1144, 1146, 1149, 2241, 1160, 1161, 1163, 1170, 1172, 1179, 1181, 1191, 1192, 1194, 1204, 1205, 1208, 2359, 1223, 1224, 1227, 2253, 1242, 1243, 1244, 1246, 2259, 1278, 1279, 1281, 1282, 1284, 1286, 1287, 1289, 1291, 1307, 1308, 1309, 1311, 1313, 1314, 1316, 1318, 1338, 1341, 1343, 1345, 1346, 1347, 1350, 1352, 1354, 2091, 1372, 1373, 1374, 1376, 1390, 1391, 1392, 1394, 1401, 1403, 1404, 1406, 1421, 1423, 1433, 1434, 1435, 1439, 1446, 241, 1448, 1449, 1451, 1472, 1475, 1476, 1478, 1480, 1481, 1482, 1486, 1487, 1488, 1490, 1492, 1524, 1525, 1527, 1529, 1530, 1532, 1534, 1535, 1536, 1538, 1540, 1542, 1560, 1561, 1564, 1567, 1568, 1570, 1572, 263, 2313, 1598, 1601, 1602, 1603, 1604, 1605, 1607, 1608, 1609, 1610, 1612, 1613, 1616, 1619, 1648, 1649, 1650, 1651, 1653, 1655, 1666, 1668, 1669, 1670, 1671, 1672, 1673, 1675, 1677, 1692, 1695, 1698, 1699, 1701, 1704, 1706, 1707, 1709, 1711, 1725, 1728, 1730, 1732, 1741, 1742, 1743, 1745, 1747, 1757, 1758, 1762, 1763, 1764, 1765, 1767, 1769, 1770, 1773, 1803, 1804, 1806, 1812, 1814, 1817, 1818, 1819, 1821, 1825, 2107, 2356, 2357, 1856, 1857, 1859, 1862, 1865, 1866, 1867, 1868, 1869, 1870, 1872, 1876, 2362, 63, 1902, 1903, 1905, 1911, 1913, 1914, 1916, 1919, 320, 1940, 1942, 1945, 1954, 1955, 1956, 1957, 1958, 1959, 1963, 1964, 1966, 1967, 1968, 1970, 1995, 1997, 1998, 2000, 2001, 2002, 2004, 2027, 2030, 2034, 2035, 2037, 2041, 2044, 2045, 2047], "/home/jwright/Downloads/cadquery/cadquery/freecad_impl/__init__.py": [18, 19, 20, 23, 26, 27, 30, 32, 33, 34, 35, 36, 37, 38, 39, 41, 42, 101, 102, 103, 104, 105, 106], "/home/jwright/Downloads/cadquery/cadquery/freecad_impl/shapes.py": [1026, 1027, 1028, 1030, 521, 599, 524, 930, 526, 88, 530, 259, 533, 535, 810, 461, 802, 602, 1033, 544, 546, 603, 518, 554, 556, 557, 559, 49, 50, 51, 52, 986, 55, 568, 104, 571, 572, 61, 574, 575, 576, 578, 579, 68, 69, 71, 72, 1036, 74, 587, 866, 591, 81, 594, 595, 84, 85, 87, 355, 89, 90, 91, 93, 94, 607, 96, 609, 610, 611, 612, 808, 616, 105, 110, 873, 624, 113, 626, 116, 446, 630, 631, 632, 633, 634, 703, 636, 637, 645, 134, 961, 649, 652, 937, 660, 663, 664, 668, 601, 160, 673, 678, 625, 681, 682, 172, 173, 686, 175, 689, 178, 691, 181, 184, 628, 187, 188, 189, 702, 191, 971, 708, 710, 711, 493, 204, 717, 206, 719, 720, 209, 210, 211, 212, 213, 726, 728, 729, 219, 220, 221, 223, 224, 208, 738, 114, 234, 809, 750, 751, 830, 241, 242, 244, 245, 246, 297, 248, 761, 250, 322, 256, 257, 770, 771, 97, 261, 262, 811, 776, 812, 778, 983, 281, 815, 284, 285, 803, 287, 288, 290, 291, 804, 293, 294, 817, 296, 988, 95, 299, 300, 818, 302, 303, 816, 305, 306, 308, 821, 822, 311, 312, 825, 314, 827, 828, 906, 868, 831, 832, 834, 323, 325, 326, 328, 329, 330, 332, 334, 335, 336, 337, 338, 340, 569, 345, 348, 826, 864, 506, 354, 59, 356, 357, 358, 871, 360, 762, 805, 373, 374, 375, 377, 378, 381, 865, 384, 386, 899, 406, 390, 391, 392, 393, 394, 907, 397, 910, 399, 400, 913, 402, 915, 917, 918, 409, 98, 412, 414, 929, 418, 422, 423, 424, 425, 939, 940, 429, 942, 431, 432, 433, 434, 949, 438, 951, 901, 953, 954, 671, 956, 492, 958, 447, 960, 449, 963, 904, 970, 759, 460, 973, 462, 589, 464, 981, 470, 471, 984, 473, 474, 79, 476, 477, 478, 479, 480, 482, 934, 1000, 1001, 490, 935, 1004, 82, 494, 1007, 496, 1009, 83, 1013, 869, 1016, 1018, 507, 508, 1021, 510, 597], "/home/jwright/Downloads/cadquery/cadquery/__init__.py": [2, 3, 4, 5, 10, 11, 15, 16, 17, 18, 21], "/home/jwright/Downloads/cadquery/cadquery/selectors.py": [51, 514, 515, 516, 518, 519, 521, 522, 523, 525, 526, 319, 528, 18, 531, 20, 21, 22, 23, 24, 538, 29, 542, 543, 544, 34, 35, 548, 553, 555, 45, 47, 48, 50, 563, 564, 53, 54, 567, 56, 57, 570, 59, 573, 574, 575, 577, 578, 579, 581, 582, 583, 585, 586, 75, 76, 77, 78, 591, 592, 593, 594, 596, 598, 87, 600, 89, 102, 103, 104, 105, 106, 108, 530, 110, 111, 112, 114, 117, 118, 119, 532, 122, 123, 124, 125, 126, 128, 129, 131, 133, 137, 138, 139, 140, 142, 655, 656, 657, 146, 659, 663, 152, 153, 156, 158, 160, 161, 162, 164, 165, 166, 568, 168, 170, 188, 190, 191, 193, 535, 546, 211, 213, 214, 121, 216, 536, 234, 236, 237, 238, 239, 242, 261, 262, 263, 265, 266, 267, 268, 269, 270, 301, 272, 293, 294, 295, 296, 297, 298, 299, 562, 302, 305, 307, 309, 310, 649, 313, 314, 316, 650, 565, 321, 329, 330, 331, 332, 333, 334, 335, 337, 339, 654, 342, 345, 349, 350, 351, 354, 355, 358, 360, 364, 365, 366, 367, 369, 370, 371, 373, 376, 379, 380, 382, 384, 387, 388, 390, 392, 396, 397, 398, 400, 80, 404, 405, 406, 408, 410, 413, 81, 419, 420, 421, 422, 423, 426, 427, 428, 429, 430, 433, 434, 437, 438, 439, 442, 445, 448, 449, 450, 587, 452, 455, 458, 463, 464, 466, 468, 590, 472, 473, 476, 477, 478, 479, 480, 481, 482, 485, 486, 487, 488, 489, 490, 491, 494, 495, 496, 497, 498, 501, 502, 503, 504, 505, 507, 508, 510, 341], "/home/jwright/Downloads/cadquery/cadquery/cqgi.py": [4, 5, 6, 7, 8, 10, 12, 23, 24, 27, 36, 38, 43, 44, 45, 46, 51, 53, 64, 66, 67, 68, 70, 71, 72, 74, 83, 95, 96, 98, 99, 101, 102, 103, 104, 105, 106, 107, 110, 111, 112, 113, 114, 116, 117, 118, 119, 120, 121, 122, 124, 125, 126, 128, 129, 131, 132, 133, 135, 136, 139, 149, 150, 151, 152, 153, 154, 155, 156, 158, 159, 160, 162, 163, 165, 166, 167, 168, 171, 175, 176, 177, 179, 180, 182, 183, 184, 185, 188, 189, 192, 193, 196, 197, 200, 201, 204, 214, 215, 218, 221, 224, 227, 230, 232, 234, 235, 237, 238, 240, 241, 242, 243, 244, 245, 246, 247, 249, 251, 256, 257, 258, 259, 260, 261, 262, 263, 265, 266, 267, 268, 271, 275, 280, 285, 286, 287, 288, 290, 295, 297, 301, 303, 309, 315, 316, 318, 323, 324, 325, 326, 328, 332, 333, 336, 340, 341, 344, 349, 351, 362, 365, 368, 372, 377, 378, 379, 381, 382, 384, 385, 386, 388, 389, 390, 391, 393, 394, 395, 397, 398, 400, 403, 404, 405, 407, 411, 412, 416, 417, 418, 420, 421, 422, 423, 425, 428, 430, 431, 433, 434, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 453, 455, 456, 459, 462, 463, 464, 466, 467, 472], "/home/jwright/Downloads/cadquery/cadquery/freecad_impl/importers.py": [2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 20, 28, 29, 33, 39, 41, 44, 45, 46, 48, 53], "/home/jwright/Downloads/cadquery/cadquery/freecad_impl/exporters.py": [1, 3, 4, 6, 9, 10, 15, 16, 17, 18, 19, 20, 23, 24, 25, 28, 34, 46, 47, 49, 51, 53, 55, 56, 59, 60, 61, 62, 63, 64, 65, 66, 71, 74, 76, 77, 78, 79, 83, 84, 86, 91, 92, 93, 95, 96, 99, 103, 105, 107, 108, 111, 115, 116, 118, 121, 122, 124, 125, 127, 128, 130, 131, 132, 133, 136, 137, 138, 139, 140, 141, 142, 143, 144, 147, 148, 149, 150, 151, 152, 153, 154, 157, 164, 165, 167, 168, 169, 170, 172, 173, 174, 177, 179, 180, 186, 187, 188, 189, 190, 191, 195, 210, 211, 212, 214, 215, 216, 217, 218, 220, 221, 223, 225, 226, 227, 232, 237, 239, 243, 245, 246, 247, 248, 251, 252, 254, 257, 258, 259, 260, 263, 266, 269, 270, 271, 273, 274, 275, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 294, 297, 304, 305, 306, 307, 353, 389, 391], "/home/jwright/Downloads/cadquery/cadquery/freecad_impl/geom.py": [515, 516, 517, 522, 523, 524, 529, 18, 20, 21, 22, 23, 26, 540, 542, 544, 546, 547, 548, 550, 632, 42, 44, 45, 46, 47, 560, 49, 50, 563, 564, 565, 567, 568, 569, 58, 572, 573, 575, 581, 70, 583, 72, 73, 74, 75, 76, 77, 78, 79, 592, 593, 594, 595, 597, 87, 89, 91, 93, 95, 608, 97, 99, 101, 614, 103, 616, 105, 618, 107, 109, 110, 113, 114, 116, 117, 630, 119, 120, 633, 122, 123, 125, 638, 127, 128, 130, 635, 132, 645, 134, 136, 144, 146, 147, 149, 152, 155, 158, 161, 162, 164, 167, 170, 173, 174, 177, 181, 182, 183, 184, 188, 189, 191, 192, 195, 586, 631, 206, 208, 209, 463, 634, 570, 238, 637, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 254, 255, 260, 261, 262, 263, 264, 266, 267, 268, 269, 270, 272, 273, 643, 278, 279, 280, 281, 282, 284, 285, 133, 48, 290, 291, 296, 297, 302, 303, 51, 308, 309, 52, 314, 315, 590, 629, 53, 320, 321, 326, 327, 55, 332, 345, 346, 348, 349, 350, 352, 354, 356, 358, 360, 362, 364, 365, 367, 609, 389, 391, 495, 69, 418, 419, 582, 423, 424, 71, 428, 430, 584, 611, 585, 445, 446, 587, 454, 588, 461, 462, 589, 464, 466, 467, 469, 591, 483, 485, 488, 489, 490, 491, 494, 613, 497, 499], "/home/jwright/Downloads/cadquery/cadquery/plugins/__init__.py": [18], "/home/jwright/Downloads/cadquery/cadquery/contrib/__init__.py": []}}
\ No newline at end of file
diff --git a/Libs/cadquery-lib/.gitignore b/Libs/cadquery-lib/.gitignore
deleted file mode 100644
index e7df8cf..0000000
--- a/Libs/cadquery-lib/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-build/
-*.pyc
-doc/_build/*
-dist/*
-.idea/*
-cadquery.egg-info
-target/*
diff --git a/Libs/cadquery-lib/.travis.yml b/Libs/cadquery-lib/.travis.yml
deleted file mode 100644
index aa8160b..0000000
--- a/Libs/cadquery-lib/.travis.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-language: python
-before_install:
-- sudo add-apt-repository -y ppa:freecad-maintainers/freecad-stable
-- sudo apt-get update -qq
-install:
-- sudo apt-get install -y freecad freecad-doc
-- gcc --version
-- g++ --version
-- python ./setup.py install
-- pip install -r requirements-dev.txt
-- pip install travis-sphinx
-
-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:
- provider: pypi
- user: dcowden
- password:
- secure: aP02wBbry1j3hYG/w++siF1lk26teuRQlPAx1c+ec8fxUw+bECa2HbPQHcIvSXB5N6nc6P3L9LjHt9ktm+Dn6FLJu3qWYNGAZx9PTn24ug0iAmB+JyNrsET3nK6WUKR1XpBqvjKgdpukd1Hknh2FSzYoyUvFWH9/CovITCFN3jo=
- on:
- tags: true
\ No newline at end of file
diff --git a/Libs/cadquery-lib/README.md b/Libs/cadquery-lib/README.md
index 8cc0b03..d041e0f 100644
--- a/Libs/cadquery-lib/README.md
+++ b/Libs/cadquery-lib/README.md
@@ -223,7 +223,11 @@ Use these steps if you would like to write CadQuery scripts as a python API. In
```bash
pip install cadquery
```
-3. test your installation::
+4. installing cadquery should install pyparsing as well, but if not::
+```bash
+ pip install pyparsing
+```
+5. test your installation::
```python
from cadquery import *
box = Workplane("XY").box(1,2,3)
@@ -234,7 +238,7 @@ You're up and running!
Installing -- Using CadQuery from Inside FreeCAD
=================================================
-Use the Excellent CadQuery-FreeCAD plugin here:
+Use the CadQuery module for FreeCAD here:
https://github.com/jmwright/cadquery-freecad-module
It includes a distribution of the latest version of cadquery.
diff --git a/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO b/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
new file mode 100644
index 0000000..823c3bd
--- /dev/null
+++ b/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
@@ -0,0 +1,154 @@
+Metadata-Version: 1.1
+Name: cadquery
+Version: 1.0.0
+Summary: CadQuery is a parametric scripting language for creating and traversing CAD models
+Home-page: https://github.com/dcowden/cadquery
+Author: David Cowden
+Author-email: dave.cowden@gmail.com
+License: Apache Public License 2.0
+Description: What is a CadQuery?
+ ========================================
+
+ [](https://travis-ci.org/dcowden/cadquery)
+ [](https://coveralls.io/r/dcowden/cadquery)
+ [](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
+ [](https://github.com/dcowden/cadquery/blob/master/LICENSE)
+
+ CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
+
+ CadQuery has several goals:
+
+ * Build 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
+ * 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.
+
+ Full Documentation
+ ============================
+ You can find the full cadquery documentation at http://dcowden.github.io/cadquery
+
+
+ Getting Started With CadQuery
+ ========================================
+
+ The easiest way to get started with CadQuery is to Install FreeCAD (version 14+) (http://www.freecadweb.org/), and then to use our great CadQuery-FreeCAD plugin here: https://github.com/jmwright/cadquery-freecad-module
+
+
+ It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
+
+ It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
+
+ We also have a Google Group to make it easy to get help from other CadQuery users. Please join the group and introduce yourself, and we would also love to hear what you are doing with CadQuery. https://groups.google.com/forum/#!forum/cadquery
+
+
+
+ Why CadQuery instead of OpenSCAD?
+ ========================================
+
+ CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
+
+ The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
+
+ CadQuery scripts have several key advantages over OpenSCAD:
+
+ 1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
+ This includes many standard libraries and IDEs
+
+ 2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
+ by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
+ in addition to the standard CSG operations supported by CGAL
+
+ 3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
+ and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
+
+ 4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
+ features based on the position of other features, workplanes, vertices, etc.
+
+ 5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
+
+ License
+ ========
+
+ CadQuery is licensed under the terms of the Apache Public License, version 2.0.
+ A copy of the license can be found at http://www.apache.org/licenses/LICENSE-2.0
+
+ Where is the GUI?
+ ==================
+
+ If you would like IDE support, you can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
+
+ CadQuery also provides the backbone of http://parametricparts.com, so the easiest way to see it in action is to review the samples and objects there.
+
+ Installing -- FreeStanding Installation
+ ========================================
+
+ Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
+
+ 1. install FreeCAD, version 0.12 or greater for your platform. http://sourceforge.net/projects/free-cad/.
+
+ 2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
+ preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
+ lib directory to your path. On (*Nix)::
+
+ import sys
+ sys.path.append('/usr/lib/freecad/lib')
+
+ or on Windows::
+
+ import sys
+ sys.path.append('/c/apps/FreeCAD/bin')
+
+ *NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
+
+ 3. install cadquery::
+
+ pip install cadquery
+
+ 3. test your installation::
+
+ from cadquery import *
+ box = Workplane("XY").box(1,2,3)
+ exporters.toString(box,'STL')
+
+ You're up and running!
+
+ Installing -- Using CadQuery from Inside FreeCAD
+ =================================================
+
+ Use the Excellent CadQuery-FreeCAD plugin here:
+ https://github.com/jmwright/cadquery-freecad-module
+
+ It includes a distribution of the latest version of cadquery.
+
+ Where does the name CadQuery come from?
+ ========================================
+
+ CadQuery is inspired by ( `jQuery `_ ), a popular framework that
+ revolutionized web development involving javascript.
+
+ If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
+
+ * A fluent api to create clean, easy to read code
+ * Language features that make selection and iteration incredibly easy
+ *
+ * Ability to use the library along side other python libraries
+ * Clear and complete documentation, with plenty of samples.
+
+
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: Intended Audience :: Information Technology
+Classifier: Intended Audience :: Science/Research
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: Unix
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Internet
+Classifier: Topic :: Scientific/Engineering
diff --git a/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt b/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
new file mode 100644
index 0000000..0507b5e
--- /dev/null
+++ b/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
@@ -0,0 +1,29 @@
+MANIFEST.in
+README.txt
+setup.cfg
+setup.py
+cadquery/__init__.py
+cadquery/cq.py
+cadquery/cq_directive.py
+cadquery/cqgi.py
+cadquery/selectors.py
+cadquery.egg-info/PKG-INFO
+cadquery.egg-info/SOURCES.txt
+cadquery.egg-info/dependency_links.txt
+cadquery.egg-info/not-zip-safe
+cadquery.egg-info/top_level.txt
+cadquery/contrib/__init__.py
+cadquery/freecad_impl/__init__.py
+cadquery/freecad_impl/exporters.py
+cadquery/freecad_impl/geom.py
+cadquery/freecad_impl/importers.py
+cadquery/freecad_impl/shapes.py
+cadquery/plugins/__init__.py
+tests/TestCQGI.py
+tests/TestCQSelectors.py
+tests/TestCadObjects.py
+tests/TestCadQuery.py
+tests/TestExporters.py
+tests/TestImporters.py
+tests/TestWorkplanes.py
+tests/__init__.py
\ No newline at end of file
diff --git a/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt b/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe b/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/Libs/cadquery-lib/cadquery.egg-info/top_level.txt b/Libs/cadquery-lib/cadquery.egg-info/top_level.txt
new file mode 100644
index 0000000..2bbe7fc
--- /dev/null
+++ b/Libs/cadquery-lib/cadquery.egg-info/top_level.txt
@@ -0,0 +1,2 @@
+cadquery
+tests
diff --git a/Libs/cadquery-lib/cadquery/cqgi.py b/Libs/cadquery-lib/cadquery/cqgi.py
index 8fa7b0c..d0777a8 100644
--- a/Libs/cadquery-lib/cadquery/cqgi.py
+++ b/Libs/cadquery-lib/cadquery/cqgi.py
@@ -34,7 +34,6 @@ class CQModel(object):
the build method can be used to generate a 3d model
"""
-
def __init__(self, script_source):
"""
Create an object by parsing the supplied python script.
@@ -48,7 +47,7 @@ class CQModel(object):
# TODO: pick up other scirpt metadata:
# describe
# pick up validation methods
- self._find_descriptions()
+ self._find_descriptions()
def _find_vars(self):
"""
@@ -136,6 +135,14 @@ class CQModel(object):
p.set_value(v)
+class ShapeResult(object):
+ """
+ An object created by a build, including the user parameters provided
+ """
+ def __init__(self):
+ self.shape = None
+ self.options = None
+
class BuildResult(object):
"""
The result of executing a CadQuery script.
@@ -149,8 +156,8 @@ class BuildResult(object):
"""
def __init__(self):
self.buildTime = None
- self.results = []
- self.debugObjects = []
+ self.results = [] #list of ShapeResult
+ self.debugObjects = [] #list of ShapeResult
self.first_result = None
self.success = False
self.exception = None
@@ -287,18 +294,25 @@ class ScriptCallback(object):
self.outputObjects = []
self.debugObjects = []
- def build_object(self, shape):
+ def build_object(self, shape,options={}):
"""
- return an object to the executing environment
+ return an object to the executing environment, with options
:param shape: a cadquery object
+ :param options: a dictionary of options that will be made available to the executing envrionment
"""
- self.outputObjects.append(shape)
+ o = ShapeResult()
+ o.options=options
+ o.shape = shape
+ self.outputObjects.append(o)
def debug(self,obj,args={}):
"""
Debug print/output an object, with optional arguments.
"""
- self.debugObjects.append(DebugObject(obj,args))
+ s = ShapeResult()
+ s.shape = obj
+ s.options = args
+ self.debugObjects.append(s)
def describe_parameter(self,var_data ):
"""
@@ -315,15 +329,7 @@ class ScriptCallback(object):
def has_results(self):
return len(self.outputObjects) > 0
-class DebugObject(object):
- """
- Represents a request to debug an object
- Object is the type of object we want to debug
- args are parameters for use during debuging ( for example, color, tranparency )
- """
- def __init__(self,object,args):
- self.args = args
- self.object = object
+
class InvalidParameterError(Exception):
"""
diff --git a/Libs/cadquery-lib/doc/cqgi.rst b/Libs/cadquery-lib/doc/cqgi.rst
index 310f7d2..84a3602 100644
--- a/Libs/cadquery-lib/doc/cqgi.rst
+++ b/Libs/cadquery-lib/doc/cqgi.rst
@@ -37,6 +37,9 @@ CQGI compliant containers provide an execution environment for scripts. The envi
Scripts must call build_output at least once. Invoking build_object more than once will send multiple objects to
the container. An error will occur if the script does not return an object using the build_object() method.
+An optional options dictionary can be provided to the build_object method. If provided, it is passed onto the executing environment, and is used to render the object. Typically, this will be colors, transparency, and other visual affects.
+
+
This CQGI compliant script produces a cube with a circle on top, and displays a workplane as well as an intermediate circle as debug output::
base_cube = cq.Workplane('XY').rect(1.0,1.0).extrude(1.0)
@@ -47,7 +50,7 @@ This CQGI compliant script produces a cube with a circle on top, and displays a
circle=top_of_cube_plane.circle(0.5)
debug(circle, { 'color': 'red' } )
- build_object( circle.extrude(1.0) )
+ build_object( circle.extrude(1.0),{"color": "#aaaaaa" )
Note that importing cadquery is not required.
At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle
diff --git a/Libs/cadquery-lib/tests/TestCQGI.py b/Libs/cadquery-lib/tests/TestCQGI.py
index 7cc967f..08c63dc 100644
--- a/Libs/cadquery-lib/tests/TestCQGI.py
+++ b/Libs/cadquery-lib/tests/TestCQGI.py
@@ -48,10 +48,11 @@ class TestCQGI(BaseTest):
result = model.build()
debugItems = result.debugObjects
self.assertTrue(len(debugItems) == 2)
- self.assertTrue( debugItems[0].object == "bar" )
- self.assertTrue( debugItems[0].args == { "color":'yellow' } )
- self.assertTrue( debugItems[1].object == 2.0 )
- self.assertTrue( debugItems[1].args == {} )
+
+ self.assertTrue( debugItems[0].shape == "bar" )
+ self.assertTrue( debugItems[0].options == { "color":'yellow' } )
+ self.assertTrue( debugItems[1].shape == 2.0 )
+ self.assertTrue( debugItems[1].options == {} )
def test_build_with_empty_params(self):
model = cqgi.CQModel(TESTSCRIPT)
@@ -59,12 +60,12 @@ class TestCQGI(BaseTest):
self.assertTrue(result.success)
self.assertTrue(len(result.results) == 1)
- self.assertTrue(result.results[0] == "2.0|3.0|bar|1.0")
+ self.assertTrue(result.results[0].shape == "2.0|3.0|bar|1.0")
def test_build_with_different_params(self):
model = cqgi.CQModel(TESTSCRIPT)
result = model.build({'height': 3.0})
- self.assertTrue(result.results[0] == "3.0|3.0|bar|1.0")
+ self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0")
def test_describe_parameters(self):
script = textwrap.dedent(
@@ -128,8 +129,8 @@ class TestCQGI(BaseTest):
model = cqgi.CQModel(script)
result = model.build({})
self.assertEquals(2, len(result.results))
- self.assertEquals(1, result.results[0])
- self.assertEquals(2, result.results[1])
+ self.assertEquals(1, result.results[0].shape)
+ self.assertEquals(2, result.results[1].shape)
def test_that_assinging_number_to_string_works(self):
script = textwrap.dedent(
@@ -139,7 +140,7 @@ class TestCQGI(BaseTest):
"""
)
result = cqgi.parse(script).build( {'h': 33.33})
- self.assertEquals(result.results[0], "33.33")
+ self.assertEquals(result.results[0].shape, "33.33")
def test_that_assigning_string_to_number_fails(self):
script = textwrap.dedent(
@@ -181,7 +182,7 @@ class TestCQGI(BaseTest):
result = cqgi.parse(script).build()
self.assertTrue(result.success)
- self.assertIsNotNone(result.first_result)
+ self.assertIsNotNone(result.first_result.shape)
def test_setting_boolean_variable(self):
script = textwrap.dedent(
@@ -195,7 +196,7 @@ class TestCQGI(BaseTest):
result = cqgi.parse(script).build({'h': False})
self.assertTrue(result.success)
- self.assertEquals(result.first_result,'*False*')
+ self.assertEquals(result.first_result.shape,'*False*')
def test_that_only_top_level_vars_are_detected(self):
script = textwrap.dedent(
@@ -213,4 +214,4 @@ class TestCQGI(BaseTest):
model = cqgi.parse(script)
- self.assertEquals(2, len(model.metadata.parameters))
\ No newline at end of file
+ self.assertEquals(2, len(model.metadata.parameters))
From 21200cc10d137cef0be50f41046d528226255b5e Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Mon, 11 Sep 2017 21:49:14 -0400
Subject: [PATCH 11/19] Added CQGI debug objects.
---
Gui/Command.py | 13 ++++++-
Libs/cadquery-lib/cadquery/cq.py | 46 ++++++++++++++++++++++---
Libs/cadquery-lib/cadquery/cqgi.py | 9 +++--
Libs/cadquery-lib/tests/TestCadQuery.py | 27 ++++++++++-----
4 files changed, 77 insertions(+), 18 deletions(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index 329496b..bb5254f 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -9,6 +9,7 @@ import ExportCQ, ImportCQ
import module_locator
import Settings
import Shared
+from random import random
from cadquery import cqgi
from Helpers import show
@@ -130,7 +131,7 @@ class CadQueryExecuteScript:
scriptText = cqCodePane.toPlainText().encode('utf-8')
# Check to see if we are executig a CQGI compliant script
- if "build_object(" in scriptText and "# build_object(" not in scriptText and "#build_boject(" not in scriptText:
+ if ("build_object(" in scriptText and "# build_object(" not in scriptText and "#build_boject(" not in scriptText) or ("debug(" in scriptText and "# debug(" not in scriptText and "#debug(" not in scriptText):
FreeCAD.Console.PrintMessage("Executing CQGI-compliant script.\r\n")
# A repreentation of the CQ script with all the metadata attached
@@ -173,6 +174,16 @@ class CadQueryExecuteScript:
show(result.shape, result.options["rgba"])
else:
show(result.shape)
+
+ for debugObj in build_result.debugObjects:
+ # Mark this as a debug object
+ debugObj.shape.val().label = "Debug" + str(random())
+
+ # Apply options to the show function if any were provided
+ if debugObj.options and debugObj.options["rgba"]:
+ show(debugObj.shape, debugObj.options["rgba"])
+ else:
+ show(debugObj.shape, (255, 0, 0, 0.80))
else:
FreeCAD.Console.PrintError("Error executing CQGI-compliant script. " + str(build_result.exception) + "\r\n")
else:
diff --git a/Libs/cadquery-lib/cadquery/cq.py b/Libs/cadquery-lib/cadquery/cq.py
index 97f135b..dc685c9 100644
--- a/Libs/cadquery-lib/cadquery/cq.py
+++ b/Libs/cadquery-lib/cadquery/cq.py
@@ -341,7 +341,7 @@ class CQ(object):
if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]):
raise ValueError("Selected faces must be co-planar.")
- if centerOption == 'CenterOfMass':
+ if centerOption == 'CenterOfMass':
center = Shape.CombinedCenter(self.objects)
elif centerOption == 'CenterOfBoundBox':
center = Shape.CombinedCenterOfBoundBox(self.objects)
@@ -1991,9 +1991,9 @@ class Workplane(CQ):
Support for non-prismatic extrusion ( IE, sweeping along a profile, not just
perpendicular to the plane extrude to surface. this is quite tricky since the surface
selected may not be planar
- """
+ """
r = self._extrude(distance,both=both) # returns a Solid (or a compound if there were multiple)
-
+
if combine:
newS = self._combineWithBase(r)
else:
@@ -2184,6 +2184,44 @@ class Workplane(CQ):
return self.newObject([newS])
+ def intersect(self, toIntersect, combine=True, clean=True):
+ """
+ Intersects the provided solid from the current solid.
+
+ if combine=True, the result and the original are updated to point to the new object
+ if combine=False, the result will be on the stack, but the original is unmodified
+
+ :param toIntersect: object to intersect
+ :type toIntersect: a solid object, or a CQ object having a solid,
+ :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
+ :raises: ValueError if there is no solid to intersect with in the chain
+ :return: a CQ object with the resulting object selected
+ """
+
+ # look for parents to intersect with
+ solidRef = self.findSolid(searchStack=True, searchParents=True)
+
+ if solidRef is None:
+ raise ValueError("Cannot find solid to intersect with")
+ solidToIntersect = None
+
+ if isinstance(toIntersect, CQ):
+ solidToIntersect = toIntersect.val()
+ elif isinstance(toIntersect, Solid):
+ solidToIntersect = toIntersect
+ else:
+ raise ValueError("Cannot intersect type '{}'".format(type(toIntersect)))
+
+ newS = solidRef.intersect(solidToIntersect)
+
+ if clean: newS = newS.clean()
+
+ if combine:
+ solidRef.wrapped = newS.wrapped
+
+ return self.newObject([newS])
+
+
def cutBlind(self, distanceToCut, clean=True):
"""
Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
@@ -2308,7 +2346,7 @@ class Workplane(CQ):
for ws in wireSets:
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
toFuse.append(thisObj)
-
+
if both:
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir.multiply(-1.))
toFuse.append(thisObj)
diff --git a/Libs/cadquery-lib/cadquery/cqgi.py b/Libs/cadquery-lib/cadquery/cqgi.py
index d0777a8..d654d72 100644
--- a/Libs/cadquery-lib/cadquery/cqgi.py
+++ b/Libs/cadquery-lib/cadquery/cqgi.py
@@ -109,10 +109,8 @@ class CQModel(object):
c = compile(self.ast_tree, CQSCRIPT, 'exec')
exec (c, env)
result.set_debug(collector.debugObjects )
- if collector.has_results():
- result.set_success_result(collector.outputObjects)
- else:
- raise NoOutputError("Script did not call build_object-- no output available.")
+ result.set_success_result(collector.outputObjects)
+
except Exception, ex:
#print "Error Executing Script:"
result.set_failure_result(ex)
@@ -171,7 +169,8 @@ class BuildResult(object):
def set_success_result(self, results):
self.results = results
- self.first_result = self.results[0]
+ if len(self.results) > 0:
+ self.first_result = self.results[0]
self.success = True
diff --git a/Libs/cadquery-lib/tests/TestCadQuery.py b/Libs/cadquery-lib/tests/TestCadQuery.py
index f12165a..12f41b7 100644
--- a/Libs/cadquery-lib/tests/TestCadQuery.py
+++ b/Libs/cadquery-lib/tests/TestCadQuery.py
@@ -604,6 +604,20 @@ class TestCadQuery(BaseTest):
self.assertEqual(10,currentS.faces().size())
+ def testIntersect(self):
+ """
+ Tests the intersect function.
+ """
+ s = Workplane(Plane.XY())
+ currentS = s.rect(2.0, 2.0).extrude(0.5)
+ toIntersect = s.rect(1.0, 1.0).extrude(1)
+
+ currentS.intersect(toIntersect.val())
+
+ self.assertEqual(6, currentS.faces().size())
+ bb = currentS.val().BoundingBox()
+ self.assertListEqual([bb.xlen, bb.ylen, bb.zlen], [1, 1, 0.5])
+
def testBoundingBox(self):
"""
Tests the boudingbox center of a model
@@ -1387,7 +1401,7 @@ class TestCadQuery(BaseTest):
result =topOfLid.union(bottom)
self.saveModel(result)
-
+
def testExtrude(self):
"""
Test symmetric extrude
@@ -1395,19 +1409,16 @@ class TestCadQuery(BaseTest):
r = 1.
h = 1.
decimal_places = 9.
-
+
#extrude symmetrically
s = Workplane("XY").circle(r).extrude(h,both=True)
-
+
top_face = s.faces(">Z")
bottom_face = s.faces("
Date: Tue, 12 Sep 2017 15:02:10 -0400
Subject: [PATCH 12/19] Added menu icons to examples.
---
Gui/Command.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Gui/Command.py b/Gui/Command.py
index bb5254f..f679ced 100644
--- a/Gui/Command.py
+++ b/Gui/Command.py
@@ -90,7 +90,8 @@ class CadQueryExecuteExample:
self.exFile = str(exFile)
def GetResources(self):
- return {"MenuText": str(self.exFile)}
+ return {"MenuText": str(self.exFile),
+ "Pixmap": ":/icons/accessories-text-editor.svg"}
def Activated(self):
FreeCAD.Console.PrintMessage(self.exFile + "\r\n")
From 24a77b727e60b48bb797ebaf8b9a16f55c08ff0b Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Wed, 13 Sep 2017 14:40:21 -0400
Subject: [PATCH 13/19] Cleaned up, commented and converted the first five
examples to be CQGI-compliant.
---
Examples/Ex000_Introduction.py | 20 ---------
Examples/Ex001_Simple_Block.py | 28 ++++++------
.../Ex002_Block_With_Bored_Center_Hole.py | 29 +++++++------
...03_Pillow_Block_With_Counterbored_Holes.py | 43 ++++++++++++-------
Examples/Ex004_Extruded_Cylindrical_Plate.py | 41 +++++++++++-------
Examples/Ex005_Extruded_Lines_and_Arcs.py | 41 ++++++++++++------
6 files changed, 113 insertions(+), 89 deletions(-)
delete mode 100644 Examples/Ex000_Introduction.py
diff --git a/Examples/Ex000_Introduction.py b/Examples/Ex000_Introduction.py
deleted file mode 100644
index a11fb7f..0000000
--- a/Examples/Ex000_Introduction.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-# From within FreeCAD, you can make changes to this script and then click
-# CadQuery > Execute Script, or you can press F2.
-# There are more examples in the Examples directory included with this module.
-# Ex026_Lego_Brick.py is highly recommended as a great example of what CadQuery
-# can do.
-import cadquery
-from Helpers import show
-
-# The dimensions of the box. These can be modified rather than changing the
-# object's code directly.
-length = 2.0
-height = 1.0
-thickness = 1.0
-
-# Create a 3D box based on the dimension variables above
-result = cadquery.Workplane("XY").box(length, height, thickness)
-
-# Render the solid
-show(result)
diff --git a/Examples/Ex001_Simple_Block.py b/Examples/Ex001_Simple_Block.py
index a95b064..12c0ae5 100644
--- a/Examples/Ex001_Simple_Block.py
+++ b/Examples/Ex001_Simple_Block.py
@@ -1,15 +1,19 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the box. These can be modified rather than changing the
-# object's code directly.
-length = 80.0
-height = 60.0
-thickness = 10.0
+# These can be modified rather than hardcoding values for each dimension.
+length = 80.0 # Length of the block
+height = 60.0 # Height of the block
+thickness = 10.0 # Thickness of the block
-# Create a 3D box based on the dimension variables above
-result = cadquery.Workplane("XY").box(length, height, thickness)
+# Create a 3D block based on the dimension variables above.
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the X and Y origins to define the workplane, meaning that the
+# positive Z direction is "up", and the negative Z direction is "down".
+result = cq.Workplane("XY").box(length, height, thickness)
-# Render the solid
-show(result)
+# The following method is now outdated, but can still be used to display the
+# results of the script if you want
+# from Helpers import show
+# show(result) # Render the result of this script
+
+build_object(result)
diff --git a/Examples/Ex002_Block_With_Bored_Center_Hole.py b/Examples/Ex002_Block_With_Bored_Center_Hole.py
index 6203204..a1dda36 100644
--- a/Examples/Ex002_Block_With_Bored_Center_Hole.py
+++ b/Examples/Ex002_Block_With_Bored_Center_Hole.py
@@ -1,17 +1,20 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the box. These can be modified rather than changing the
-# object's code directly.
-length = 80.0
-height = 60.0
-thickness = 10.0
-center_hole_dia = 22.0
+# These can be modified rather than hardcoding values for each dimension.
+length = 80.0 # Length of the block
+height = 60.0 # Height of the block
+thickness = 10.0 # Thickness of the block
+center_hole_dia = 22.0 # Diameter of center hole in block
-# Create a box based on the dimensions above and add a 22mm center hole
-result = cadquery.Workplane("XY").box(length, height, thickness) \
+# Create a block based on the dimensions above and add a 22mm center hole.
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the X and Y origins to define the workplane, meaning that the
+# positive Z direction is "up", and the negative Z direction is "down".
+# 2. The highest (max) Z face is selected and a new workplane is created on it.
+# 3. The new workplane is used to drill a hole through the block.
+# 3a. The hole is automatically centered in the workplane.
+result = cq.Workplane("XY").box(length, height, thickness) \
.faces(">Z").workplane().hole(center_hole_dia)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Examples/Ex003_Pillow_Block_With_Counterbored_Holes.py b/Examples/Ex003_Pillow_Block_With_Counterbored_Holes.py
index 74c1d84..981b5c1 100644
--- a/Examples/Ex003_Pillow_Block_With_Counterbored_Holes.py
+++ b/Examples/Ex003_Pillow_Block_With_Counterbored_Holes.py
@@ -1,23 +1,34 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the box. These can be modified rather than changing the
-# object's code directly.
-length = 80.0
-height = 60.0
-thickness = 10.0
-center_hole_dia = 22.0
-cbore_hole_diameter = 2.4
-cbore_diameter = 4.4
-cbore_depth = 2.1
+# These can be modified rather than hardcoding values for each dimension.
+length = 80.0 # Length of the block
+height = 60.0 # Height of the block
+thickness = 10.0 # Thickness of the block
+center_hole_dia = 22.0 # Diameter of center hole in block
+cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter
+cbore_diameter = 4.4 # Bolt head pocket hole diameter
+cbore_depth = 2.1 # Bolt head pocket hole depth
-# Create a 3D box based on the dimensions above and add 4 counterbored holes
-result = cadquery.Workplane("XY").box(length, height, thickness) \
+# Create a 3D block based on the dimensions above and add a 22mm center hold
+# and 4 counterbored holes for bolts
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the X and Y origins to define the workplane, meaning that the
+# positive Z direction is "up", and the negative Z direction is "down".
+# 2. The highest(max) Z face is selected and a new workplane is created on it.
+# 3. The new workplane is used to drill a hole through the block.
+# 3a. The hole is automatically centered in the workplane.
+# 4. The highest(max) Z face is selected and a new workplane is created on it.
+# 5. A for-construction rectangle is created on the workplane based on the
+# block's overall dimensions.
+# 5a. For-construction objects are used only to place other geometry, they
+# do not show up in the final displayed geometry.
+# 6. The vertices of the rectangle (corners) are selected, and a counter-bored
+# hole is placed at each of the vertices (all 4 of them at once).
+result = cq.Workplane("XY").box(length, height, thickness) \
.faces(">Z").workplane().hole(center_hole_dia) \
.faces(">Z").workplane() \
.rect(length - 8.0, height - 8.0, forConstruction=True) \
.vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Examples/Ex004_Extruded_Cylindrical_Plate.py b/Examples/Ex004_Extruded_Cylindrical_Plate.py
index 0de305a..4ab483d 100644
--- a/Examples/Ex004_Extruded_Cylindrical_Plate.py
+++ b/Examples/Ex004_Extruded_Cylindrical_Plate.py
@@ -1,18 +1,29 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the model. These can be modified rather than changing the
-# object's code directly.
-circle_radius = 50.0
-rectangle_width = 13.0
-rectangle_length = 19.0
-thickness = 13.0
+# These can be modified rather than hardcoding values for each dimension.
+circle_radius = 50.0 # Radius of the plate
+thickness = 13.0 # Thickness of the plate
+rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate
+rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
-# Extrude a cylindrical plate with a rectangular hole in the middle of it
-result = cadquery.Workplane("front").circle(circle_radius) \
- .rect(rectangle_width, rectangle_length) \
- .extrude(thickness)
+# Extrude a cylindrical plate with a rectangular hole in the middle of it.
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the named plane orientation "front" to define the workplane, meaning
+# that the positive Z direction is "up", and the negative Z direction
+# is "down".
+# 2. The 2D geometry for the outer circle is created at the same time as the
+# rectangle that will create the hole in the center.
+# 2a. The circle and the rectangle will be automatically centered on the
+# workplane.
+# 2b. Unlike some other functions like the hole(), circle() takes
+# a radius and not a diameter.
+# 3. The circle and rectangle are extruded together, creating a cylindrical
+# plate with a rectangular hole in the center.
+# 3a. circle() and rect() could be changed to any other shape to completely
+# change the resulting plate and/or the hole in it.
+result = cq.Workplane("front").circle(circle_radius) \
+ .rect(rectangle_width, rectangle_length) \
+ .extrude(thickness)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Examples/Ex005_Extruded_Lines_and_Arcs.py b/Examples/Ex005_Extruded_Lines_and_Arcs.py
index 34e3a1b..cc67cbf 100644
--- a/Examples/Ex005_Extruded_Lines_and_Arcs.py
+++ b/Examples/Ex005_Extruded_Lines_and_Arcs.py
@@ -1,17 +1,32 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the model. These can be modified rather than changing the
-# object's code directly.
-width = 2.0
-thickness = 0.25
+# These can be modified rather than hardcoding values for each dimension.
+width = 2.0 # Overall width of the plate
+thickness = 0.25 # Thickness of the plate
# Extrude a plate outline made of lines and an arc
-result = cadquery.Workplane("front").lineTo(width, 0) \
- .lineTo(width, 1.0) \
- .threePointArc((1.0, 1.5), (0.0, 1.0)) \
- .close().extrude(thickness)
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the named plane orientation "front" to define the workplane, meaning
+# that the positive Z direction is "up", and the negative Z direction
+# is "down".
+# 2. Draws a line from the origin to an X position of the plate's width.
+# 2a. The starting point of a 2D drawing like this will be at the center of the
+# workplane (0, 0) unless the moveTo() function moves the starting point.
+# 3. A line is drawn from the last position straight up in the Y direction
+# 1.0 millimeters.
+# 4. An arc is drawn from the last point, through point (1.0, 1.5) which is
+# half-way back to the origin in the X direction and 0.5 mm above where
+# the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm
+# above (in the Y direction) where our first line started from.
+# 5. close() is called to automatically draw the last line for us and close
+# the sketch so that it can be extruded.
+# 5a. Without the close(), the 2D sketch will be left open and the extrude
+# operation will provide unpredictable results.
+# 6. The 2D sketch is extruded into a solid object of the specified thickness.
+result = cq.Workplane("front").lineTo(width, 0) \
+ .lineTo(width, 1.0) \
+ .threePointArc((1.0, 1.5), (0.0, 1.0)) \
+ .close().extrude(thickness)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
From a54a7a21a6662b04da68555de6298365f58846d9 Mon Sep 17 00:00:00 2001
From: Jeremy Mack Wright
Date: Fri, 15 Sep 2017 14:22:34 -0400
Subject: [PATCH 14/19] Converted more examples and switched to CadQuery being
a submodule rather than a subtree.
---
.gitmodules | 3 +
.../Ex006_Moving_the_Current_Working_Point.py | 45 +-
Examples/Ex007_Using_Point_Lists.py | 41 +-
Examples/Ex008_Polygon_Creation.py | 45 +-
Init.py | 2 +-
Libs/cadquery | 1 +
Libs/cadquery-lib/AUTHORS.md | 4 -
Libs/cadquery-lib/LICENSE | 208 --
Libs/cadquery-lib/MANIFEST | 24 -
Libs/cadquery-lib/MANIFEST.in | 1 -
Libs/cadquery-lib/README.md | 272 --
Libs/cadquery-lib/README.txt | 125 -
Libs/cadquery-lib/build-docs.sh | 2 -
Libs/cadquery-lib/cadquery.egg-info/PKG-INFO | 154 -
.../cadquery.egg-info/SOURCES.txt | 29 -
.../cadquery.egg-info/dependency_links.txt | 1 -
.../cadquery.egg-info/not-zip-safe | 1 -
.../cadquery.egg-info/top_level.txt | 2 -
Libs/cadquery-lib/cadquery/README.txt | 8 -
Libs/cadquery-lib/cadquery/__init__.py | 21 -
.../cadquery-lib/cadquery/contrib/__init__.py | 18 -
Libs/cadquery-lib/cadquery/cq.py | 2565 -----------------
Libs/cadquery-lib/cadquery/cq_directive.py | 85 -
Libs/cadquery-lib/cadquery/cqgi.py | 477 ---
.../cadquery/freecad_impl/README.txt | 3 -
.../cadquery/freecad_impl/__init__.py | 107 -
.../cadquery/freecad_impl/exporters.py | 392 ---
.../cadquery/freecad_impl/geom.py | 647 -----
.../cadquery/freecad_impl/importers.py | 71 -
.../cadquery/freecad_impl/shapes.py | 1044 -------
.../cadquery-lib/cadquery/plugins/__init__.py | 18 -
Libs/cadquery-lib/cadquery/selectors.py | 663 -----
Libs/cadquery-lib/changes.md | 100 -
Libs/cadquery-lib/doc/README | 2 -
.../doc/_static/ParametricPulley.PNG | Bin 59186 -> 0 bytes
Libs/cadquery-lib/doc/_static/block.png | Bin 9385 -> 0 bytes
.../doc/_static/cadquery_cheatsheet.html | 404 ---
Libs/cadquery-lib/doc/_static/cqlogo.png | Bin 3136 -> 0 bytes
.../doc/_static/hyOzd-cablefix.png | Bin 27301 -> 0 bytes
.../doc/_static/hyOzd-finished.jpg | Bin 149415 -> 0 bytes
Libs/cadquery-lib/doc/_static/new_badge.png | Bin 3163 -> 0 bytes
.../doc/_static/parametric-cup-screencap.PNG | Bin 66208 -> 0 bytes
.../parametric-pillowblock-screencap.png | Bin 59428 -> 0 bytes
Libs/cadquery-lib/doc/_static/pillowblock.png | Bin 17582 -> 0 bytes
.../cadquery-lib/doc/_static/quickstart-1.png | Bin 6162 -> 0 bytes
.../cadquery-lib/doc/_static/quickstart-2.png | Bin 6162 -> 0 bytes
.../cadquery-lib/doc/_static/quickstart-3.png | Bin 7084 -> 0 bytes
.../cadquery-lib/doc/_static/quickstart-4.png | Bin 7095 -> 0 bytes
.../cadquery-lib/doc/_static/quickstart-5.png | Bin 11703 -> 0 bytes
Libs/cadquery-lib/doc/_static/quickstart.png | Bin 17947 -> 0 bytes
.../doc/_static/quickstart/000.png | Bin 11963 -> 0 bytes
.../doc/_static/quickstart/001.png | Bin 56946 -> 0 bytes
.../doc/_static/quickstart/002.png | Bin 120334 -> 0 bytes
.../doc/_static/quickstart/003.png | Bin 123947 -> 0 bytes
.../doc/_static/quickstart/004.png | Bin 124307 -> 0 bytes
.../doc/_static/quickstart/005.png | Bin 136681 -> 0 bytes
Libs/cadquery-lib/doc/_static/simpleblock.png | Bin 8698 -> 0 bytes
Libs/cadquery-lib/doc/apireference.rst | 169 --
Libs/cadquery-lib/doc/classreference.rst | 71 -
Libs/cadquery-lib/doc/conf.py | 276 --
Libs/cadquery-lib/doc/cqgi.rst | 167 --
Libs/cadquery-lib/doc/designprinciples.rst | 74 -
Libs/cadquery-lib/doc/examples.rst | 1096 -------
Libs/cadquery-lib/doc/extending.rst | 180 --
Libs/cadquery-lib/doc/fileformat.rst | 24 -
Libs/cadquery-lib/doc/index.rst | 58 -
Libs/cadquery-lib/doc/installation.rst | 58 -
Libs/cadquery-lib/doc/intro.rst | 95 -
Libs/cadquery-lib/doc/primer.rst | 153 -
Libs/cadquery-lib/doc/quickstart.rst | 242 --
Libs/cadquery-lib/doc/roadmap.rst | 154 -
Libs/cadquery-lib/doc/selectors.rst | 140 -
.../examples/FreeCAD/Ex001_Simple_Block.py | 32 -
.../Ex002_Block_With_Bored_Center_Hole.py | 33 -
...03_Pillow_Block_With_Counterbored_Holes.py | 40 -
.../Ex004_Extruded_Cylindrical_Plate.py | 34 -
.../FreeCAD/Ex005_Extruded_Lines_and_Arcs.py | 33 -
.../Ex006_Moving_the_Current_Working_Point.py | 38 -
.../FreeCAD/Ex007_Using_Point_Lists.py | 36 -
.../FreeCAD/Ex008_Polygon_Creation.py | 36 -
.../examples/FreeCAD/Ex009_Polylines.py | 44 -
.../Ex010_Defining_an_Edge_with_a_Spline.py | 45 -
.../Ex011_Mirroring_Symmetric_Geometry.py | 34 -
.../Ex012_Creating_Workplanes_on_Faces.py | 31 -
.../Ex013_Locating_a_Workplane_on_a_Vertex.py | 34 -
.../FreeCAD/Ex014_Offset_Workplanes.py | 34 -
.../FreeCAD/Ex015_Rotated_Workplanes.py | 32 -
.../Ex016_Using_Construction_Geometry.py | 29 -
.../Ex017_Shelling_to_Create_Thin_Features.py | 28 -
.../examples/FreeCAD/Ex018_Making_Lofts.py | 29 -
.../FreeCAD/Ex019_Counter_Sunk_Holes.py | 30 -
.../Ex020_Rounding_Corners_with_Fillets.py | 28 -
.../FreeCAD/Ex021_Splitting_an_Object.py | 31 -
.../FreeCAD/Ex022_Classic_OCC_Bottle.py | 40 -
.../FreeCAD/Ex023_Parametric_Enclosure.py | 102 -
...x024_Using_FreeCAD_Solids_as_CQ_Objects.py | 41 -
.../examples/FreeCAD/Ex025_Revolution.py | 41 -
Libs/cadquery-lib/registering.txt | 11 -
Libs/cadquery-lib/requirements-dev.txt | 6 -
Libs/cadquery-lib/requirements.txt | 1 -
Libs/cadquery-lib/runtests.py | 18 -
Libs/cadquery-lib/setup.cfg | 0
Libs/cadquery-lib/setup.py | 58 -
Libs/cadquery-lib/tests/README.txt | 1 -
Libs/cadquery-lib/tests/TestCQGI.py | 217 --
Libs/cadquery-lib/tests/TestCQSelectors.py | 503 ----
Libs/cadquery-lib/tests/TestCadObjects.py | 104 -
Libs/cadquery-lib/tests/TestCadQuery.py | 1424 ---------
Libs/cadquery-lib/tests/TestExporters.py | 43 -
Libs/cadquery-lib/tests/TestImporters.py | 54 -
Libs/cadquery-lib/tests/TestWorkplanes.py | 125 -
Libs/cadquery-lib/tests/__init__.py | 54 -
112 files changed, 92 insertions(+), 14004 deletions(-)
create mode 100644 .gitmodules
create mode 160000 Libs/cadquery
delete mode 100644 Libs/cadquery-lib/AUTHORS.md
delete mode 100644 Libs/cadquery-lib/LICENSE
delete mode 100644 Libs/cadquery-lib/MANIFEST
delete mode 100644 Libs/cadquery-lib/MANIFEST.in
delete mode 100644 Libs/cadquery-lib/README.md
delete mode 100644 Libs/cadquery-lib/README.txt
delete mode 100755 Libs/cadquery-lib/build-docs.sh
delete mode 100644 Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
delete mode 100644 Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
delete mode 100644 Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
delete mode 100644 Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
delete mode 100644 Libs/cadquery-lib/cadquery.egg-info/top_level.txt
delete mode 100644 Libs/cadquery-lib/cadquery/README.txt
delete mode 100644 Libs/cadquery-lib/cadquery/__init__.py
delete mode 100644 Libs/cadquery-lib/cadquery/contrib/__init__.py
delete mode 100644 Libs/cadquery-lib/cadquery/cq.py
delete mode 100644 Libs/cadquery-lib/cadquery/cq_directive.py
delete mode 100644 Libs/cadquery-lib/cadquery/cqgi.py
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/README.txt
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/exporters.py
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/geom.py
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/importers.py
delete mode 100644 Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
delete mode 100644 Libs/cadquery-lib/cadquery/plugins/__init__.py
delete mode 100644 Libs/cadquery-lib/cadquery/selectors.py
delete mode 100644 Libs/cadquery-lib/changes.md
delete mode 100644 Libs/cadquery-lib/doc/README
delete mode 100644 Libs/cadquery-lib/doc/_static/ParametricPulley.PNG
delete mode 100644 Libs/cadquery-lib/doc/_static/block.png
delete mode 100644 Libs/cadquery-lib/doc/_static/cadquery_cheatsheet.html
delete mode 100644 Libs/cadquery-lib/doc/_static/cqlogo.png
delete mode 100644 Libs/cadquery-lib/doc/_static/hyOzd-cablefix.png
delete mode 100644 Libs/cadquery-lib/doc/_static/hyOzd-finished.jpg
delete mode 100644 Libs/cadquery-lib/doc/_static/new_badge.png
delete mode 100644 Libs/cadquery-lib/doc/_static/parametric-cup-screencap.PNG
delete mode 100644 Libs/cadquery-lib/doc/_static/parametric-pillowblock-screencap.png
delete mode 100644 Libs/cadquery-lib/doc/_static/pillowblock.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart-1.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart-2.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart-3.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart-4.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart-5.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/000.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/001.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/002.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/003.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/004.png
delete mode 100644 Libs/cadquery-lib/doc/_static/quickstart/005.png
delete mode 100644 Libs/cadquery-lib/doc/_static/simpleblock.png
delete mode 100644 Libs/cadquery-lib/doc/apireference.rst
delete mode 100644 Libs/cadquery-lib/doc/classreference.rst
delete mode 100644 Libs/cadquery-lib/doc/conf.py
delete mode 100644 Libs/cadquery-lib/doc/cqgi.rst
delete mode 100644 Libs/cadquery-lib/doc/designprinciples.rst
delete mode 100644 Libs/cadquery-lib/doc/examples.rst
delete mode 100644 Libs/cadquery-lib/doc/extending.rst
delete mode 100644 Libs/cadquery-lib/doc/fileformat.rst
delete mode 100644 Libs/cadquery-lib/doc/index.rst
delete mode 100644 Libs/cadquery-lib/doc/installation.rst
delete mode 100644 Libs/cadquery-lib/doc/intro.rst
delete mode 100644 Libs/cadquery-lib/doc/primer.rst
delete mode 100644 Libs/cadquery-lib/doc/quickstart.rst
delete mode 100644 Libs/cadquery-lib/doc/roadmap.rst
delete mode 100644 Libs/cadquery-lib/doc/selectors.rst
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex001_Simple_Block.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex002_Block_With_Bored_Center_Hole.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex003_Pillow_Block_With_Counterbored_Holes.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex004_Extruded_Cylindrical_Plate.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex006_Moving_the_Current_Working_Point.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex007_Using_Point_Lists.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex008_Polygon_Creation.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex009_Polylines.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex010_Defining_an_Edge_with_a_Spline.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex011_Mirroring_Symmetric_Geometry.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex012_Creating_Workplanes_on_Faces.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex013_Locating_a_Workplane_on_a_Vertex.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex014_Offset_Workplanes.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex015_Rotated_Workplanes.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex016_Using_Construction_Geometry.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex017_Shelling_to_Create_Thin_Features.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex018_Making_Lofts.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex019_Counter_Sunk_Holes.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex020_Rounding_Corners_with_Fillets.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex021_Splitting_an_Object.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex022_Classic_OCC_Bottle.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex023_Parametric_Enclosure.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex024_Using_FreeCAD_Solids_as_CQ_Objects.py
delete mode 100644 Libs/cadquery-lib/examples/FreeCAD/Ex025_Revolution.py
delete mode 100644 Libs/cadquery-lib/registering.txt
delete mode 100644 Libs/cadquery-lib/requirements-dev.txt
delete mode 100644 Libs/cadquery-lib/requirements.txt
delete mode 100644 Libs/cadquery-lib/runtests.py
delete mode 100644 Libs/cadquery-lib/setup.cfg
delete mode 100644 Libs/cadquery-lib/setup.py
delete mode 100644 Libs/cadquery-lib/tests/README.txt
delete mode 100644 Libs/cadquery-lib/tests/TestCQGI.py
delete mode 100644 Libs/cadquery-lib/tests/TestCQSelectors.py
delete mode 100644 Libs/cadquery-lib/tests/TestCadObjects.py
delete mode 100644 Libs/cadquery-lib/tests/TestCadQuery.py
delete mode 100644 Libs/cadquery-lib/tests/TestExporters.py
delete mode 100644 Libs/cadquery-lib/tests/TestImporters.py
delete mode 100644 Libs/cadquery-lib/tests/TestWorkplanes.py
delete mode 100644 Libs/cadquery-lib/tests/__init__.py
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..fb62e70
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Libs/cadquery"]
+ path = Libs/cadquery
+ url = https://github.com/dcowden/cadquery.git
diff --git a/Examples/Ex006_Moving_the_Current_Working_Point.py b/Examples/Ex006_Moving_the_Current_Working_Point.py
index fbb463c..166fe9f 100644
--- a/Examples/Ex006_Moving_the_Current_Working_Point.py
+++ b/Examples/Ex006_Moving_the_Current_Working_Point.py
@@ -1,22 +1,35 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the model. These can be modified rather than changing the
-# object's code directly.
-circle_radius = 3.0
-thickness = 0.25
+# These can be modified rather than hardcoding values for each dimension.
+circle_radius = 3.0 # The outside radius of the plate
+thickness = 0.25 # The thickness of the plate
-# Make the plate with two cutouts in it
-# Current point is the center of the circle, at (0,0)
-result = cadquery.Workplane("front").circle(circle_radius)
-result = result.center(1.5, 0.0).rect(0.5, 0.5) # New work center is (1.5,0.0)
+# Make a plate with two cutouts in it by moving the workplane center point
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the named plane orientation "front" to define the workplane, meaning
+# that the positive Z direction is "up", and the negative Z direction
+# is "down".
+# 1b. The initial workplane center point is the center of the circle, at (0,0).
+# 2. A circle is created at the center of the workplane
+# 2a. Notice that circle() takes a radius and not a diameter
+result = cq.Workplane("front").circle(circle_radius)
-result = result.center(-1.5, 1.5).circle(0.25) # New work center is ( 0.0,1.5)
-# The new center is specified relative to the previous center,
-# not global coordinates!
+# 3. The work center is movide to (1.5, 0.0) by calling center().
+# 3a. The new center is specified relative to the previous center,not
+# relative to global coordinates.
+# 4. A 0.5mm x 0.5mm 2D square is drawn inside the circle.
+# 4a. The plate has not been extruded yet, only 2D geometry is being created.
+result = result.center(1.5, 0.0).rect(0.5, 0.5)
+# 5. The work center is moved again, this time to (-1.5, 1.5).
+# 6. A 2D circle is created at that new center with a radius of 0.25mm.
+result = result.center(-1.5, 1.5).circle(0.25)
+
+# 7. All 2D geometry is extruded to the specified thickness of the plate.
+# 7a. The small circle and the square are enclosed in the outer circle of the
+# plate and so it is assumed that we want them to be cut out of the plate.
+# A separate cut operation is not needed.
result = result.extrude(thickness)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Examples/Ex007_Using_Point_Lists.py b/Examples/Ex007_Using_Point_Lists.py
index effd879..7a1bb84 100644
--- a/Examples/Ex007_Using_Point_Lists.py
+++ b/Examples/Ex007_Using_Point_Lists.py
@@ -1,21 +1,32 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the model. These can be modified rather than changing the
-# object's code directly.
-plate_radius = 2.0
-hole_pattern_radius = 0.25
-thickness = 0.125
+# These can be modified rather than hardcoding values for each dimension.
+plate_radius = 2.0 # The radius of the plate that will be extruded
+hole_pattern_radius = 0.25 # Radius of circle where the holes will be placed
+thickness = 0.125 # The thickness of the plate that will be extruded
-# Make the plate with 4 holes in it at various points
-# Make the base
-r = cadquery.Workplane("front").circle(plate_radius)
-# Now four points are on the stack
+# Make a plate with 4 holes in it at various points in a polar arrangement from
+# the center of the workplane.
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the named plane orientation "front" to define the workplane, meaning
+# that the positive Z direction is "up", and the negative Z direction
+# is "down".
+# 2. A 2D circle is drawn that will become though outer profile of the plate.
+r = cq.Workplane("front").circle(plate_radius)
+
+# 3. Push 4 points on the stack that will be used as the center points of the
+# holes.
r = r.pushPoints([(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)])
-# Circle will operate on all four points
+
+# 4. This circle() call will operate on all four points, putting a circle at
+# each one.
r = r.circle(hole_pattern_radius)
+
+# 5. All 2D geometry is extruded to the specified thickness of the plate.
+# 5a. The small hole circles are enclosed in the outer circle of the plate and
+# so it is assumed that we want them to be cut out of the plate. A
+# separate cut operation is not needed.
result = r.extrude(thickness)
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Examples/Ex008_Polygon_Creation.py b/Examples/Ex008_Polygon_Creation.py
index b3f3469..c9d37c9 100644
--- a/Examples/Ex008_Polygon_Creation.py
+++ b/Examples/Ex008_Polygon_Creation.py
@@ -1,20 +1,39 @@
-# This example is meant to be used from within the CadQuery module of FreeCAD.
-import cadquery
-from Helpers import show
+import cadquery as cq
-# The dimensions of the model. These can be modified rather than changing the
-# object's code directly.
-width = 3.0
-height = 4.0
-thickness = 0.25
-polygon_sides = 6
-polygon_dia = 1.0
+# These can be modified rather than hardcoding values for each dimension.
+width = 3.0 # The width of the plate
+height = 4.0 # The height of the plate
+thickness = 0.25 # The thickness of the plate
+polygon_sides = 6 # The number of sides that the polygonal holes should have
+polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
# Create a plate with two polygons cut through it
-result = cadquery.Workplane("front").box(width, height, thickness) \
+# 1. Establishes a workplane that an object can be built on.
+# 1a. Uses the named plane orientation "front" to define the workplane, meaning
+# that the positive Z direction is "up", and the negative Z direction
+# is "down".
+# 2. A 3D box is created in one box() operation to represent the plate.
+# 2a. The box is centered around the origin, which creates a result that may
+# be unituitive when the polygon cuts are made.
+# 3. 2 points are pushed onto the stack and will be used as centers for the
+# polygonal holes.
+# 4. The two polygons are created, on for each point, with one call to
+# polygon() using the number of sides and the circle that bounds the
+# polygon.
+# 5. The polygons are cut thru all objects that are in the line of extrusion.
+# 5a. A face was not selected, and so the polygons are created on the
+# workplane. Since the box was centered around the origin, the polygons end
+# up being in the center of the box. This makes them cut from the center to
+# the outside along the normal (positive direction).
+# 6. The polygons are cut through all objects, starting at the center of the
+# box/plate and going "downward" (opposite of normal) direction. Functions
+# like cutBlind() assume a positive cut direction, but cutThruAll() assumes
+# instead that the cut is made from a max direction and cuts downward from
+# that max through all objects.
+result = cq.Workplane("front").box(width, height, thickness) \
.pushPoints([(0, 0.75), (0, -0.75)]) \
.polygon(polygon_sides, polygon_dia) \
.cutThruAll()
-# Render the solid
-show(result)
+# Displays the result of this script
+build_object(result)
diff --git a/Init.py b/Init.py
index bb2a5f8..120b0d7 100644
--- a/Init.py
+++ b/Init.py
@@ -14,7 +14,7 @@ libs_dir_path = os.path.join(module_base_path, 'Libs')
sys.path.insert(0, libs_dir_path)
# Tack on our CadQuery library git subtree
-cq_lib_path = os.path.join(libs_dir_path, 'cadquery-lib')
+cq_lib_path = os.path.join(libs_dir_path, 'cadquery')
sys.path.insert(1, cq_lib_path)
# Make sure we get the right libs under the FreeCAD installation
diff --git a/Libs/cadquery b/Libs/cadquery
new file mode 160000
index 0000000..0c40258
--- /dev/null
+++ b/Libs/cadquery
@@ -0,0 +1 @@
+Subproject commit 0c40258e28c0fad08b699430bce34ea2613d8d1e
diff --git a/Libs/cadquery-lib/AUTHORS.md b/Libs/cadquery-lib/AUTHORS.md
deleted file mode 100644
index 31b30e8..0000000
--- a/Libs/cadquery-lib/AUTHORS.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Core CQ Developers
-
-* [Dave Cowden](https://github.com/dcowden), Creator - Lead Developer
-* [Jeremy Wright](https://github.com/jmwright) (a.k.a [innovationstech](https://github.com/innovationstech))
diff --git a/Libs/cadquery-lib/LICENSE b/Libs/cadquery-lib/LICENSE
deleted file mode 100644
index d4fa4f1..0000000
--- a/Libs/cadquery-lib/LICENSE
+++ /dev/null
@@ -1,208 +0,0 @@
-CadQuery
-Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the Apache Public License, v 2.0
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [Parametric Products Intellectual Holdings, LLC]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
diff --git a/Libs/cadquery-lib/MANIFEST b/Libs/cadquery-lib/MANIFEST
deleted file mode 100644
index ce5c215..0000000
--- a/Libs/cadquery-lib/MANIFEST
+++ /dev/null
@@ -1,24 +0,0 @@
-README.txt
-README.md
-setup.cfg
-setup.py
-cadquery\cq.py
-cadquery\__init__.py
-cadquery\cq_directive.py
-cadquery\selectors.py
-cadquery\cqgi.py
-cadquery\contrib\__init__.py
-cadquery\freecad_impl\__init__.py
-cadquery\freecad_impl\exporters.py
-cadquery\freecad_impl\importers.py
-cadquery\freecad_impl\geom.py
-cadquery\freecad_impl\shapes.py
-cadquery\plugins\__init__.py
-tests\TestCQSelectors.py
-tests\TestCadObjects.py
-tests\TestCadQuery.py
-tests\TestExporters.py
-tests\TestImporters.py
-tests\TestWorkplanes.py
-tests\TestCQGI.py
-tests\__init__.py
diff --git a/Libs/cadquery-lib/MANIFEST.in b/Libs/cadquery-lib/MANIFEST.in
deleted file mode 100644
index bb3ec5f..0000000
--- a/Libs/cadquery-lib/MANIFEST.in
+++ /dev/null
@@ -1 +0,0 @@
-include README.md
diff --git a/Libs/cadquery-lib/README.md b/Libs/cadquery-lib/README.md
deleted file mode 100644
index d041e0f..0000000
--- a/Libs/cadquery-lib/README.md
+++ /dev/null
@@ -1,272 +0,0 @@
-What is a CadQuery?
-========================================
-
-[](https://travis-ci.org/dcowden/cadquery?branch=master)
-[](https://coveralls.io/r/dcowden/cadquery)
-[](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
-[](https://github.com/dcowden/cadquery/blob/master/LICENSE)
-
-CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
-
-CadQuery has several goals:
-
-* 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 (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.
-
-Full Documentation
-============================
-You can find the full cadquery documentation at http://dcowden.github.io/cadquery
-
-Getting Started With CadQuery
-========================================
-
-The easiest way to get started with CadQuery is to Install FreeCAD (version 14+) (http://www.freecadweb.org/), and then to use our great CadQuery-FreeCAD plugin here: https://github.com/jmwright/cadquery-freecad-module
-
-
-It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
-
-It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
-
-We also have a Google Group to make it easy to get help from other CadQuery users. Please join the group and introduce yourself, and we would also love to hear what you are doing with CadQuery. https://groups.google.com/forum/#!forum/cadquery
-
-Examples
-======================
-
-This resin mold was modeled using cadquery and then created on a CNC machine:
-
-
-
-This Prusa i3 extruder support uses cadquery to build the model (https://github.com/adam-urbanczyk/cadquery-models) :
-
-
-
-
-
-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):
-
-
-
-
-This example uses Jupyter notebook to produce a really cool web-based scripting environment ( https://github.com/RustyVermeer/avnb/blob/master/readme.md ) :
-
-
-
-
-
-
-
-
-
-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?
-========================================
-
-CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
-
-The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
-
-CadQuery scripts have several key advantages over OpenSCAD:
-
-1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
- This includes many standard libraries and IDEs
-
-2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
- by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
- in addition to the standard CSG operations supported by CGAL
-
-3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
- and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
-
-4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
- features based on the position of other features, workplanes, vertices, etc.
-
-5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
-
-License
-========
-
-CadQuery is licensed under the terms of the Apache Public License, version 2.0.
-A copy of the license can be found at http://www.apache.org/licenses/LICENSE-2.0
-
-CadQuery GUI Interfaces
-=======================
-
-There are currently several known CadQuery GUIs:
-
-### CadQuery FreeCAD Module
-You can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
-
-### CadQuery GUI (under active development)
-Work is underway on a stand-alone gui here: https://github.com/jmwright/cadquery-gui
-
-### ParametricParts.com
-If you are impatient and want to see a working example with no installation, have a look at this lego brick example http://parametricparts.com/parts/vqb5dy69/.
-
-The script that generates the model is on the 'modelscript' tab.
-
-
-Installing -- FreeStanding Installation
-========================================
-
-Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
-
-1. install FreeCAD, version 0.15 or greater for your platform. https://github.com/FreeCAD/FreeCAD/releases.
-
-2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
- preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
- lib directory to your path. On (*Nix)::
-
-```python
- import sys
- sys.path.append('/usr/lib/freecad/lib')
-```
-
- or on Windows::
-
-```python
- import sys
- sys.path.append('/c/apps/FreeCAD/bin')
-```
-
- *NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
-
-3. install cadquery::
-```bash
- pip install cadquery
-```
-4. installing cadquery should install pyparsing as well, but if not::
-```bash
- pip install pyparsing
-```
-5. test your installation::
-```python
- from cadquery import *
- box = Workplane("XY").box(1,2,3)
- exporters.toString(box,'STL')
-```
-You're up and running!
-
-Installing -- Using CadQuery from Inside FreeCAD
-=================================================
-
-Use the CadQuery module for FreeCAD here:
- https://github.com/jmwright/cadquery-freecad-module
-
-It includes a distribution of the latest version of cadquery.
-
-Roadmap/Future Work
-=======================
-
-Work has begun on Cadquery 2.0, which will feature:
-
- 1. Feature trees, for more powerful selection
- 2. Direct use of OpenCascade Community Edition(OCE), so that it is no longer required to install FreeCAD
- 3. https://github.com/jmwright/cadquery-gui, which will allow visualization of workplanes
-
-The project page can be found here: https://github.com/dcowden/cadquery/projects/1
-
-A more detailed description of the plan for CQ 2.0 is here: https://docs.google.com/document/d/1cXuxBkVeYmGOo34MGRdG7E3ILypQqkrJ26oVf3CUSPQ
-
-Where does the name CadQuery come from?
-========================================
-
-CadQuery is inspired by jQuery, a popular framework that
-revolutionized web development involving javascript.
-
-If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
-
-* A fluent api to create clean, easy to read code
-* Language features that make selection and iteration incredibly easy
-*
-* Ability to use the library along side other python libraries
-* Clear and complete documentation, with plenty of samples.
-
diff --git a/Libs/cadquery-lib/README.txt b/Libs/cadquery-lib/README.txt
deleted file mode 100644
index a356b5b..0000000
--- a/Libs/cadquery-lib/README.txt
+++ /dev/null
@@ -1,125 +0,0 @@
-What is a CadQuery?
-========================================
-
-CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
-
-CadQuery has several goals:
-
-* Build 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
-* 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.
-
-Getting Started With CadQuery
-========================================
-
-The easiest way to get started with CadQuery is to Install FreeCAD ( version 14 recommended ) (http://www.freecadweb.org/) , and then to use our CadQuery-FreeCAD plugin here:
-
-https://github.com/jmwright/cadquery-freecad-module
-
-
-It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
-
-It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
-
-
-Recently Added Features
-========================================
-
-* 12/5/14 -- New FreeCAD/CadQuery Module! https://github.com/jmwright/cadquery-freecad-module
-* 10/25/14 -- Added Revolution Feature ( thanks Jeremy ! )
-
-
-Why CadQuery instead of OpenSCAD?
-========================================
-
-CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
-
-The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
-
-CadQuery scripts have several key advantages over OpenSCAD:
-
-1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
- This includes many standard libraries and IDEs
-
-2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
- by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
- in addition to the standard CSG operations supported by CGAL
-
-3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
- and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
-
-4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
- features based on the position of other features, workplanes, vertices, etc.
-
-5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
-
-License
-========
-
-CadQuery is licensed under the terms of the LGPLv3. http://www.gnu.org/copyleft/lesser.html
-
-Where is the GUI?
-==================
-
-If you would like IDE support, you can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
-
-CadQuery also provides the backbone of http://parametricparts.com, so the easiest way to see it in action is to review the samples and objects there.
-
-Installing -- FreeStanding Installation
-========================================
-
-Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
-
-1. install FreeCAD, version 0.14 or greater for your platform. http://sourceforge.net/projects/free-cad/.
-
-2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
- preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
- lib directory to your path. On (*Nix)::
-
- import sys
- sys.path.append('/usr/lib/freecad/lib')
-
- or on Windows::
-
- import sys
- sys.path.append('/c/apps/FreeCAD/bin')
-
- *NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
-
-3. install cadquery::
-
- pip install cadquery
-
-3. test your installation::
-
- from cadquery import *
- box = Workplane("XY").box(1,2,3)
- exporters.toString(box,'STL')
-
-You're up and running!
-
-Installing -- Using CadQuery from Inside FreeCAD
-=================================================
-
-Use the Excellent CadQuery-FreeCAD plugin here:
- https://github.com/jmwright/cadquery-freecad-module
-
-It includes a distribution of the latest version of cadquery.
-
-Where does the name CadQuery come from?
-========================================
-
-CadQuery is inspired by ( `jQuery `_ ), a popular framework that
-revolutionized web development involving javascript.
-
-If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
-
-* A fluent api to create clean, easy to read code
-* Language features that make selection and iteration incredibly easy
-*
-* Ability to use the library along side other python libraries
-* Clear and complete documentation, with plenty of samples.
-
diff --git a/Libs/cadquery-lib/build-docs.sh b/Libs/cadquery-lib/build-docs.sh
deleted file mode 100755
index bef2f78..0000000
--- a/Libs/cadquery-lib/build-docs.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-sphinx-build -b html doc target/docs
\ No newline at end of file
diff --git a/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO b/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
deleted file mode 100644
index 823c3bd..0000000
--- a/Libs/cadquery-lib/cadquery.egg-info/PKG-INFO
+++ /dev/null
@@ -1,154 +0,0 @@
-Metadata-Version: 1.1
-Name: cadquery
-Version: 1.0.0
-Summary: CadQuery is a parametric scripting language for creating and traversing CAD models
-Home-page: https://github.com/dcowden/cadquery
-Author: David Cowden
-Author-email: dave.cowden@gmail.com
-License: Apache Public License 2.0
-Description: What is a CadQuery?
- ========================================
-
- [](https://travis-ci.org/dcowden/cadquery)
- [](https://coveralls.io/r/dcowden/cadquery)
- [](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
- [](https://github.com/dcowden/cadquery/blob/master/LICENSE)
-
- CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
-
- CadQuery has several goals:
-
- * Build 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
- * 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.
-
- Full Documentation
- ============================
- You can find the full cadquery documentation at http://dcowden.github.io/cadquery
-
-
- Getting Started With CadQuery
- ========================================
-
- The easiest way to get started with CadQuery is to Install FreeCAD (version 14+) (http://www.freecadweb.org/), and then to use our great CadQuery-FreeCAD plugin here: https://github.com/jmwright/cadquery-freecad-module
-
-
- It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
-
- It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
-
- We also have a Google Group to make it easy to get help from other CadQuery users. Please join the group and introduce yourself, and we would also love to hear what you are doing with CadQuery. https://groups.google.com/forum/#!forum/cadquery
-
-
-
- Why CadQuery instead of OpenSCAD?
- ========================================
-
- CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
-
- The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
-
- CadQuery scripts have several key advantages over OpenSCAD:
-
- 1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
- This includes many standard libraries and IDEs
-
- 2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
- by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
- in addition to the standard CSG operations supported by CGAL
-
- 3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
- and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
-
- 4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
- features based on the position of other features, workplanes, vertices, etc.
-
- 5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
-
- License
- ========
-
- CadQuery is licensed under the terms of the Apache Public License, version 2.0.
- A copy of the license can be found at http://www.apache.org/licenses/LICENSE-2.0
-
- Where is the GUI?
- ==================
-
- If you would like IDE support, you can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
-
- CadQuery also provides the backbone of http://parametricparts.com, so the easiest way to see it in action is to review the samples and objects there.
-
- Installing -- FreeStanding Installation
- ========================================
-
- Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
-
- 1. install FreeCAD, version 0.12 or greater for your platform. http://sourceforge.net/projects/free-cad/.
-
- 2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
- preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
- lib directory to your path. On (*Nix)::
-
- import sys
- sys.path.append('/usr/lib/freecad/lib')
-
- or on Windows::
-
- import sys
- sys.path.append('/c/apps/FreeCAD/bin')
-
- *NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
-
- 3. install cadquery::
-
- pip install cadquery
-
- 3. test your installation::
-
- from cadquery import *
- box = Workplane("XY").box(1,2,3)
- exporters.toString(box,'STL')
-
- You're up and running!
-
- Installing -- Using CadQuery from Inside FreeCAD
- =================================================
-
- Use the Excellent CadQuery-FreeCAD plugin here:
- https://github.com/jmwright/cadquery-freecad-module
-
- It includes a distribution of the latest version of cadquery.
-
- Where does the name CadQuery come from?
- ========================================
-
- CadQuery is inspired by ( `jQuery `_ ), a popular framework that
- revolutionized web development involving javascript.
-
- If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
-
- * A fluent api to create clean, easy to read code
- * Language features that make selection and iteration incredibly easy
- *
- * Ability to use the library along side other python libraries
- * Clear and complete documentation, with plenty of samples.
-
-
-Platform: any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: End Users/Desktop
-Classifier: Intended Audience :: Information Technology
-Classifier: Intended Audience :: Science/Research
-Classifier: Intended Audience :: System Administrators
-Classifier: License :: OSI Approved :: Apache Software License
-Classifier: Operating System :: POSIX
-Classifier: Operating System :: MacOS
-Classifier: Operating System :: Unix
-Classifier: Programming Language :: Python
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Internet
-Classifier: Topic :: Scientific/Engineering
diff --git a/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt b/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
deleted file mode 100644
index 0507b5e..0000000
--- a/Libs/cadquery-lib/cadquery.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-MANIFEST.in
-README.txt
-setup.cfg
-setup.py
-cadquery/__init__.py
-cadquery/cq.py
-cadquery/cq_directive.py
-cadquery/cqgi.py
-cadquery/selectors.py
-cadquery.egg-info/PKG-INFO
-cadquery.egg-info/SOURCES.txt
-cadquery.egg-info/dependency_links.txt
-cadquery.egg-info/not-zip-safe
-cadquery.egg-info/top_level.txt
-cadquery/contrib/__init__.py
-cadquery/freecad_impl/__init__.py
-cadquery/freecad_impl/exporters.py
-cadquery/freecad_impl/geom.py
-cadquery/freecad_impl/importers.py
-cadquery/freecad_impl/shapes.py
-cadquery/plugins/__init__.py
-tests/TestCQGI.py
-tests/TestCQSelectors.py
-tests/TestCadObjects.py
-tests/TestCadQuery.py
-tests/TestExporters.py
-tests/TestImporters.py
-tests/TestWorkplanes.py
-tests/__init__.py
\ No newline at end of file
diff --git a/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt b/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/Libs/cadquery-lib/cadquery.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe b/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
deleted file mode 100644
index 8b13789..0000000
--- a/Libs/cadquery-lib/cadquery.egg-info/not-zip-safe
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/Libs/cadquery-lib/cadquery.egg-info/top_level.txt b/Libs/cadquery-lib/cadquery.egg-info/top_level.txt
deleted file mode 100644
index 2bbe7fc..0000000
--- a/Libs/cadquery-lib/cadquery.egg-info/top_level.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-cadquery
-tests
diff --git a/Libs/cadquery-lib/cadquery/README.txt b/Libs/cadquery-lib/cadquery/README.txt
deleted file mode 100644
index ab8dc7e..0000000
--- a/Libs/cadquery-lib/cadquery/README.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-***
-Core CadQuery implementation.
-
-No files should depend on or import FreeCAD , pythonOCC, or other CAD Kernel libraries!!!
-Dependencies should be on the classes provided by implementation packages, which in turn
-can depend on CAD libraries.
-
-***
\ No newline at end of file
diff --git a/Libs/cadquery-lib/cadquery/__init__.py b/Libs/cadquery-lib/cadquery/__init__.py
deleted file mode 100644
index a9b77e4..0000000
--- a/Libs/cadquery-lib/cadquery/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#these items point to the freecad implementation
-from .freecad_impl.geom import Plane,BoundBox,Vector,Matrix,sortWiresByBuildOrder
-from .freecad_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound
-from .freecad_impl import exporters
-from .freecad_impl import importers
-
-#these items are the common implementation
-
-#the order of these matter
-from .selectors import *
-from .cq import *
-
-
-__all__ = [
- 'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
- 'Shape','Vertex','Edge','Wire','Face','Solid','Shell','Compound','exporters', 'importers',
- 'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector',
- 'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins'
-]
-
-__version__ = "1.0.0"
diff --git a/Libs/cadquery-lib/cadquery/contrib/__init__.py b/Libs/cadquery-lib/cadquery/contrib/__init__.py
deleted file mode 100644
index 67c7b68..0000000
--- a/Libs/cadquery-lib/cadquery/contrib/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-"""
diff --git a/Libs/cadquery-lib/cadquery/cq.py b/Libs/cadquery-lib/cadquery/cq.py
deleted file mode 100644
index dc685c9..0000000
--- a/Libs/cadquery-lib/cadquery/cq.py
+++ /dev/null
@@ -1,2565 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-"""
-
-import time
-import math
-from cadquery import *
-from cadquery import selectors
-from cadquery import exporters
-
-
-class CQContext(object):
- """
- A shared context for modeling.
-
- All objects in the same CQ chain share a reference to this same object instance
- which allows for shared state when needed,
- """
- def __init__(self):
- self.pendingWires = [] # a list of wires that have been created and need to be extruded
- self.pendingEdges = [] # a list of created pending edges that need to be joined into wires
- # a reference to the first point for a set of edges.
- # Used to determine how to behave when close() is called
- self.firstPoint = None
- self.tolerance = 0.0001 # user specified tolerance
-
-
-class CQ(object):
- """
- Provides enhanced functionality for a wrapped CAD primitive.
-
- Examples include feature selection, feature creation, 2d drawing
- using work planes, and 3d operations like fillets, shells, and splitting
- """
-
- def __init__(self, obj):
- """
- Construct a new CadQuery (CQ) object that wraps a CAD primitive.
-
- :param obj: Object to Wrap.
- :type obj: A CAD Primitive ( wire,vertex,face,solid,edge )
- """
- self.objects = []
- self.ctx = CQContext()
- self.parent = None
-
- if obj: # guarded because sometimes None for internal use
- self.objects.append(obj)
-
- def newObject(self, objlist):
- """
- Make a new CQ object.
-
- :param objlist: The stack of objects to use
- :type objlist: a list of CAD primitives ( wire,face,edge,solid,vertex,etc )
-
- The parent of the new object will be set to the current object,
- to preserve the chain correctly.
-
- Custom plugins and subclasses should use this method to create new CQ objects
- correctly.
- """
- r = CQ(None) # create a completely blank one
- r.parent = self
- r.ctx = self.ctx # context solid remains the same
- r.objects = list(objlist)
- return r
-
- def _collectProperty(self, propName):
- """
- Collects all of the values for propName,
- for all items on the stack.
- FreeCAD objects do not implement id correctly,
- so hashCode is used to ensure we don't add the same
- object multiple times.
-
- One weird use case is that the stack could have a solid reference object
- on it. This is meant to be a reference to the most recently modified version
- of the context solid, whatever it is.
- """
- all = {}
- for o in self.objects:
-
- # tricky-- if an object is a compound of solids,
- # do not return all of the solids underneath-- typically
- # then we'll keep joining to ourself
- if propName == 'Solids' and isinstance(o, Solid) and o.ShapeType() == 'Compound':
- for i in getattr(o, 'Compounds')():
- all[i.hashCode()] = i
- else:
- if hasattr(o, propName):
- for i in getattr(o, propName)():
- all[i.hashCode()] = i
-
- return list(all.values())
-
- def split(self, keepTop=False, keepBottom=False):
- """
- Splits a solid on the stack into two parts, optionally keeping the separate parts.
-
- :param boolean keepTop: True to keep the top, False or None to discard it
- :param boolean keepBottom: True to keep the bottom, False or None to discard it
- :raises: ValueError if keepTop and keepBottom are both false.
- :raises: ValueError if there is not a solid in the current stack or the parent chain
- :returns: CQ object with the desired objects on the stack.
-
- The most common operation splits a solid and keeps one half. This sample creates
- split bushing::
-
- #drill a hole in the side
- c = Workplane().box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()F
- #now cut it in half sideways
- c.faces(">Y").workplane(-0.5).split(keepTop=True)
- """
-
- solid = self.findSolid()
-
- if (not keepTop) and (not keepBottom):
- raise ValueError("You have to keep at least one half")
-
- maxDim = solid.BoundingBox().DiagonalLength * 10.0
- topCutBox = self.rect(maxDim, maxDim)._extrude(maxDim)
- bottomCutBox = self.rect(maxDim, maxDim)._extrude(-maxDim)
-
- top = solid.cut(bottomCutBox)
- bottom = solid.cut(topCutBox)
-
- if keepTop and keepBottom:
- # Put both on the stack, leave original unchanged.
- return self.newObject([top, bottom])
- else:
- # Put the one we are keeping on the stack, and also update the
- # context solidto the one we kept.
- if keepTop:
- solid.wrapped = top.wrapped
- return self.newObject([top])
- else:
- solid.wrapped = bottom.wrapped
- return self.newObject([bottom])
-
- def combineSolids(self, otherCQToCombine=None):
- """
- !!!DEPRECATED!!! use union()
- Combines all solids on the current stack, and any context object, together
- into a single object.
-
- After the operation, the returned solid is also the context solid.
-
- :param otherCQToCombine: another CadQuery to combine.
- :return: a cQ object with the resulting combined solid on the stack.
-
- Most of the time, both objects will contain a single solid, which is
- combined and returned on the stack of the new object.
- """
- #loop through current stack objects, and combine them
- #TODO: combine other types of objects as well, like edges and wires
- toCombine = self.solids().vals()
-
- if otherCQToCombine:
- for obj in otherCQToCombine.solids().vals():
- toCombine.append(obj)
-
- if len(toCombine) < 1:
- raise ValueError("Cannot Combine: at least one solid required!")
-
- #get context solid and we don't want to find our own objects
- ctxSolid = self.findSolid(searchStack=False, searchParents=True)
-
- if ctxSolid is None:
- ctxSolid = toCombine.pop(0)
-
- #now combine them all. make sure to save a reference to the ctxSolid pointer!
- s = ctxSolid
- for tc in toCombine:
- s = s.fuse(tc)
-
- ctxSolid.wrapped = s.wrapped
- return self.newObject([s])
-
- def all(self):
- """
- Return a list of all CQ objects on the stack.
-
- useful when you need to operate on the elements
- individually.
-
- Contrast with vals, which returns the underlying
- objects for all of the items on the stack
- """
- return [self.newObject([o]) for o in self.objects]
-
- def size(self):
- """
- Return the number of objects currently on the stack
- """
- return len(self.objects)
-
- def vals(self):
- """
- get the values in the current list
-
- :rtype: list of FreeCAD objects
- :returns: the values of the objects on the stack.
-
- Contrast with :py:meth:`all`, which returns CQ objects for all of the items on the stack
- """
- return self.objects
-
- def add(self, obj):
- """
- Adds an object or a list of objects to the stack
-
- :param obj: an object to add
- :type obj: a CQ object, CAD primitive, or list of CAD primitives
- :return: a CQ object with the requested operation performed
-
- If an CQ object, the values of that object's stack are added. If a list of cad primitives,
- they are all added. If a single CAD primitive it is added
-
- Used in rare cases when you need to combine the results of several CQ results
- into a single CQ object. Shelling is one common example
- """
- if type(obj) == list:
- self.objects.extend(obj)
- elif type(obj) == CQ or type(obj) == Workplane:
- self.objects.extend(obj.objects)
- else:
- self.objects.append(obj)
- return self
-
- def val(self):
- """
- Return the first value on the stack
-
- :return: the first value on the stack.
- :rtype: A FreeCAD object or a SolidReference
- """
- return self.objects[0]
-
- def toFreecad(self):
- """
- Directly returns the wrapped FreeCAD object to cut down on the amount of boiler plate code
- needed when rendering a model in FreeCAD's 3D view.
- :return: The wrapped FreeCAD object
- :rtype A FreeCAD object or a SolidReference
- """
-
- return self.objects[0].wrapped
-
- def workplane(self, offset=0.0, invert=False, centerOption='CenterOfMass'):
- """
- Creates a new 2-D workplane, located relative to the first face on the stack.
-
- :param offset: offset for the work plane in the Z direction. Default
- :param invert: invert the Z direction from that of the face.
- :type offset: float or None=0.0
- :type invert: boolean or None=False
- :rtype: Workplane object ( which is a subclass of CQ )
-
- The first element on the stack must be a face, a set of
- co-planar faces or a vertex. If a vertex, then the parent
- item on the chain immediately before the vertex must be a
- face.
-
- The result will be a 2-d working plane
- with a new coordinate system set up as follows:
-
- * The origin will be located in the *center* of the
- face/faces, if a face/faces was selected. If a vertex was
- selected, the origin will be at the vertex, and located
- on the face.
- * The Z direction will be normal to the plane of the face,computed
- at the center point.
- * The X direction will be parallel to the x-y plane. If the workplane is parallel to
- the global x-y plane, the x direction of the workplane will co-incide with the
- global x direction.
-
- Most commonly, the selected face will be planar, and the workplane lies in the same plane
- of the face ( IE, offset=0). Occasionally, it is useful to define a face offset from
- an existing surface, and even more rarely to define a workplane based on a face that is
- not planar.
-
- To create a workplane without first having a face, use the Workplane() method.
-
- Future Enhancements:
- * Allow creating workplane from planar wires
- * Allow creating workplane based on an arbitrary point on a face, not just the center.
- For now you can work around by creating a workplane and then offsetting the center
- afterwards.
- """
- def _isCoPlanar(f0, f1):
- """Test if two faces are on the same plane."""
- p0 = f0.Center()
- p1 = f1.Center()
- n0 = f0.normalAt()
- n1 = f1.normalAt()
-
- # test normals (direction of planes)
- if not ((abs(n0.x-n1.x) < self.ctx.tolerance) or
- (abs(n0.y-n1.y) < self.ctx.tolerance) or
- (abs(n0.z-n1.z) < self.ctx.tolerance)):
- return False
-
- # test if p1 is on the plane of f0 (offset of planes)
- return abs(n0.dot(p0.sub(p1)) < self.ctx.tolerance)
-
- def _computeXdir(normal):
- """
- Figures out the X direction based on the given normal.
- :param :normal The direction that's normal to the plane.
- :type :normal A Vector
- :return A vector representing the X direction.
- """
- xd = Vector(0, 0, 1).cross(normal)
- if xd.Length < self.ctx.tolerance:
- #this face is parallel with the x-y plane, so choose x to be in global coordinates
- xd = Vector(1, 0, 0)
- return xd
-
- if len(self.objects) > 1:
- # are all objects 'PLANE'?
- if not all(o.geomType() == 'PLANE' for o in self.objects):
- raise ValueError("If multiple objects selected, they all must be planar faces.")
-
- # are all faces co-planar with each other?
- if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]):
- raise ValueError("Selected faces must be co-planar.")
-
- if centerOption == 'CenterOfMass':
- center = Shape.CombinedCenter(self.objects)
- elif centerOption == 'CenterOfBoundBox':
- center = Shape.CombinedCenterOfBoundBox(self.objects)
-
- normal = self.objects[0].normalAt()
- xDir = _computeXdir(normal)
-
- else:
- obj = self.objects[0]
-
- if isinstance(obj, Face):
- if centerOption == 'CenterOfMass':
- center = obj.Center()
- elif centerOption == 'CenterOfBoundBox':
- center = obj.CenterOfBoundBox()
- normal = obj.normalAt(center)
- xDir = _computeXdir(normal)
- else:
- if hasattr(obj, 'Center'):
- if centerOption == 'CenterOfMass':
- center = obj.Center()
- elif centerOption == 'CenterOfBoundBox':
- center = obj.CenterOfBoundBox()
- normal = self.plane.zDir
- xDir = self.plane.xDir
- else:
- raise ValueError("Needs a face or a vertex or point on a work plane")
-
- #invert if requested
- if invert:
- normal = normal.multiply(-1.0)
-
- #offset origin if desired
- offsetVector = normal.normalized().multiply(offset)
- offsetCenter = center.add(offsetVector)
-
- #make the new workplane
- plane = Plane(offsetCenter, xDir, normal)
- s = Workplane(plane)
- s.parent = self
- s.ctx = self.ctx
-
- #a new workplane has the center of the workplane on the stack
- return s
-
- def first(self):
- """
- Return the first item on the stack
- :returns: the first item on the stack.
- :rtype: a CQ object
- """
- return self.newObject(self.objects[0:1])
-
- def item(self, i):
- """
-
- Return the ith item on the stack.
- :rtype: a CQ object
- """
- return self.newObject([self.objects[i]])
-
- def last(self):
- """
- Return the last item on the stack.
- :rtype: a CQ object
- """
- return self.newObject([self.objects[-1]])
-
- def end(self):
- """
- Return the parent of this CQ element
- :rtype: a CQ object
- :raises: ValueError if there are no more parents in the chain.
-
- For example::
-
- CQ(obj).faces("+Z").vertices().end()
-
- will return the same as::
-
- CQ(obj).faces("+Z")
- """
- if self.parent:
- return self.parent
- else:
- raise ValueError("Cannot End the chain-- no parents!")
-
- def findSolid(self, searchStack=True, searchParents=True):
- """
- Finds the first solid object in the chain, searching from the current node
- backwards through parents until one is found.
-
- :param searchStack: should objects on the stack be searched first.
- :param searchParents: should parents be searched?
- :raises: ValueError if no solid is found in the current object or its parents,
- and errorOnEmpty is True
-
- This function is very important for chains that are modifying a single parent object,
- most often a solid.
-
- Most of the time, a chain defines or selects a solid, and then modifies it using workplanes
- or other operations.
-
- Plugin Developers should make use of this method to find the solid that should be modified,
- if the plugin implements a unary operation, or if the operation will automatically merge its
- results with an object already on the stack.
- """
- #notfound = ValueError("Cannot find a Valid Solid to Operate on!")
-
- if searchStack:
- for s in self.objects:
- if isinstance(s, Solid):
- return s
- elif isinstance(s, Compound):
- return s.Solids()
-
- if searchParents and self.parent is not None:
- return self.parent.findSolid(searchStack=True, searchParents=searchParents)
-
- return None
-
- def _selectObjects(self, objType, selector=None):
- """
- Filters objects of the selected type with the specified selector,and returns results
-
- :param objType: the type of object we are searching for
- :type objType: string: (Vertex|Edge|Wire|Solid|Shell|Compound|CompSolid)
- :return: a CQ object with the selected objects on the stack.
-
- **Implementation Note**: This is the base implementation of the vertices,edges,faces,
- solids,shells, and other similar selector methods. It is a useful extension point for
- plugin developers to make other selector methods.
- """
- # A single list of all faces from all objects on the stack
- toReturn = self._collectProperty(objType)
-
- if selector is not None:
- if isinstance(selector, str) or isinstance(selector, unicode):
- selectorObj = selectors.StringSyntaxSelector(selector)
- else:
- selectorObj = selector
- toReturn = selectorObj.filter(toReturn)
-
- return self.newObject(toReturn)
-
- def vertices(self, selector=None):
- """
- Select the vertices of objects on the stack, optionally filtering the selection. If there
- are multiple objects on the stack, the vertices of all objects are collected and a list of
- all the distinct vertices is returned.
-
- :param selector:
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains the *distinct* vertices of *all* objects on the
- current stack, after being filtered by the selector, if provided
-
- If there are no vertices for any objects on the current stack, an empty CQ object
- is returned
-
- The typical use is to select the vertices of a single object on the stack. For example::
-
- Workplane().box(1,1,1).faces("+Z").vertices().size()
-
- returns 4, because the topmost face of cube will contain four vertices. While this::
-
- Workplane().box(1,1,1).faces().vertices().size()
-
- returns 8, because a cube has a total of 8 vertices
-
- **Note** Circles are peculiar, they have a single vertex at the center!
-
- :py:class:`StringSyntaxSelector`
-
- """
- return self._selectObjects('Vertices', selector)
-
- def faces(self, selector=None):
- """
- Select the faces of objects on the stack, optionally filtering the selection. If there are
- multiple objects on the stack, the faces of all objects are collected and a list of all the
- distinct faces is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* faces of *all* objects on
- the current stack, filtered by the provided selector.
-
- If there are no vertices for any objects on the current stack, an empty CQ object
- is returned.
-
- The typical use is to select the faces of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").size()
-
- returns 1, because a cube has one face with a normal in the +Z direction. Similarly::
-
- CQ(aCube).faces().size()
-
- returns 6, because a cube has a total of 6 faces, And::
-
- CQ(aCube).faces("|Z").size()
-
- returns 2, because a cube has 2 faces having normals parallel to the z direction
-
- See more about selectors HERE
- """
- return self._selectObjects('Faces', selector)
-
- def edges(self, selector=None):
- """
- Select the edges of objects on the stack, optionally filtering the selection. If there are
- multiple objects on the stack, the edges of all objects are collected and a list of all the
- distinct edges is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* edges of *all* objects on
- the current stack, filtered by the provided selector.
-
- If there are no edges for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the edges of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").edges().size()
-
- returns 4, because a cube has one face with a normal in the +Z direction. Similarly::
-
- CQ(aCube).edges().size()
-
- returns 12, because a cube has a total of 12 edges, And::
-
- CQ(aCube).edges("|Z").size()
-
- returns 4, because a cube has 4 edges parallel to the z direction
-
- See more about selectors HERE
- """
- return self._selectObjects('Edges', selector)
-
- def wires(self, selector=None):
- """
- Select the wires of objects on the stack, optionally filtering the selection. If there are
- multiple objects on the stack, the wires of all objects are collected and a list of all the
- distinct wires is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* wires of *all* objects on
- the current stack, filtered by the provided selector.
-
- If there are no wires for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the wires of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").wires().size()
-
- returns 1, because a face typically only has one outer wire
-
- See more about selectors HERE
- """
- return self._selectObjects('Wires', selector)
-
- def solids(self, selector=None):
- """
- Select the solids of objects on the stack, optionally filtering the selection. If there are
- multiple objects on the stack, the solids of all objects are collected and a list of all the
- distinct solids is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on
- the current stack, filtered by the provided selector.
-
- If there are no solids for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the a single object on the stack. For example::
-
- CQ(aCube).solids().size()
-
- returns 1, because a cube consists of one solid.
-
- It is possible for single CQ object ( or even a single CAD primitive ) to contain
- multiple solids.
-
- See more about selectors HERE
- """
- return self._selectObjects('Solids', selector)
-
- def shells(self, selector=None):
- """
- Select the shells of objects on the stack, optionally filtering the selection. If there are
- multiple objects on the stack, the shells of all objects are collected and a list of all the
- distinct shells is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on
- the current stack, filtered by the provided selector.
-
- If there are no shells for any objects on the current stack, an empty CQ object is returned
-
- Most solids will have a single shell, which represents the outer surface. A shell will
- typically be composed of multiple faces.
-
- See more about selectors HERE
- """
- return self._selectObjects('Shells', selector)
-
- def compounds(self, selector=None):
- """
- Select compounds on the stack, optionally filtering the selection. If there are multiple
- objects on the stack, they are collected and a list of all the distinct compounds
- is returned.
-
- :param selector: A selector
- :type selector: None, a Selector object, or a string selector expression.
- :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on
- the current stack, filtered by the provided selector.
-
- A compound contains multiple CAD primitives that resulted from a single operation, such as
- a union, cut, split, or fillet. Compounds can contain multiple edges, wires, or solids.
-
- See more about selectors HERE
- """
- return self._selectObjects('Compounds', selector)
-
- def toSvg(self, opts=None):
- """
- Returns svg text that represents the first item on the stack.
-
- for testing purposes.
-
- :param opts: svg formatting options
- :type opts: dictionary, width and height
- :return: a string that contains SVG that represents this item.
- """
- return exporters.getSVG(self.val().wrapped, opts)
-
- def exportSvg(self, fileName):
- """
- Exports the first item on the stack as an SVG file
-
- For testing purposes mainly.
-
- :param fileName: the filename to export
- :type fileName: String, absolute path to the file
- """
- exporters.exportSVG(self, fileName)
-
- def rotateAboutCenter(self, axisEndPoint, angleDegrees):
- """
- Rotates all items on the stack by the specified angle, about the specified axis
-
- The center of rotation is a vector starting at the center of the object on the stack,
- and ended at the specified point.
-
- :param axisEndPoint: the second point of axis of rotation
- :type axisEndPoint: a three-tuple in global coordinates
- :param angleDegrees: the rotation angle, in degrees
- :type angleDegrees: float
- :returns: a CQ object, with all items rotated.
-
- WARNING: This version returns the same cq object instead of a new one-- the
- old object is not accessible.
-
- Future Enhancements:
- * A version of this method that returns a transformed copy, rather than modifying
- the originals
- * This method doesnt expose a very good interface, because the axis of rotation
- could be inconsistent between multiple objects. This is because the beginning
- of the axis is variable, while the end is fixed. This is fine when operating on
- one object, but is not cool for multiple.
- """
-
- #center point is the first point in the vector
- endVec = Vector(axisEndPoint)
-
- def _rot(obj):
- startPt = obj.Center()
- endPt = startPt + endVec
- return obj.rotate(startPt, endPt, angleDegrees)
-
- return self.each(_rot, False)
-
- def rotate(self, axisStartPoint, axisEndPoint, angleDegrees):
- """
- Returns a copy of all of the items on the stack rotated through and angle around the axis
- of rotation.
-
- :param axisStartPoint: The first point of the axis of rotation
- :type axisStartPoint: a 3-tuple of floats
- :type axisEndPoint: The second point of the axis of rotation
- :type axisEndPoint: a 3-tuple of floats
- :param angleDegrees: the rotation angle, in degrees
- :type angleDegrees: float
- :returns: a CQ object
- """
- return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees)
- for o in self.objects])
-
- def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
- """
- Mirror a single CQ object. This operation is the same as in the FreeCAD PartWB's mirroring
-
- :param mirrorPlane: the plane to mirror about
- :type mirrorPlane: string, one of "XY", "YX", "XZ", "ZX", "YZ", "ZY" the planes
- :param basePointVector: the base point to mirror about
- :type basePointVector: tuple
- """
- newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
- return newS.first()
-
-
- def translate(self, vec):
- """
- Returns a copy of all of the items on the stack moved by the specified translation vector.
-
- :param tupleDistance: distance to move, in global coordinates
- :type tupleDistance: a 3-tuple of float
- :returns: a CQ object
- """
- return self.newObject([o.translate(vec) for o in self.objects])
-
-
- def shell(self, thickness):
- """
- Remove the selected faces to create a shell of the specified thickness.
-
- To shell, first create a solid, and *in the same chain* select the faces you wish to remove.
-
- :param thickness: a positive float, representing the thickness of the desired shell.
- Negative values shell inwards, positive values shell outwards.
- :raises: ValueError if the current stack contains objects that are not faces of a solid
- further up in the chain.
- :returns: a CQ object with the resulting shelled solid selected.
-
- This example will create a hollowed out unit cube, where the top most face is open,
- and all other walls are 0.2 units thick::
-
- Workplane().box(1,1,1).faces("+Z").shell(0.2)
-
- Shelling is one of the cases where you may need to use the add method to select several
- faces. For example, this example creates a 3-walled corner, by removing three faces
- of a cube::
-
- s = Workplane().box(1,1,1)
- s1 = s.faces("+Z")
- s1.add(s.faces("+Y")).add(s.faces("+X"))
- self.saveModel(s1.shell(0.2))
-
- This fairly yucky syntax for selecting multiple faces is planned for improvement
-
- **Note**: When sharp edges are shelled inwards, they remain sharp corners, but **outward**
- shells are automatically filleted, because an outward offset from a corner generates
- a radius.
-
-
- Future Enhancements:
- Better selectors to make it easier to select multiple faces
- """
- solidRef = self.findSolid()
-
- for f in self.objects:
- if type(f) != Face:
- raise ValueError("Shelling requires that faces be selected")
-
- s = solidRef.shell(self.objects, thickness)
- solidRef.wrapped = s.wrapped
- return self.newObject([s])
-
- def fillet(self, radius):
- """
- Fillets a solid on the selected edges.
-
- The edges on the stack are filleted. The solid to which the edges belong must be in the
- parent chain of the selected edges.
-
- :param radius: the radius of the fillet, must be > zero
- :type radius: positive float
- :raises: ValueError if at least one edge is not selected
- :raises: ValueError if the solid containing the edge is not in the chain
- :returns: cq object with the resulting solid selected.
-
- This example will create a unit cube, with the top edges filleted::
-
- s = Workplane().box(1,1,1).faces("+Z").edges().fillet(0.1)
- """
- # TODO: we will need much better edge selectors for this to work
- # TODO: ensure that edges selected actually belong to the solid in the chain, otherwise,
- # TODO: we segfault
-
- solid = self.findSolid()
-
- edgeList = self.edges().vals()
- if len(edgeList) < 1:
- raise ValueError("Fillets requires that edges be selected")
-
- s = solid.fillet(radius, edgeList)
- solid.wrapped = s.wrapped
- return self.newObject([s])
-
- def chamfer(self, length, length2=None):
- """
- Chamfers a solid on the selected edges.
-
- The edges on the stack are chamfered. The solid to which the
- edges belong must be in the parent chain of the selected
- edges.
-
- Optional parameter `length2` can be supplied with a different
- value than `length` for a chamfer that is shorter on one side
- longer on the other side.
-
- :param length: the length of the fillet, must be greater than zero
- :param length2: optional parameter for asymmetrical chamfer
- :type length: positive float
- :type length2: positive float
- :raises: ValueError if at least one edge is not selected
- :raises: ValueError if the solid containing the edge is not in the chain
- :returns: cq object with the resulting solid selected.
-
- This example will create a unit cube, with the top edges chamfered::
-
- s = Workplane("XY").box(1,1,1).faces("+Z").chamfer(0.1)
-
- This example will create chamfers longer on the sides::
-
- s = Workplane("XY").box(1,1,1).faces("+Z").chamfer(0.2, 0.1)
- """
- solid = self.findSolid()
-
- edgeList = self.edges().vals()
- if len(edgeList) < 1:
- raise ValueError("Chamfer requires that edges be selected")
-
- s = solid.chamfer(length, length2, edgeList)
-
- solid.wrapped = s.wrapped
- return self.newObject([s])
-
-
-class Workplane(CQ):
- """
- Defines a coordinate system in space, in which 2-d coordinates can be used.
-
- :param plane: the plane in which the workplane will be done
- :type plane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right)
- :param origin: the desired origin of the new workplane
- :type origin: a 3-tuple in global coordinates, or None to default to the origin
- :param obj: an object to use initially for the stack
- :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial
- stack value.
- :raises: ValueError if the provided plane is not a plane, a valid named workplane
- :return: A Workplane object, with coordinate system matching the supplied plane.
-
- The most common use is::
-
- s = Workplane("XY")
-
- After creation, the stack contains a single point, the origin of the underlying plane,
- and the *current point* is on the origin.
-
- .. note::
- You can also create workplanes on the surface of existing faces using
- :py:meth:`CQ.workplane`
- """
-
- FOR_CONSTRUCTION = 'ForConstruction'
-
- def __init__(self, inPlane, origin=(0, 0, 0), obj=None):
- """
- make a workplane from a particular plane
-
- :param inPlane: the plane in which the workplane will be done
- :type inPlane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right)
- :param origin: the desired origin of the new workplane
- :type origin: a 3-tuple in global coordinates, or None to default to the origin
- :param obj: an object to use initially for the stack
- :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial
- stack value.
- :raises: ValueError if the provided plane is not a plane, or one of XY|YZ|XZ
- :return: A Workplane object, with coordinate system matching the supplied plane.
-
- The most common use is::
-
- s = Workplane("XY")
-
- After creation, the stack contains a single point, the origin of the underlying plane, and
- the *current point* is on the origin.
- """
-
- if inPlane.__class__.__name__ == 'Plane':
- tmpPlane = inPlane
- elif isinstance(inPlane, str) or isinstance(inPlane, unicode):
- tmpPlane = Plane.named(inPlane, origin)
- else:
- tmpPlane = None
-
- if tmpPlane is None:
- raise ValueError(
- 'Provided value {} is not a valid work plane'.format(inPlane))
-
- self.obj = obj
- self.plane = tmpPlane
- self.firstPoint = None
- # Changed so that workplane has the center as the first item on the stack
- self.objects = [self.plane.origin]
- self.parent = None
- self.ctx = CQContext()
-
- 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: 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.origin = self.plane.toWorldCoords(offset)
- ns = self.newObject([p.origin])
- ns.plane = p
-
- return ns
-
- def newObject(self, objlist):
- """
- Create a new workplane object from this one.
-
- Overrides CQ.newObject, and should be used by extensions, plugins, and
- subclasses to create new objects.
-
- :param objlist: new objects to put on the stack
- :type objlist: a list of CAD primitives
- :return: a new Workplane object with the current workplane as a parent.
- """
-
- #copy the current state to the new object
- ns = Workplane("XY")
- ns.plane = self.plane
- ns.parent = self
- ns.objects = list(objlist)
- ns.ctx = self.ctx
- return ns
-
- def _findFromPoint(self, useLocalCoords=False):
- """
- Finds the start point for an operation when an existing point
- is implied. Examples include 2d operations such as lineTo,
- which allows specifying the end point, and implicitly use the
- end of the previous line as the starting point
-
- :return: a Vector representing the point to use, or none if
- such a point is not available.
-
- :param useLocalCoords: selects whether the point is returned
- in local coordinates or global coordinates.
-
- The algorithm is this:
- * If an Edge is on the stack, its end point is used.yp
- * if a vector is on the stack, it is used
-
- WARNING: only the last object on the stack is used.
-
- NOTE:
- """
- obj = self.objects[-1]
-
- if isinstance(obj, Edge):
- p = obj.endPoint()
- elif isinstance(obj, Vector):
- p = obj
- else:
- raise RuntimeError("Cannot convert object type '%s' to vector " % type(obj))
-
- if useLocalCoords:
- return self.plane.toLocalCoords(p)
- else:
- return p
-
- def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True):
- """
- Creates an array of points and pushes them onto the stack.
- If you want to position the array at another point, create another workplane
- that is shifted to the position you would like to use as a reference
-
- :param xSpacing: spacing between points in the x direction ( must be > 0)
- :param ySpacing: spacing between points in the y direction ( must be > 0)
- :param xCount: number of points ( > 0 )
- :param yCount: number of points ( > 0 )
- :param center: if true, the array will be centered at the center of the workplane. if
- false, the lower left corner will be at the center of the work plane
- """
-
- 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
- for x in range(xCount):
- for y in range(yCount):
- lpoints.append((xSpacing * x, ySpacing * y))
-
- #shift points down and left relative to origin if requested
- if center:
- xc = xSpacing*(xCount-1) * 0.5
- yc = ySpacing*(yCount-1) * 0.5
- cpoints = []
- for p in lpoints:
- cpoints.append((p[0] - xc, p[1] - yc))
- lpoints = list(cpoints)
-
- return self.pushPoints(lpoints)
-
- def pushPoints(self, pntList):
- """
- Pushes a list of points onto the stack as vertices.
- The points are in the 2-d coordinate space of the workplane face
-
- :param pntList: a list of points to push onto the stack
- :type pntList: list of 2-tuples, in *local* coordinates
- :return: a new workplane with the desired points on the stack.
-
- A common use is to provide a list of points for a subsequent operation, such as creating
- circles or holes. This example creates a cube, and then drills three holes through it,
- based on three points::
-
- s = Workplane().box(1,1,1).faces(">Z").workplane().\
- pushPoints([(-0.3,0.3),(0.3,0.3),(0,0)])
- body = s.circle(0.05).cutThruAll()
-
- Here the circle function operates on all three points, and is then extruded to create three
- holes. See :py:meth:`circle` for how it works.
- """
- vecs = []
- for pnt in pntList:
- vec = self.plane.toWorldCoords(pnt)
- vecs.append(vec)
-
- return self.newObject(vecs)
-
- def center(self, x, y):
- """
- Shift local coordinates to the specified location.
-
- The location is specified in terms of local coordinates.
-
- :param float x: the new x location
- :param float y: the new y location
- :returns: the workplane object, with the center adjusted.
-
- The current point is set to the new center.
- This method is useful to adjust the center point after it has been created automatically on
- a face, but not where you'd like it to be.
-
- In this example, we adjust the workplane center to be at the corner of a cube, instead of
- the center of a face, which is the default::
-
- #this workplane is centered at x=0.5,y=0.5, the center of the upper face
- s = Workplane().box(1,1,1).faces(">Z").workplane()
-
- s.center(-0.5,-0.5) # move the center to the corner
- t = s.circle(0.25).extrude(0.2)
- assert ( t.faces().size() == 9 ) # a cube with a cylindrical nub at the top right corner
-
- The result is a cube with a round boss on the corner
- """
- "Shift local coordinates to the specified location, according to current coordinates"
- self.plane.setOrigin2d(x, y)
- n = self.newObject([self.plane.origin])
- return n
-
- def lineTo(self, x, y, forConstruction=False):
- """
- Make a line from the current point to the provided point
-
- :param float x: the x point, in workplane plane coordinates
- :param float y: the y point, in workplane plane coordinates
- :return: the Workplane object with the current point at the end of the new line
-
- see :py:meth:`line` if you want to use relative dimensions to make a line instead.
- """
- startPoint = self._findFromPoint(False)
-
- endPoint = self.plane.toWorldCoords((x, y))
-
- p = Edge.makeLine(startPoint, endPoint)
-
- if not forConstruction:
- self._addPendingEdge(p)
-
- return self.newObject([p])
-
- # line a specified incremental amount from current point
- def line(self, xDist, yDist, forConstruction=False):
- """
- Make a line from the current point to the provided point, using
- dimensions relative to the current point
-
- :param float xDist: x distance from current point
- :param float yDist: y distance from current point
- :return: the workplane object with the current point at the end of the new line
-
- see :py:meth:`lineTo` if you want to use absolute coordinates to make a line instead.
- """
- p = self._findFromPoint(True) # return local coordinates
- return self.lineTo(p.x + xDist, yDist + p.y, forConstruction)
-
- def vLine(self, distance, forConstruction=False):
- """
- Make a vertical line from the current point the provided distance
-
- :param float distance: (y) distance from current point
- :return: the workplane object with the current point at the end of the new line
- """
- return self.line(0, distance, forConstruction)
-
- def hLine(self, distance, forConstruction=False):
- """
- Make a horizontal line from the current point the provided distance
-
- :param float distance: (x) distance from current point
- :return: the Workplane object with the current point at the end of the new line
- """
- return self.line(distance, 0, forConstruction)
-
- def vLineTo(self, yCoord, forConstruction=False):
- """
- Make a vertical line from the current point to the provided y coordinate.
-
- Useful if it is more convenient to specify the end location rather than distance,
- as in :py:meth:`vLine`
-
- :param float yCoord: y coordinate for the end of the line
- :return: the Workplane object with the current point at the end of the new line
- """
- p = self._findFromPoint(True)
- return self.lineTo(p.x, yCoord, forConstruction)
-
- def hLineTo(self, xCoord, forConstruction=False):
- """
- Make a horizontal line from the current point to the provided x coordinate.
-
- Useful if it is more convenient to specify the end location rather than distance,
- as in :py:meth:`hLine`
-
- :param float xCoord: x coordinate for the end of the line
- :return: the Workplane object with the current point at the end of the new line
- """
- p = self._findFromPoint(True)
- return self.lineTo(xCoord, p.y, forConstruction)
-
- #absolute move in current plane, not drawing
- def moveTo(self, x=0, y=0):
- """
- Move to the specified point, without drawing.
-
- :param x: desired x location, in local coordinates
- :type x: float, or none for zero
- :param y: desired y location, in local coordinates
- :type y: float, or none for zero.
-
- Not to be confused with :py:meth:`center`, which moves the center of the entire
- workplane, this method only moves the current point ( and therefore does not affect objects
- already drawn ).
-
- See :py:meth:`move` to do the same thing but using relative dimensions
- """
- newCenter = Vector(x, y, 0)
- return self.newObject([self.plane.toWorldCoords(newCenter)])
-
- #relative move in current plane, not drawing
- def move(self, xDist=0, yDist=0):
- """
- Move the specified distance from the current point, without drawing.
-
- :param xDist: desired x distance, in local coordinates
- :type xDist: float, or none for zero
- :param yDist: desired y distance, in local coordinates
- :type yDist: float, or none for zero.
-
- Not to be confused with :py:meth:`center`, which moves the center of the entire
- workplane, this method only moves the current point ( and therefore does not affect objects
- already drawn ).
-
- See :py:meth:`moveTo` to do the same thing but using absolute coordinates
- """
- p = self._findFromPoint(True)
- newCenter = p + Vector(xDist, yDist, 0)
- return self.newObject([self.plane.toWorldCoords(newCenter)])
-
- def spline(self, listOfXYTuple, forConstruction=False):
- """
- Create a spline interpolated through the provided points.
-
- :param listOfXYTuple: points to interpolate through
- :type listOfXYTuple: list of 2-tuple
- :return: a Workplane object with the current point at the end of the spline
-
- The spline will begin at the current point, and
- end with the last point in the XY tuple list
-
- This example creates a block with a spline for one side::
-
- s = Workplane(Plane.XY())
- sPnts = [
- (2.75,1.5),
- (2.5,1.75),
- (2.0,1.5),
- (1.5,1.0),
- (1.0,1.25),
- (0.5,1.0),
- (0,1.0)
- ]
- r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close()
- r = r.extrude(0.5)
-
- *WARNING* It is fairly easy to create a list of points
- that cannot be correctly interpreted as a spline.
-
- Future Enhancements:
- * provide access to control points
- """
- gstartPoint = self._findFromPoint(False)
- gEndPoint = self.plane.toWorldCoords(listOfXYTuple[-1])
-
- vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple]
- allPoints = [gstartPoint] + vecs
-
- e = Edge.makeSpline(allPoints)
-
- if not forConstruction:
- self._addPendingEdge(e)
-
- return self.newObject([e])
-
- def threePointArc(self, point1, point2, forConstruction=False):
- """
- Draw an arc from the current point, through point1, and ending at point2
-
- :param point1: point to draw through
- :type point1: 2-tuple, in workplane coordinates
- :param point2: end point for the arc
- :type point2: 2-tuple, in workplane coordinates
- :return: a workplane with the current point at the end of the arc
-
- Future Enhancements:
- provide a version that allows an arc using relative measures
- provide a centerpoint arc
- provide tangent arcs
- """
-
- gstartPoint = self._findFromPoint(False)
- gpoint1 = self.plane.toWorldCoords(point1)
- gpoint2 = self.plane.toWorldCoords(point2)
-
- arc = Edge.makeThreePointArc(gstartPoint, gpoint1, gpoint2)
-
- if not forConstruction:
- self._addPendingEdge(arc)
-
- return self.newObject([arc])
-
- def rotateAndCopy(self, matrix):
- """
- Makes a copy of all edges on the stack, rotates them according to the
- provided matrix, and then attempts to consolidate them into a single wire.
-
- :param matrix: a 4xr transformation matrix, in global coordinates
- :type matrix: a FreeCAD Base.Matrix object
- :return: a CadQuery object with consolidated wires, and any originals on the stack.
-
- The most common use case is to create a set of open edges, and then mirror them
- around either the X or Y axis to complete a closed shape.
-
- see :py:meth:`mirrorX` and :py:meth:`mirrorY` to mirror about the global X and Y axes
- see :py:meth:`mirrorX` and for an example
-
- Future Enhancements:
- faster implementation: this one transforms 3 times to accomplish the result
- """
-
- #convert edges to a wire, if there are pending edges
- n = self.wire(forConstruction=False)
-
- #attempt to consolidate wires together.
- consolidated = n.consolidateWires()
-
- rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(), matrix)
-
- for w in rotatedWires:
- consolidated.objects.append(w)
- consolidated._addPendingWire(w)
-
- #attempt again to consolidate all of the wires
- c = consolidated.consolidateWires()
-
- return c
-
- def mirrorY(self):
- """
- Mirror entities around the y axis of the workplane plane.
-
- :return: a new object with any free edges consolidated into as few wires as possible.
-
- All free edges are collected into a wire, and then the wire is mirrored,
- and finally joined into a new wire
-
- Typically used to make creating wires with symmetry easier. This line of code::
-
- s = Workplane().lineTo(2,2).threePointArc((3,1),(2,0)).mirrorX().extrude(0.25)
-
- Produces a flat, heart shaped object
-
- Future Enhancements:
- mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
- """
- tm = Matrix()
- tm.rotateY(math.pi)
- return self.rotateAndCopy(tm)
-
- def mirrorX(self):
- """
- Mirror entities around the x axis of the workplane plane.
-
- :return: a new object with any free edges consolidated into as few wires as possible.
-
- All free edges are collected into a wire, and then the wire is mirrored,
- and finally joined into a new wire
-
- Typically used to make creating wires with symmetry easier.
-
- Future Enhancements:
- mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
- """
- tm = Matrix()
- tm.rotateX(math.pi)
- return self.rotateAndCopy(tm)
-
- def _addPendingEdge(self, edge):
- """
- Queues an edge for later combination into a wire.
-
- :param edge:
- :return:
- """
- self.ctx.pendingEdges.append(edge)
-
- if self.ctx.firstPoint is None:
- self.ctx.firstPoint = self.plane.toLocalCoords(edge.startPoint())
-
- def _addPendingWire(self, wire):
- """
- Queue a Wire for later extrusion
-
- Internal Processing Note. In FreeCAD, edges-->wires-->faces-->solids.
-
- but users do not normally care about these distinctions. Users 'think' in terms
- of edges, and solids.
-
- CadQuery tracks edges as they are drawn, and automatically combines them into wires
- when the user does an operation that needs it.
-
- Similarly, cadQuery tracks pending wires, and automatically combines them into faces
- when necessary to make a solid.
- """
- self.ctx.pendingWires.append(wire)
-
- def consolidateWires(self):
- """
- Attempt to consolidate wires on the stack into a single.
- If possible, a new object with the results are returned.
- if not possible, the wires remain separated
-
- FreeCAD has a bug in Part.Wire([]) which does not create wires/edges properly sometimes
- Additionally, it has a bug where a profile composed of two wires ( rather than one )
- also does not work properly. Together these are a real problem.
- """
- wires = self.wires().vals()
- if len(wires) < 2:
- return self
-
- #TODO: this makes the assumption that either all wires could be combined, or none.
- #in reality trying each combination of wires is probably not reasonable anyway
- w = Wire.combine(wires)
-
- #ok this is a little tricky. if we consolidate wires, we have to actually
- #modify the pendingWires collection to remove the original ones, and replace them
- #with the consolidate done
- #since we are already assuming that all wires could be consolidated, its easy, we just
- #clear the pending wire list
- r = self.newObject([w])
- r.ctx.pendingWires = []
- r._addPendingWire(w)
- return r
-
- def wire(self, forConstruction=False):
- """
- Returns a CQ object with all pending edges connected into a wire.
-
- All edges on the stack that can be combined will be combined into a single wire object,
- and other objects will remain on the stack unmodified
-
- :param forConstruction: whether the wire should be used to make a solid, or if it is just
- for reference
- :type forConstruction: boolean. true if the object is only for reference
-
- This method is primarily of use to plugin developers making utilities for 2-d construction.
- This method should be called when a user operation implies that 2-d construction is
- finished, and we are ready to begin working in 3d
-
- SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles
- edges, wires, etc
-
- Any non edges will still remain.
- """
-
- edges = self.ctx.pendingEdges
-
- #do not consolidate if there are no free edges
- if len(edges) == 0:
- return self
-
- self.ctx.pendingEdges = []
-
- others = []
- for e in self.objects:
- if type(e) != Edge:
- others.append(e)
-
-
- w = Wire.assembleEdges(edges)
- if not forConstruction:
- self._addPendingWire(w)
-
- return self.newObject(others + [w])
-
- def each(self, callBackFunction, useLocalCoordinates=False):
- """
- Runs the provided function on each value in the stack, and collects the return values into
- a new CQ object.
-
- Special note: a newly created workplane always has its center point as its only stack item
-
- :param callBackFunction: the function to call for each item on the current stack.
- :param useLocalCoordinates: should values be converted from local coordinates first?
- :type useLocalCoordinates: boolean
-
- The callback function must accept one argument, which is the item on the stack, and return
- one object, which is collected. If the function returns None, nothing is added to the stack.
- The object passed into the callBackFunction is potentially transformed to local coordinates,
- if useLocalCoordinates is true
-
- useLocalCoordinates is very useful for plugin developers.
-
- If false, the callback function is assumed to be working in global coordinates. Objects
- created are added as-is, and objects passed into the function are sent in using global
- coordinates
-
- If true, the calling function is assumed to be working in local coordinates. Objects are
- transformed to local coordinates before they are passed into the callback method, and result
- objects are transformed to global coordinates after they are returned.
-
- This allows plugin developers to create objects in local coordinates, without worrying
- about the fact that the working plane is different than the global coordinate system.
-
-
- TODO: wrapper object for Wire will clean up forConstruction flag everywhere
- """
- results = []
- for obj in self.objects:
-
- if useLocalCoordinates:
- #TODO: this needs to work for all types of objects, not just vectors!
- r = callBackFunction(self.plane.toLocalCoords(obj))
- r = r.transformShape(self.plane.rG)
- else:
- r = callBackFunction(obj)
-
- if type(r) == Wire:
- if not r.forConstruction:
- self._addPendingWire(r)
-
- results.append(r)
-
- return self.newObject(results)
-
- def eachpoint(self, callbackFunction, useLocalCoordinates=False):
- """
- Same as each(), except each item on the stack is converted into a point before it
- is passed into the callback function.
-
- :return: CadQuery object which contains a list of vectors (points ) on its stack.
-
- :param useLocalCoordinates: should points be in local or global coordinates
- :type useLocalCoordinates: boolean
-
- The resulting object has a point on the stack for each object on the original stack.
- Vertices and points remain a point. Faces, Wires, Solids, Edges, and Shells are converted
- to a point by using their center of mass.
-
- If the stack has zero length, a single point is returned, which is the center of the current
- workplane/coordinate system
- """
- #convert stack to a list of points
- pnts = []
- if len(self.objects) == 0:
- #nothing on the stack. here, we'll assume we should operate with the
- #origin as the context point
- pnts.append(self.plane.origin)
- else:
-
- for v in self.objects:
- pnts.append(v.Center())
-
- return self.newObject(pnts).each(callbackFunction, useLocalCoordinates)
-
- def rect(self, xLen, yLen, centered=True, forConstruction=False):
- """
- Make a rectangle for each item on the stack.
-
- :param xLen: length in xDirection ( in workplane coordinates )
- :type xLen: float > 0
- :param yLen: length in yDirection ( in workplane coordinates )
- :type yLen: float > 0
- :param boolean centered: true if the rect is centered on the reference point, false if the
- lower-left is on the reference point
- :param forConstruction: should the new wires be reference geometry only?
- :type forConstruction: true if the wires are for reference, false if they are creating part
- geometry
- :return: a new CQ object with the created wires on the stack
-
- A common use case is to use a for-construction rectangle to define the centers of a hole
- pattern::
-
- s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25)
-
- Creates 4 circles at the corners of a square centered on the origin.
-
- Future Enhancements:
- better way to handle forConstruction
- project points not in the workplane plane onto the workplane plane
- """
- def makeRectangleWire(pnt):
- # Here pnt is in local coordinates due to useLocalCoords=True
- # (xc,yc,zc) = pnt.toTuple()
- if centered:
- p1 = pnt.add(Vector(xLen/-2.0, yLen/-2.0, 0))
- p2 = pnt.add(Vector(xLen/2.0, yLen/-2.0, 0))
- p3 = pnt.add(Vector(xLen/2.0, yLen/2.0, 0))
- p4 = pnt.add(Vector(xLen/-2.0, yLen/2.0, 0))
- else:
- p1 = pnt
- p2 = pnt.add(Vector(xLen, 0, 0))
- p3 = pnt.add(Vector(xLen, yLen, 0))
- p4 = pnt.add(Vector(0, yLen, 0))
-
- w = Wire.makePolygon([p1, p2, p3, p4, p1], forConstruction)
- return w
- #return Part.makePolygon([p1,p2,p3,p4,p1])
-
- return self.eachpoint(makeRectangleWire, True)
-
- #circle from current point
- def circle(self, radius, forConstruction=False):
- """
- Make a circle for each item on the stack.
-
- :param radius: radius of the circle
- :type radius: float > 0
- :param forConstruction: should the new wires be reference geometry only?
- :type forConstruction: true if the wires are for reference, false if they are creating
- part geometry
- :return: a new CQ object with the created wires on the stack
-
- A common use case is to use a for-construction rectangle to define the centers of a
- hole pattern::
-
- s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25)
-
- Creates 4 circles at the corners of a square centered on the origin. Another common case is
- to use successive circle() calls to create concentric circles. This works because the
- center of a circle is its reference point::
-
- s = Workplane().circle(2.0).circle(1.0)
-
- Creates two concentric circles, which when extruded will form a ring.
-
- Future Enhancements:
- better way to handle forConstruction
- project points not in the workplane plane onto the workplane plane
-
- """
- def makeCircleWire(obj):
- cir = Wire.makeCircle(radius, obj, Vector(0, 0, 1))
- cir.forConstruction = forConstruction
- return cir
-
- return self.eachpoint(makeCircleWire, useLocalCoordinates=True)
-
- def polygon(self, nSides, diameter, forConstruction=False):
- """
- Creates a polygon inscribed in a circle of the specified diameter for each point on
- the stack
-
- The first vertex is always oriented in the x direction.
-
- :param nSides: number of sides, must be > 3
- :param diameter: the size of the circle the polygon is inscribed into
- :return: a polygon wire
- """
- def _makePolygon(center):
- #pnt is a vector in local coordinates
- angle = 2.0 * math.pi / nSides
- pnts = []
- for i in range(nSides+1):
- pnts.append(center + Vector((diameter / 2.0 * math.cos(angle*i)),
- (diameter / 2.0 * math.sin(angle*i)), 0))
- return Wire.makePolygon(pnts, forConstruction)
-
- return self.eachpoint(_makePolygon, True)
-
- def polyline(self, listOfXYTuple, forConstruction=False):
- """
- Create a polyline from a list of points
-
- :param listOfXYTuple: a list of points in Workplane coordinates
- :type listOfXYTuple: list of 2-tuples
- :param forConstruction: whether or not the edges are used for reference
- :type forConstruction: true if the edges are for reference, false if they are for creating geometry
- part geometry
- :return: a new CQ object with a list of edges on the stack
-
- *NOTE* most commonly, the resulting wire should be closed.
- """
-
- # Our list of new edges that will go into a new CQ object
- edges = []
-
- # The very first startPoint comes from our original object, but not after that
- startPoint = self._findFromPoint(False)
-
- # Draw a line for each set of points, starting from the from-point of the original CQ object
- for curTuple in listOfXYTuple:
- endPoint = self.plane.toWorldCoords(curTuple)
-
- edges.append(Edge.makeLine(startPoint, endPoint))
-
- # We need to move the start point for the next line that we draw or we get stuck at the same startPoint
- startPoint = endPoint
-
- if not forConstruction:
- self._addPendingEdge(edges[-1])
-
- return self.newObject(edges)
-
- def close(self):
- """
- End 2-d construction, and attempt to build a closed wire.
-
- :return: a CQ object with a completed wire on the stack, if possible.
-
- After 2-d drafting with lineTo,threePointArc, and polyline, it is necessary
- to convert the edges produced by these into one or more wires.
-
- When a set of edges is closed, cadQuery assumes it is safe to build the group of edges
- into a wire. This example builds a simple triangular prism::
-
- s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2)
- """
- self.lineTo(self.ctx.firstPoint.x, self.ctx.firstPoint.y)
-
- # Need to reset the first point after closing a wire
- self.ctx.firstPoint=None
-
- return self.wire()
-
- def largestDimension(self):
- """
- Finds the largest dimension in the stack.
- Used internally to create thru features, this is how you can compute
- how long or wide a feature must be to make sure to cut through all of the material
- :return: A value representing the largest dimension of the first solid on the stack
- """
- #TODO: this implementation is naive and returns the dims of the first solid... most of
- #TODO: the time this works. but a stronger implementation would be to search all solids.
- s = self.findSolid()
- if s:
- return s.BoundingBox().DiagonalLength * 5.0
- else:
- return -1
-
- def cutEach(self, fcn, useLocalCoords=False, clean=True):
- """
- Evaluates the provided function at each point on the stack (ie, eachpoint)
- and then cuts the result from the context solid.
- :param fcn: a function suitable for use in the eachpoint method: ie, that accepts a vector
- :param useLocalCoords: same as for :py:meth:`eachpoint`
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object that contains the resulting solid
- :raises: an error if there is not a context solid to cut from
- """
- ctxSolid = self.findSolid()
- if ctxSolid is None:
- raise ValueError("Must have a solid in the chain to cut from!")
-
- #will contain all of the counterbores as a single compound
- results = self.eachpoint(fcn, useLocalCoords).vals()
- s = ctxSolid
- for cb in results:
- s = s.cut(cb)
-
- if clean: s = s.clean()
-
- ctxSolid.wrapped = s.wrapped
- return self.newObject([s])
-
- #but parameter list is different so a simple function pointer wont work
- def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None, clean=True):
- """
- Makes a counterbored hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param cboreDiameter: the diameter of the cbore
- :type cboreDiameter: float > 0 and > diameter
- :param cboreDepth: depth of the counterbore
- :type cboreDepth: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane plane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().cboreHole(0.125, 0.25,0.125,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. Counterbored holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cskHole` to make countersinks instead of counterbores
- """
- if depth is None:
- depth = self.largestDimension()
-
- def _makeCbore(center):
- """
- Makes a single hole with counterbore at the supplied point
- returns a solid suitable for subtraction
- pnt is in local coordinates
- """
- boreDir = Vector(0, 0, -1)
- #first make the hole
- hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coordianates!
-
- #add the counter bore
- cbore = Solid.makeCylinder(cboreDiameter / 2.0, cboreDepth, center, boreDir)
- r = hole.fuse(cbore)
- return r
-
- return self.cutEach(_makeCbore, True, clean)
-
- #TODO: almost all code duplicated!
- #but parameter list is different so a simple function pointer wont work
- def cskHole(self, diameter, cskDiameter, cskAngle, depth=None, clean=True):
- """
- Makes a countersunk hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param cskDiameter: the diameter of the countersink
- :type cskDiameter: float > 0 and > diameter
- :param cskAngle: angle of the countersink, in degrees ( 82 is common )
- :type cskAngle: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().cskHole(0.125, 0.25,82,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cboreHole` to make counterbores instead of countersinks
- """
-
- if depth is None:
- depth = self.largestDimension()
-
- def _makeCsk(center):
- #center is in local coordinates
-
- boreDir = Vector(0, 0, -1)
-
- #first make the hole
- hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coords!
- r = cskDiameter / 2.0
- h = r / math.tan(math.radians(cskAngle / 2.0))
- csk = Solid.makeCone(r, 0.0, h, center, boreDir)
- r = hole.fuse(csk)
- return r
-
- return self.cutEach(_makeCsk, True, clean)
-
- #TODO: almost all code duplicated!
- #but parameter list is different so a simple function pointer wont work
- def hole(self, diameter, depth=None, clean=True):
- """
- Makes a hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().hole(0.125, 0.25,82,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cboreHole` and :py:meth:`cskHole` to make counterbores or countersinks
- """
- if depth is None:
- depth = self.largestDimension()
-
- def _makeHole(center):
- """
- Makes a single hole with counterbore at the supplied point
- returns a solid suitable for subtraction
- pnt is in local coordinates
- """
- boreDir = Vector(0, 0, -1)
- #first make the hole
- hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates!
- return hole
-
- return self.cutEach(_makeHole, True, clean)
-
- #TODO: duplicated code with _extrude and extrude
- def twistExtrude(self, distance, angleDegrees, combine=True, clean=True):
- """
- Extrudes a wire in the direction normal to the plane, but also twists by the specified
- angle over the length of the extrusion
-
- The center point of the rotation will be the center of the workplane
-
- See extrude for more details, since this method is the same except for the the addition
- of the angle. In fact, if angle=0, the result is the same as a linear extrude.
-
- **NOTE** This method can create complex calculations, so be careful using it with
- complex geometries
-
- :param distance: the distance to extrude normal to the workplane
- :param angle: angline ( in degrees) to rotate through the extrusion
- :param boolean combine: True to combine the resulting solid with parent solids if found.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
- """
- #group wires together into faces based on which ones are inside the others
- #result is a list of lists
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
-
- self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
-
- #compute extrusion vector and extrude
- eDir = self.plane.zDir.multiply(distance)
-
- #one would think that fusing faces into a compound and then extruding would work,
- #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
- #but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there
- # are multiple sets
- r = None
- for ws in wireSets:
- thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin,
- eDir, angleDegrees)
- if r is None:
- r = thisObj
- else:
- r = r.fuse(thisObj)
-
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def extrude(self, distance, combine=True, clean=True, both=False):
- """
- Use all un-extruded wires in the parent chain to create a prismatic solid.
-
- :param distance: the distance to extrude, normal to the workplane plane
- :type distance: float, negative means opposite the normal direction
- :param boolean combine: True to combine the resulting solid with parent solids if found.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :param boolean both: extrude in both directions symmetrically
- :return: a CQ object with the resulting solid selected.
-
- extrude always *adds* material to a part.
-
- The returned object is always a CQ object, and depends on wither combine is True, and
- whether a context solid is already defined:
-
- * if combine is False, the new value is pushed onto the stack.
- * if combine is true, the value is combined with the context solid if it exists,
- and the resulting solid becomes the new context solid.
-
- FutureEnhancement:
- Support for non-prismatic extrusion ( IE, sweeping along a profile, not just
- perpendicular to the plane extrude to surface. this is quite tricky since the surface
- selected may not be planar
- """
- r = self._extrude(distance,both=both) # returns a Solid (or a compound if there were multiple)
-
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True):
- """
- Use all un-revolved wires in the parent chain to create a solid.
-
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :param combine: True to combine the resulting solid with parent solids if found.
- :type combine: boolean, combine with parent solid
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
-
- The returned object is always a CQ object, and depends on wither combine is True, and
- whether a context solid is already defined:
-
- * if combine is False, the new value is pushed onto the stack.
- * if combine is true, the value is combined with the context solid if it exists,
- and the resulting solid becomes the new context solid.
- """
- #Make sure we account for users specifying angles larger than 360 degrees
- angleDegrees %= 360.0
-
- #Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve
- angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees
-
- # The default start point of the vector defining the axis of rotation will be the origin
- # of the workplane
- if axisStart is None:
- axisStart = self.plane.toWorldCoords((0, 0)).toTuple()
- else:
- axisStart = self.plane.toWorldCoords(axisStart).toTuple()
-
- # The default end point of the vector defining the axis of rotation should be along the
- # normal from the plane
- if axisEnd is None:
- # Make sure we match the user's assumed axis of rotation if they specified an start
- # but not an end
- if axisStart[1] != 0:
- axisEnd = self.plane.toWorldCoords((0, axisStart[1])).toTuple()
- else:
- axisEnd = self.plane.toWorldCoords((0, 1)).toTuple()
- else:
- axisEnd = self.plane.toWorldCoords(axisEnd).toTuple()
-
- # returns a Solid (or a compound if there were multiple)
- r = self._revolve(angleDegrees, axisStart, axisEnd)
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def sweep(self, path, makeSolid=True, isFrenet=False, combine=True, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a swept solid.
-
- :param path: A wire along which the pending wires will be swept
- :param boolean combine: True to combine the resulting solid with parent solids if found.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
- """
-
- r = self._sweep(path.wire(), makeSolid, isFrenet) # returns a Solid (or a compound if there were multiple)
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def _combineWithBase(self, obj):
- """
- Combines the provided object with the base solid, if one can be found.
- :param obj:
- :return: a new object that represents the result of combining the base object with obj,
- or obj if one could not be found
- """
- baseSolid = self.findSolid(searchParents=True)
- r = obj
- if baseSolid is not None:
- r = baseSolid.fuse(obj)
- baseSolid.wrapped = r.wrapped
-
- return self.newObject([r])
-
- def combine(self, clean=True):
- """
- Attempts to combine all of the items on the stack into a single item.
- WARNING: all of the items must be of the same type!
-
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there are no items on the stack, or if they cannot be combined
- :return: a CQ object with the resulting object selected
- """
- items = list(self.objects)
- s = items.pop(0)
- for ss in items:
- s = s.fuse(ss)
-
- if clean: s = s.clean()
-
- return self.newObject([s])
-
- def union(self, toUnion=None, combine=True, clean=True):
- """
- Unions all of the items on the stack of toUnion with the current solid.
- If there is no current solid, the items in toUnion are unioned together.
- if combine=True, the result and the original are updated to point to the new object
- if combine=False, the result will be on the stack, but the original is unmodified
-
- :param toUnion:
- :type toUnion: a solid object, or a CQ object having a solid,
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there is no solid to add to in the chain
- :return: a CQ object with the resulting object selected
- """
-
- #first collect all of the items together
- if type(toUnion) == CQ or type(toUnion) == Workplane:
- solids = toUnion.solids().vals()
- if len(solids) < 1:
- raise ValueError("CQ object must have at least one solid on the stack to union!")
- newS = solids.pop(0)
- for s in solids:
- newS = newS.fuse(s)
- elif type(toUnion) == Solid:
- newS = toUnion
- else:
- raise ValueError("Cannot union type '{}'".format(type(toUnion)))
-
- #now combine with existing solid, if there is one
- # look for parents to cut from
- solidRef = self.findSolid(searchStack=True, searchParents=True)
- if combine and solidRef is not None:
- r = solidRef.fuse(newS)
- solidRef.wrapped = newS.wrapped
- else:
- r = newS
-
- if clean: r = r.clean()
-
- return self.newObject([r])
-
- def cut(self, toCut, combine=True, clean=True):
- """
- Cuts the provided solid from the current solid, IE, perform a solid subtraction
-
- if combine=True, the result and the original are updated to point to the new object
- if combine=False, the result will be on the stack, but the original is unmodified
-
- :param toCut: object to cut
- :type toCut: a solid object, or a CQ object having a solid,
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there is no solid to subtract from in the chain
- :return: a CQ object with the resulting object selected
- """
-
- # look for parents to cut from
- solidRef = self.findSolid(searchStack=True, searchParents=True)
-
- if solidRef is None:
- raise ValueError("Cannot find solid to cut from")
- solidToCut = None
- if type(toCut) == CQ or type(toCut) == Workplane:
- solidToCut = toCut.val()
- elif type(toCut) == Solid:
- solidToCut = toCut
- else:
- raise ValueError("Cannot cut type '{}'".format(type(toCut)))
-
- newS = solidRef.cut(solidToCut)
-
- if clean: newS = newS.clean()
-
- if combine:
- solidRef.wrapped = newS.wrapped
-
- return self.newObject([newS])
-
- def intersect(self, toIntersect, combine=True, clean=True):
- """
- Intersects the provided solid from the current solid.
-
- if combine=True, the result and the original are updated to point to the new object
- if combine=False, the result will be on the stack, but the original is unmodified
-
- :param toIntersect: object to intersect
- :type toIntersect: a solid object, or a CQ object having a solid,
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there is no solid to intersect with in the chain
- :return: a CQ object with the resulting object selected
- """
-
- # look for parents to intersect with
- solidRef = self.findSolid(searchStack=True, searchParents=True)
-
- if solidRef is None:
- raise ValueError("Cannot find solid to intersect with")
- solidToIntersect = None
-
- if isinstance(toIntersect, CQ):
- solidToIntersect = toIntersect.val()
- elif isinstance(toIntersect, Solid):
- solidToIntersect = toIntersect
- else:
- raise ValueError("Cannot intersect type '{}'".format(type(toIntersect)))
-
- newS = solidRef.intersect(solidToIntersect)
-
- if clean: newS = newS.clean()
-
- if combine:
- solidRef.wrapped = newS.wrapped
-
- return self.newObject([newS])
-
-
- def cutBlind(self, distanceToCut, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
-
- Similar to extrude, except that a solid in the parent chain is required to remove material
- from. cutBlind always removes material from a part.
-
- :param distanceToCut: distance to extrude before cutting
- :type distanceToCut: float, >0 means in the positive direction of the workplane normal,
- <0 means in the negative direction
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there is no solid to subtract from in the chain
- :return: a CQ object with the resulting object selected
-
- see :py:meth:`cutThruAll` to cut material from the entire part
-
- Future Enhancements:
- Cut Up to Surface
- """
- #first, make the object
- toCut = self._extrude(distanceToCut)
-
- #now find a solid in the chain
-
- solidRef = self.findSolid()
-
- s = solidRef.cut(toCut)
-
- if clean: s = s.clean()
-
- solidRef.wrapped = s.wrapped
- return self.newObject([s])
-
- def cutThruAll(self, positive=False, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
-
- Similar to extrude, except that a solid in the parent chain is required to remove material
- from. cutThruAll always removes material from a part.
-
- :param boolean positive: True to cut in the positive direction, false to cut in the
- negative direction
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there is no solid to subtract from in the chain
- :return: a CQ object with the resulting object selected
-
- see :py:meth:`cutBlind` to cut material to a limited depth
- """
- maxDim = self.largestDimension()
- if not positive:
- maxDim *= (-1.0)
-
- return self.cutBlind(maxDim, clean)
-
- def loft(self, filled=True, ruled=False, combine=True):
- """
- Make a lofted solid, through the set of wires.
- :return: a CQ object containing the created loft
- """
- wiresToLoft = self.ctx.pendingWires
- self.ctx.pendingWires = []
-
- r = Solid.makeLoft(wiresToLoft, ruled)
-
- if combine:
- parentSolid = self.findSolid(searchStack=False, searchParents=True)
- if parentSolid is not None:
- r = parentSolid.fuse(r)
- parentSolid.wrapped = r.wrapped
-
- return self.newObject([r])
-
- def _extrude(self, distance, both=False):
- """
- Make a prismatic solid from the existing set of pending wires.
-
- :param distance: distance to extrude
- :param boolean both: extrude in both directions symmetrically
- :return: a FreeCAD solid, suitable for boolean operations.
-
- This method is a utility method, primarily for plugin and internal use.
- It is the basis for cutBlind,extrude,cutThruAll, and all similar methods.
-
- Future Enhancements:
- extrude along a profile (sweep)
- """
-
- #group wires together into faces based on which ones are inside the others
- #result is a list of lists
- s = time.time()
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
- #print "sorted wires in %d sec" % ( time.time() - s )
- self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
-
- #compute extrusion vector and extrude
- eDir = self.plane.zDir.multiply(distance)
-
-
- #one would think that fusing faces into a compound and then extruding would work,
- #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
- #but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- # underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are
- # multiple sets
-
- # IMPORTANT NOTE: OCC is slow slow slow in boolean operations. So you do NOT want to fuse
- # each item to another and save the result-- instead, you want to combine all of the new
- # items into a compound, and fuse them together!!!
- # r = None
- # for ws in wireSets:
- # thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
- # if r is None:
- # r = thisObj
- # else:
- # s = time.time()
- # r = r.fuse(thisObj)
- # print "Fused in %0.3f sec" % ( time.time() - s )
- # return r
-
- toFuse = []
- for ws in wireSets:
- thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
- toFuse.append(thisObj)
-
- if both:
- thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir.multiply(-1.))
- toFuse.append(thisObj)
-
- return Compound.makeCompound(toFuse)
-
- def _revolve(self, angleDegrees, axisStart, axisEnd):
- """
- Make a solid from the existing set of pending wires.
-
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :return: a FreeCAD solid, suitable for boolean operations.
-
- This method is a utility method, primarily for plugin and internal use.
- """
- #We have to gather the wires to be revolved
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
-
- #Mark that all of the wires have been used to create a revolution
- self.ctx.pendingWires = []
-
- #Revolve the wires, make a compound out of them and then fuse them
- toFuse = []
- for ws in wireSets:
- thisObj = Solid.revolve(ws[0], ws[1:], angleDegrees, axisStart, axisEnd)
- toFuse.append(thisObj)
-
- return Compound.makeCompound(toFuse)
-
- def _sweep(self, path, makeSolid=True, isFrenet=False):
- """
- Makes a swept solid from an existing set of pending wires.
-
- :param path: A wire along which the pending wires will be swept
- :return:a FreeCAD solid, suitable for boolean operations
- """
-
- # group wires together into faces based on which ones are inside the others
- # result is a list of lists
- s = time.time()
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
- # print "sorted wires in %d sec" % ( time.time() - s )
- self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
-
- toFuse = []
- for ws in wireSets:
- thisObj = Solid.sweep(ws[0], ws[1:], path.val(), makeSolid, isFrenet)
- toFuse.append(thisObj)
-
- return Compound.makeCompound(toFuse)
-
- def box(self, length, width, height, centered=(True, True, True), combine=True, clean=True):
- """
- Return a 3d box with specified dimensions for each object on the stack.
-
- :param length: box size in X direction
- :type length: float > 0
- :param width: box size in Y direction
- :type width: float > 0
- :param height: box size in Z direction
- :type height: float > 0
- :param centered: should the box be centered, or should reference point be at the lower
- bound of the range?
- :param combine: should the results be combined with other solids on the stack
- (and each other)?
- :type combine: true to combine shapes, false otherwise.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- Centered is a tuple that describes whether the box should be centered on the x,y, and
- z axes. If true, the box is centered on the respective axis relative to the workplane
- origin, if false, the workplane center will represent the lower bound of the resulting box
-
- one box is created for each item on the current stack. If no items are on the stack, one box
- using the current workplane center is created.
-
- If combine is true, the result will be a single object on the stack:
- if a solid was found in the chain, the result is that solid with all boxes produced
- fused onto it otherwise, the result is the combination of all the produced boxes
-
- if combine is false, the result will be a list of the boxes produced
-
- Most often boxes form the basis for a part::
-
- #make a single box with lower left corner at origin
- s = Workplane().box(1,2,3,centered=(False,False,False)
-
- But sometimes it is useful to create an array of them:
-
- #create 4 small square bumps on a larger base plate:
- s = Workplane().box(4,4,0.5).faces(">Z").workplane()\
- .rect(3,3,forConstruction=True).vertices().box(0.25,0.25,0.25,combine=True)
-
- """
-
- def _makebox(pnt):
-
- #(xp,yp,zp) = self.plane.toLocalCoords(pnt)
- (xp, yp, zp) = pnt.toTuple()
- if centered[0]:
- xp -= (length / 2.0)
- if centered[1]:
- yp -= (width / 2.0)
- if centered[2]:
- zp -= (height / 2.0)
-
- return Solid.makeBox(length, width, height, Vector(xp, yp, zp))
-
- boxes = self.eachpoint(_makebox, True)
-
- #if combination is not desired, just return the created boxes
- if not combine:
- return boxes
- else:
- #combine everything
- return self.union(boxes, clean=clean)
-
- def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360,
- centered=(True, True, True), combine=True, clean=True):
- """
- Returns a 3D sphere with the specified radius for each point on the stack
-
- :param radius: The radius of the sphere
- :type radius: float > 0
- :param direct: The direction axis for the creation of the sphere
- :type direct: A three-tuple
- :param angle1: The first angle to sweep the sphere arc through
- :type angle1: float > 0
- :param angle2: The second angle to sweep the sphere arc through
- :type angle2: float > 0
- :param angle3: The third angle to sweep the sphere arc through
- :type angle3: float > 0
- :param centered: A three-tuple of booleans that determines whether the sphere is centered
- on each axis origin
- :param combine: Whether the results should be combined with other solids on the stack
- (and each other)
- :type combine: true to combine shapes, false otherwise
- :return: A sphere object for each point on the stack
-
- Centered is a tuple that describes whether the sphere should be centered on the x,y, and
- z axes. If true, the sphere is centered on the respective axis relative to the workplane
- origin, if false, the workplane center will represent the lower bound of the resulting
- sphere.
-
- One sphere is created for each item on the current stack. If no items are on the stack, one
- box using the current workplane center is created.
-
- If combine is true, the result will be a single object on the stack:
- If a solid was found in the chain, the result is that solid with all spheres produced
- fused onto it otherwise, the result is the combination of all the produced boxes
-
- If combine is false, the result will be a list of the spheres produced
- """
-
- # Convert the direction tuple to a vector, if needed
- if isinstance(direct, tuple):
- direct = Vector(direct)
-
- def _makesphere(pnt):
- """
- Inner function that is used to create a sphere for each point/object on the workplane
- :param pnt: The center point for the sphere
- :return: A CQ Solid object representing a sphere
- """
- (xp, yp, zp) = pnt.toTuple()
-
- if not centered[0]:
- xp += radius
-
- if not centered[1]:
- yp += radius
-
- if not centered[2]:
- zp += radius
-
- return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3)
-
- # We want a sphere for each point on the workplane
- spheres = self.eachpoint(_makesphere, True)
-
- # If we don't need to combine everything, just return the created spheres
- if not combine:
- return spheres
- else:
- return self.union(spheres, clean=clean)
-
- def clean(self):
- """
- Cleans the current solid by removing unwanted edges from the
- faces.
-
- Normally you don't have to call this function. It is
- automatically called after each related operation. You can
- disable this behavior with `clean=False` parameter if method
- has any. In some cases this can improve performance
- drastically but is generally dis-advised since it may break
- some operations such as fillet.
-
- Note that in some cases where lots of solid operations are
- chained, `clean()` may actually improve performance since
- the shape is 'simplified' at each step and thus next operation
- is easier.
-
- Also note that, due to limitation of the underlying engine,
- `clean` may fail to produce a clean output in some cases such as
- spherical faces.
- """
- try:
- cleanObjects = [obj.clean() for obj in self.objects]
- except AttributeError:
- raise AttributeError("%s object doesn't support `clean()` method!" % obj.ShapeType())
- return self.newObject(cleanObjects)
diff --git a/Libs/cadquery-lib/cadquery/cq_directive.py b/Libs/cadquery-lib/cadquery/cq_directive.py
deleted file mode 100644
index 0dc5fae..0000000
--- a/Libs/cadquery-lib/cadquery/cq_directive.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""
-A special directive for including a cq object.
-
-"""
-
-import traceback
-from cadquery import *
-from cadquery import cqgi
-import StringIO
-from docutils.parsers.rst import directives
-
-template = """
-
-.. raw:: html
-
-
- %(out_svg)s
-
-
-
-
-"""
-template_content_indent = ' '
-
-
-def cq_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- # only consider inline snippets
- plot_code = '\n'.join(content)
-
- # Since we don't have a filename, use a hash based on the content
- # the script must define a variable called 'out', which is expected to
- # be a CQ object
- out_svg = "Your Script Did not assign call build_output() function!"
-
- try:
- _s = StringIO.StringIO()
- result = cqgi.parse(plot_code).build()
-
- if result.success:
- exporters.exportShape(result.first_result, "SVG", _s)
- out_svg = _s.getvalue()
- else:
- raise result.exception
-
- except Exception:
- traceback.print_exc()
- out_svg = traceback.format_exc()
-
- # now out
- # Now start generating the lines of output
- lines = []
-
- # get rid of new lines
- out_svg = out_svg.replace('\n', '')
-
- txt_align = "left"
- if "align" in options:
- txt_align = options['align']
-
- lines.extend((template % locals()).split('\n'))
-
- lines.extend(['::', ''])
- lines.extend([' %s' % row.rstrip()
- for row in plot_code.split('\n')])
- lines.append('')
-
- if len(lines):
- state_machine.insert_input(
- lines, state_machine.input_lines.source(0))
-
- return []
-
-
-def setup(app):
- setup.app = app
- setup.config = app.config
- setup.confdir = app.confdir
-
- options = {'height': directives.length_or_unitless,
- 'width': directives.length_or_percentage_or_unitless,
- 'align': directives.unchanged
- }
-
- app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)
diff --git a/Libs/cadquery-lib/cadquery/cqgi.py b/Libs/cadquery-lib/cadquery/cqgi.py
deleted file mode 100644
index d654d72..0000000
--- a/Libs/cadquery-lib/cadquery/cqgi.py
+++ /dev/null
@@ -1,477 +0,0 @@
-"""
-The CadQuery Gateway Interface.
-Provides classes and tools for executing CadQuery scripts
-"""
-import ast
-import traceback
-import time
-import cadquery
-
-CQSCRIPT = ""
-
-def parse(script_source):
- """
- Parses the script as a model, and returns a model.
-
- If you would prefer to access the underlying model without building it,
- for example, to inspect its available parameters, construct a CQModel object.
-
- :param script_source: the script to run. Must be a valid cadquery script
- :return: a CQModel object that defines the script and allows execution
-
- """
- model = CQModel(script_source)
- return model
-
-
-class CQModel(object):
- """
- Represents a Cadquery Script.
-
- After construction, the metadata property contains
- a ScriptMetaData object, which describes the model in more detail,
- and can be used to retrive the parameters defined by the model.
-
- the build method can be used to generate a 3d model
- """
- def __init__(self, script_source):
- """
- Create an object by parsing the supplied python script.
- :param script_source: a python script to parse
- """
- self.metadata = ScriptMetadata()
- self.ast_tree = ast.parse(script_source, CQSCRIPT)
- self.script_source = script_source
- self._find_vars()
-
- # TODO: pick up other scirpt metadata:
- # describe
- # pick up validation methods
- self._find_descriptions()
-
- def _find_vars(self):
- """
- Parse the script, and populate variables that appear to be
- overridable.
- """
- #assumption here: we assume that variable declarations
- #are only at the top level of the script. IE, we'll ignore any
- #variable definitions at lower levels of the script
-
- #we dont want to use the visit interface because here we excplicitly
- #want to walk only the top level of the tree.
- assignment_finder = ConstantAssignmentFinder(self.metadata)
-
- for node in self.ast_tree.body:
- if isinstance(node, ast.Assign):
- assignment_finder.visit_Assign(node)
-
- def _find_descriptions(self):
- description_finder = ParameterDescriptionFinder(self.metadata)
- description_finder.visit(self.ast_tree)
-
- def validate(self, params):
- """
- Determine if the supplied parameters are valid.
- NOT IMPLEMENTED YET-- raises NotImplementedError
- :param params: a dictionary of parameters
-
- """
- raise NotImplementedError("not yet implemented")
-
- def build(self, build_parameters=None, build_options=None):
- """
- Executes the script, using the optional parameters to override those in the model
- :param build_parameters: a dictionary of variables. The variables must be
- assignable to the underlying variable type. These variables override default values in the script
- :param build_options: build options for how to build the model. Build options include things like
- timeouts, tesselation tolerances, etc
- :raises: Nothing. If there is an exception, it will be on the exception property of the result.
- This is the interface so that we can return other information on the result, such as the build time
- :return: a BuildResult object, which includes the status of the result, and either
- a resulting shape or an exception
- """
- if not build_parameters:
- build_parameters = {}
-
- start = time.clock()
- result = BuildResult()
-
- try:
- self.set_param_values(build_parameters)
- collector = ScriptCallback()
- env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
- .add_entry("build_object", collector.build_object) \
- .add_entry("debug", collector.debug) \
- .add_entry("describe_parameter",collector.describe_parameter) \
- .build()
-
- c = compile(self.ast_tree, CQSCRIPT, 'exec')
- exec (c, env)
- result.set_debug(collector.debugObjects )
- result.set_success_result(collector.outputObjects)
-
- except Exception, ex:
- #print "Error Executing Script:"
- result.set_failure_result(ex)
- #traceback.print_exc()
- #print "Full Text of Script:"
- #print self.script_source
-
- end = time.clock()
- result.buildTime = end - start
- return result
-
- def set_param_values(self, params):
- model_parameters = self.metadata.parameters
-
- for k, v in params.iteritems():
- if k not in model_parameters:
- raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
-
- p = model_parameters[k]
- p.set_value(v)
-
-
-class ShapeResult(object):
- """
- An object created by a build, including the user parameters provided
- """
- def __init__(self):
- self.shape = None
- self.options = None
-
-class BuildResult(object):
- """
- The result of executing a CadQuery script.
- The success property contains whether the exeuction was successful.
-
- If successful, the results property contains a list of all results,
- and the first_result property contains the first result.
-
- If unsuccessful, the exception property contains a reference to
- the stack trace that occurred.
- """
- def __init__(self):
- self.buildTime = None
- self.results = [] #list of ShapeResult
- self.debugObjects = [] #list of ShapeResult
- self.first_result = None
- self.success = False
- self.exception = None
-
- def set_failure_result(self, ex):
- self.exception = ex
- self.success = False
-
- def set_debug(self, debugObjects):
- self.debugObjects = debugObjects
-
- def set_success_result(self, results):
- self.results = results
- if len(self.results) > 0:
- self.first_result = self.results[0]
- self.success = True
-
-
-class ScriptMetadata(object):
- """
- Defines the metadata for a parsed CQ Script.
- the parameters property is a dict of InputParameter objects.
- """
- def __init__(self):
- self.parameters = {}
-
- def add_script_parameter(self, p):
- self.parameters[p.name] = p
-
- def add_parameter_description(self,name,description):
- #print 'Adding Parameter name=%s, desc=%s' % ( name, description )
- p = self.parameters[name]
- p.desc = description
-
-
-class ParameterType(object):
- pass
-
-
-class NumberParameterType(ParameterType):
- pass
-
-
-class StringParameterType(ParameterType):
- pass
-
-
-class BooleanParameterType(ParameterType):
- pass
-
-
-class InputParameter:
- """
- Defines a parameter that can be supplied when the model is executed.
-
- Name, varType, and default_value are always available, because they are computed
- from a variable assignment line of code:
-
- The others are only available if the script has used define_parameter() to
- provide additional metadata
-
- """
- def __init__(self):
-
- #: the default value for the variable.
- self.default_value = None
-
- #: the name of the parameter.
- self.name = None
-
- #: type of the variable: BooleanParameter, StringParameter, NumericParameter
- self.varType = None
-
- #: help text describing the variable. Only available if the script used describe_parameter()
- self.desc = None
-
- #: valid values for the variable. Only available if the script used describe_parameter()
- self.valid_values = []
-
- self.ast_node = None
-
- @staticmethod
- def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
-
- if valid_values is None:
- valid_values = []
-
- p = InputParameter()
- p.ast_node = ast_node
- p.default_value = default_value
- p.name = var_name
- p.desc = desc
- p.varType = var_type
- p.valid_values = valid_values
- return p
-
- def set_value(self, new_value):
-
- if len(self.valid_values) > 0 and new_value not in self.valid_values:
- raise InvalidParameterError(
- "Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
- .format(str(new_value), self.name, str(self.valid_values)))
-
- if self.varType == NumberParameterType:
- try:
- f = float(new_value)
- self.ast_node.n = f
- except ValueError:
- raise InvalidParameterError(
- "Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
- .format(str(new_value), self.name))
-
- elif self.varType == StringParameterType:
- self.ast_node.s = str(new_value)
- elif self.varType == BooleanParameterType:
- if new_value:
- self.ast_node.id = 'True'
- else:
- self.ast_node.id = 'False'
- else:
- raise ValueError("Unknown Type of var: ", str(self.varType))
-
- def __str__(self):
- return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
- self.name, str(self.varType), str(self.default_value))
-
-
-class ScriptCallback(object):
- """
- Allows a script to communicate with the container
- the build_object() method is exposed to CQ scripts, to allow them
- to return objects to the execution environment
- """
- def __init__(self):
- self.outputObjects = []
- self.debugObjects = []
-
- def build_object(self, shape,options={}):
- """
- return an object to the executing environment, with options
- :param shape: a cadquery object
- :param options: a dictionary of options that will be made available to the executing envrionment
- """
- o = ShapeResult()
- o.options=options
- o.shape = shape
- self.outputObjects.append(o)
-
- def debug(self,obj,args={}):
- """
- Debug print/output an object, with optional arguments.
- """
- s = ShapeResult()
- s.shape = obj
- s.options = args
- self.debugObjects.append(s)
-
- def describe_parameter(self,var_data ):
- """
- Do Nothing-- we parsed the ast ahead of exection to get what we need.
- """
- pass
-
- def add_error(self, param, field_list):
- """
- Not implemented yet: allows scripts to indicate that there are problems with inputs
- """
- pass
-
- def has_results(self):
- return len(self.outputObjects) > 0
-
-
-
-class InvalidParameterError(Exception):
- """
- Raised when an attempt is made to provide a new parameter value
- that cannot be assigned to the model
- """
- pass
-
-
-class NoOutputError(Exception):
- """
- Raised when the script does not execute the build_object() method to
- return a solid
- """
- pass
-
-
-class ScriptExecutionError(Exception):
- """
- Represents a script syntax error.
- Useful for helping clients pinpoint issues with the script
- interactively
- """
-
- def __init__(self, line=None, message=None):
- if line is None:
- self.line = 0
- else:
- self.line = line
-
- if message is None:
- self.message = "Unknown Script Error"
- else:
- self.message = message
-
- def full_message(self):
- return self.__repr__()
-
- def __str__(self):
- return self.__repr__()
-
- def __repr__(self):
- return "ScriptError [Line %s]: %s" % (self.line, self.message)
-
-
-class EnvironmentBuilder(object):
- """
- Builds an execution environment for a cadquery script.
- The environment includes the builtins, as well as
- the other methods the script will need.
- """
- def __init__(self):
- self.env = {}
-
- def with_real_builtins(self):
- return self.with_builtins(__builtins__)
-
- def with_builtins(self, env_dict):
- self.env['__builtins__'] = env_dict
- return self
-
- def with_cadquery_objects(self):
- self.env['cadquery'] = cadquery
- self.env['cq'] = cadquery
- return self
-
- def add_entry(self, name, value):
- self.env[name] = value
- return self
-
- def build(self):
- return self.env
-
-class ParameterDescriptionFinder(ast.NodeTransformer):
- """
- Visits a parse tree, looking for function calls to describe_parameter(var, description )
- """
- def __init__(self, cq_model):
- self.cqModel = cq_model
-
- def visit_Call(self,node):
- """
- Called when we see a function call. Is it describe_parameter?
- """
- try:
- if node.func.id == 'describe_parameter':
- #looks like we have a call to our function.
- #first parameter is the variable,
- #second is the description
- varname = node.args[0].id
- desc = node.args[1].s
- self.cqModel.add_parameter_description(varname,desc)
-
- except:
- #print "Unable to handle function call"
- pass
- return node
-
-class ConstantAssignmentFinder(ast.NodeTransformer):
- """
- Visits a parse tree, and adds script parameters to the cqModel
- """
-
- def __init__(self, cq_model):
- self.cqModel = cq_model
-
- def handle_assignment(self, var_name, value_node):
- try:
-
- if type(value_node) == ast.Num:
- self.cqModel.add_script_parameter(
- InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
- elif type(value_node) == ast.Str:
- self.cqModel.add_script_parameter(
- InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
- elif type(value_node == ast.Name):
- if value_node.id == 'True':
- self.cqModel.add_script_parameter(
- InputParameter.create(value_node, var_name, BooleanParameterType, True))
- elif value_node.id == 'False':
- self.cqModel.add_script_parameter(
- InputParameter.create(value_node, var_name, BooleanParameterType, True))
- except:
- print "Unable to handle assignment for variable '%s'" % var_name
- pass
-
- def visit_Assign(self, node):
-
- try:
- left_side = node.targets[0]
-
- #do not handle attribute assignments
- if isinstance(left_side,ast.Attribute):
- return
-
- if type(node.value) in [ast.Num, ast.Str, ast.Name]:
- self.handle_assignment(left_side.id, node.value)
- elif type(node.value) == ast.Tuple:
- # we have a multi-value assignment
- for n, v in zip(left_side.elts, node.value.elts):
- self.handle_assignment(n.id, v)
- except:
- traceback.print_exc()
- print "Unable to handle assignment for node '%s'" % ast.dump(left_side)
-
- return node
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/README.txt b/Libs/cadquery-lib/cadquery/freecad_impl/README.txt
deleted file mode 100644
index 34ea788..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-It is ok for files in this directory to import FreeCAD, FreeCAD.Base, and FreeCAD.Part.
-
-Other modules should _not_ depend on FreeCAD
\ No newline at end of file
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py b/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
deleted file mode 100644
index 3a82a2f..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/__init__.py
+++ /dev/null
@@ -1,107 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-"""
-import os
-import sys
-
-
-def _fc_path():
- """Find FreeCAD"""
- # Look for FREECAD_LIB env variable
- _PATH = os.environ.get('FREECAD_LIB', '')
- if _PATH and os.path.exists(_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",
- "/opt/freecad/lib/",
- "/usr/bin/freecad/lib",
- "/usr/lib/freecad-daily/lib",
- "/usr/lib/freecad",
- "/usr/lib64/freecad/lib",
- ]:
- if os.path.exists(_PATH):
- return _PATH
-
- elif sys.platform.startswith('win'):
- # Try all the usual suspects
- for _PATH in [
- "c:/Program Files/FreeCAD0.12/bin",
- "c:/Program Files/FreeCAD0.13/bin",
- "c:/Program Files/FreeCAD0.14/bin",
- "c:/Program Files/FreeCAD0.15/bin",
- "c:/Program Files/FreeCAD0.16/bin",
- "c:/Program Files/FreeCAD0.17/bin",
- "c:/Program Files (x86)/FreeCAD0.12/bin",
- "c:/Program Files (x86)/FreeCAD0.13/bin",
- "c:/Program Files (x86)/FreeCAD0.14/bin",
- "c:/Program Files (x86)/FreeCAD0.15/bin",
- "c:/Program Files (x86)/FreeCAD0.16/bin",
- "c:/Program Files (x86)/FreeCAD0.17/bin",
- "c:/apps/FreeCAD0.12/bin",
- "c:/apps/FreeCAD0.13/bin",
- "c:/apps/FreeCAD0.14/bin",
- "c:/apps/FreeCAD0.15/bin",
- "c:/apps/FreeCAD0.16/bin",
- "c:/apps/FreeCAD0.17/bin",
- "c:/Program Files/FreeCAD 0.12/bin",
- "c:/Program Files/FreeCAD 0.13/bin",
- "c:/Program Files/FreeCAD 0.14/bin",
- "c:/Program Files/FreeCAD 0.15/bin",
- "c:/Program Files/FreeCAD 0.16/bin",
- "c:/Program Files/FreeCAD 0.17/bin",
- "c:/Program Files (x86)/FreeCAD 0.12/bin",
- "c:/Program Files (x86)/FreeCAD 0.13/bin",
- "c:/Program Files (x86)/FreeCAD 0.14/bin",
- "c:/Program Files (x86)/FreeCAD 0.15/bin",
- "c:/Program Files (x86)/FreeCAD 0.16/bin",
- "c:/Program Files (x86)/FreeCAD 0.17/bin",
- "c:/apps/FreeCAD 0.12/bin",
- "c:/apps/FreeCAD 0.13/bin",
- "c:/apps/FreeCAD 0.14/bin",
- "c:/apps/FreeCAD 0.15/bin",
- "c:/apps/FreeCAD 0.16/bin",
- "c:/apps/FreeCAD 0.17/bin",
- ]:
- if os.path.exists(_PATH):
- return _PATH
-
- elif sys.platform.startswith('darwin'):
- # Assume we're dealing with a Mac
- for _PATH in [
- "/Applications/FreeCAD.app/Contents/lib",
- os.path.join(os.path.expanduser("~"),
- "Library/Application Support/FreeCAD/lib"),
- ]:
- if os.path.exists(_PATH):
- return _PATH
-
- raise ImportError('cadquery was unable to determine freecad library path')
-
-
-# Make sure that the correct FreeCAD path shows up in Python's system path
-try:
- import FreeCAD
-except ImportError:
- path = _fc_path()
- sys.path.insert(0, path)
- import FreeCAD
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/exporters.py b/Libs/cadquery-lib/cadquery/freecad_impl/exporters.py
deleted file mode 100644
index c4b097a..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/exporters.py
+++ /dev/null
@@ -1,392 +0,0 @@
-import cadquery
-
-import FreeCAD
-import Drawing
-
-import tempfile, os, StringIO
-
-
-try:
- import xml.etree.cElementTree as ET
-except ImportError:
- import xml.etree.ElementTree as ET
-
-
-class ExportTypes:
- STL = "STL"
- STEP = "STEP"
- AMF = "AMF"
- SVG = "SVG"
- TJS = "TJS"
-
-
-class UNITS:
- MM = "mm"
- IN = "in"
-
-
-def toString(shape, exportType, tolerance=0.1):
- s = StringIO.StringIO()
- exportShape(shape, exportType, s, tolerance)
- return s.getvalue()
-
-
-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.
- """
- bb = shape.BoundBox
-
- dimList = [ bb.XLength, bb.YLength,bb.ZLength ]
- #no real part would likely be bigger than 10 inches on any side
- if max(dimList) > 10:
- return UNITS.MM
-
- #no real part would likely be smaller than 0.1 mm on all dimensions
- if min(dimList) < 0.1:
- return UNITS.IN
-
- #no real part would have the sum of its dimensions less than about 5mm
- if sum(dimList) < 10:
- return UNITS.IN
-
- return UNITS.MM
-
-
-class AmfWriter(object):
- def __init__(self,tessellation):
-
- self.units = "mm"
- self.tessellation = tessellation
-
- 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")
- mesh = ET.SubElement(object,'mesh')
- vertices = ET.SubElement(mesh,'vertices')
- volume = ET.SubElement(mesh,'volume')
-
- #add vertices
- for v in self.tessellation[0]:
- vtx = ET.SubElement(vertices,'vertex')
- coord = ET.SubElement(vtx,'coordinates')
- x = ET.SubElement(coord,'x')
- x.text = str(v.x)
- y = ET.SubElement(coord,'y')
- y.text = str(v.y)
- z = ET.SubElement(coord,'z')
- z.text = str(v.z)
-
- #add triangles
- for t in self.tessellation[1]:
- triangle = ET.SubElement(volume,'triangle')
- v1 = ET.SubElement(triangle,'v1')
- v1.text = str(t[0])
- v2 = ET.SubElement(triangle,'v2')
- v2.text = str(t[1])
- v3 = ET.SubElement(triangle,'v3')
- v3.text = str(t[2])
-
-
- 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 JsonMesh(object):
- def __init__(self):
-
- self.vertices = [];
- self.faces = [];
- self.nVertices = 0;
- self.nFaces = 0;
-
- def addVertex(self,x,y,z):
- self.nVertices += 1;
- self.vertices.extend([x,y,z]);
-
- #add triangle composed of the three provided vertex indices
- def addTriangleFace(self, i,j,k):
- #first position means justa simple triangle
- self.nFaces += 1;
- self.faces.extend([0,int(i),int(j),int(k)]);
-
- """
- Get a json model from this model.
- For now we'll forget about colors, vertex normals, and all that stuff
- """
- def toJson(self):
- return JSON_TEMPLATE % {
- 'vertices' : str(self.vertices),
- 'faces' : str(self.faces),
- 'nVertices': self.nVertices,
- 'nFaces' : self.nFaces
- };
-
-
-def getPaths(freeCadSVG):
- """
- freeCad svg is worthless-- except for paths, which are fairly useful
- this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element
- returns two lists-- one list of visible lines, and one list of hidden lines
-
- HACK ALERT!!!!!
- FreeCAD does not give a way to determine which lines are hidden and which are not
- the only way to tell is that hidden lines are in a with 0.15 stroke and visible are 0.35 stroke.
- so we actually look for that as a way to parse.
-
- to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we
- cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick!
- """
-
- hiddenPaths = []
- visiblePaths = []
- if len(freeCadSVG) > 0:
- #yuk, freecad returns svg fragments. stupid stupid
- fullDoc = "%s" % freeCadSVG
- e = ET.ElementTree(ET.fromstring(fullDoc))
- segments = e.findall(".//g")
- for s in segments:
- paths = s.findall("path")
-
- if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK
- mylist = hiddenPaths
- else:
- mylist = visiblePaths
-
- for p in paths:
- mylist.append(p.get("d"))
- return (hiddenPaths,visiblePaths)
- else:
- return ([],[])
-
-
-def getSVG(shape,opts=None):
- """
- Export a shape to SVG
- """
-
- d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
-
- if opts:
- d.update(opts)
-
- #need to guess the scale and the coordinate center
- uom = guessUnitOfMeasure(shape)
-
- width=float(d['width'])
- height=float(d['height'])
- marginLeft=float(d['marginLeft'])
- marginTop=float(d['marginTop'])
-
- #TODO: provide option to give 3 views
- viewVector = FreeCAD.Base.Vector(-1.75,1.1,5)
- (visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector)
-
- (hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented!
-
- #get bounding box -- these are all in 2-d space
- bb = visibleG0.BoundBox
- bb.add(visibleG1.BoundBox)
- bb.add(hiddenG0.BoundBox)
- bb.add(hiddenG1.BoundBox)
-
- #width pixels for x, height pixesl for y
- unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
-
- #compute amount to translate-- move the top left into view
- (xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale)
-
- #compute paths ( again -- had to strip out freecad crap )
- hiddenContent = ""
- for p in hiddenPaths:
- hiddenContent += PATHTEMPLATE % p
-
- visibleContent = ""
- for p in visiblePaths:
- visibleContent += PATHTEMPLATE % p
-
- svg = SVG_TEMPLATE % (
- {
- "unitScale" : str(unitScale),
- "strokeWidth" : str(1.0/unitScale),
- "hiddenContent" : hiddenContent ,
- "visibleContent" :visibleContent,
- "xTranslate" : str(xTranslate),
- "yTranslate" : str(yTranslate),
- "width" : str(width),
- "height" : str(height),
- "textboxY" :str(height - 30),
- "uom" : str(uom)
- }
- )
- #svg = SVG_TEMPLATE % (
- # {"content": projectedContent}
- #)
- return svg
-
-
-def exportSVG(shape, fileName):
- """
- 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
- export a view of a part to svg
- """
-
- svg = getSVG(shape.val().wrapped)
- f = open(fileName,'w')
- f.write(svg)
- f.close()
-
-
-
-JSON_TEMPLATE= """\
-{
- "metadata" :
- {
- "formatVersion" : 3,
- "generatedBy" : "ParametricParts",
- "vertices" : %(nVertices)d,
- "faces" : %(nFaces)d,
- "normals" : 0,
- "colors" : 0,
- "uvs" : 0,
- "materials" : 1,
- "morphTargets" : 0
- },
-
- "scale" : 1.0,
-
- "materials": [ {
- "DbgColor" : 15658734,
- "DbgIndex" : 0,
- "DbgName" : "Material",
- "colorAmbient" : [0.0, 0.0, 0.0],
- "colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746],
- "colorSpecular" : [0.5, 0.5, 0.5],
- "shading" : "Lambert",
- "specularCoef" : 50,
- "transparency" : 1.0,
- "vertexColors" : false
- }],
-
- "vertices": %(vertices)s,
-
- "morphTargets": [],
-
- "normals": [],
-
- "colors": [],
-
- "uvs": [[]],
-
- "faces": %(faces)s
-}
-"""
-
-SVG_TEMPLATE = """
-
-"""
-
-PATHTEMPLATE="\t\t\t\n"
-
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/geom.py b/Libs/cadquery-lib/cadquery/freecad_impl/geom.py
deleted file mode 100644
index 2bd8c3a..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/geom.py
+++ /dev/null
@@ -1,647 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-"""
-
-import math
-import cadquery
-import FreeCAD
-import Part as FreeCADPart
-
-
-def sortWiresByBuildOrder(wireList, plane, result=[]):
- """Tries to determine how wires should be combined into faces.
-
- Assume:
- The wires make up one or more faces, which could have 'holes'
- Outer wires are listed ahead of inner wires
- there are no wires inside wires inside wires
- ( IE, islands -- we can deal with that later on )
- none of the wires are construction wires
-
- Compute:
- one or more sets of wires, with the outer wire listed first, and inner
- ones
-
- Returns, list of lists.
- """
- result = []
-
- remainingWires = list(wireList)
- while remainingWires:
- outerWire = remainingWires.pop(0)
- group = [outerWire]
- otherWires = list(remainingWires)
- for w in otherWires:
- if plane.isWireInside(outerWire, w):
- group.append(w)
- remainingWires.remove(w)
- result.append(group)
-
- return result
-
-
-class Vector(object):
- """Create a 3-dimensional vector
-
- :param args: a 3-d vector, with x-y-z parts.
-
- you can either provide:
- * nothing (in which case the null vector is return)
- * a FreeCAD vector
- * a vector ( in which case it is copied )
- * a 3-tuple
- * three float values, x, y, and z
- """
- def __init__(self, *args):
- if len(args) == 3:
- fV = FreeCAD.Base.Vector(args[0], args[1], args[2])
- elif len(args) == 1:
- if isinstance(args[0], Vector):
- fV = args[0].wrapped
- elif isinstance(args[0], tuple):
- fV = FreeCAD.Base.Vector(args[0][0], args[0][1], args[0][2])
- elif isinstance(args[0], FreeCAD.Base.Vector):
- fV = args[0]
- else:
- fV = args[0]
- elif len(args) == 0:
- fV = FreeCAD.Base.Vector(0, 0, 0)
- else:
- raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple")
-
- self._wrapped = fV
-
- @property
- def x(self):
- return self.wrapped.x
-
- @property
- def y(self):
- return self.wrapped.y
-
- @property
- def z(self):
- return self.wrapped.z
-
- @property
- def Length(self):
- return self.wrapped.Length
-
- @property
- def wrapped(self):
- return self._wrapped
-
- def toTuple(self):
- return (self.x, self.y, self.z)
-
- # TODO: is it possible to create a dynamic proxy without all this code?
- def cross(self, v):
- return Vector(self.wrapped.cross(v.wrapped))
-
- def dot(self, v):
- return self.wrapped.dot(v.wrapped)
-
- def sub(self, v):
- return Vector(self.wrapped.sub(v.wrapped))
-
- def add(self, v):
- return Vector(self.wrapped.add(v.wrapped))
-
- def multiply(self, scale):
- """Return a copy multiplied by the provided scalar"""
- tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
- return Vector(tmp_fc_vector.multiply(scale))
-
- def normalized(self):
- """Return a normalized version of this vector"""
- tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
- tmp_fc_vector.normalize()
- return Vector(tmp_fc_vector)
-
- def Center(self):
- """Return the vector itself
-
- The center of myself is myself.
- Provided so that vectors, vertexes, and other shapes all support a
- common interface, when Center() is requested for all objects on the
- stack.
- """
- return self
-
- def getAngle(self, v):
- return self.wrapped.getAngle(v.wrapped)
-
- def distanceToLine(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def projectToLine(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def distanceToPlane(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def projectToPlane(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def __add__(self, v):
- return self.add(v)
-
- def __repr__(self):
- return self.wrapped.__repr__()
-
- def __str__(self):
- return self.wrapped.__str__()
-
- def __ne__(self, other):
- return self.wrapped.__ne__(other)
-
- def __eq__(self, other):
- return self.wrapped.__eq__(other)
-
-
-class Matrix:
- """A 3d , 4x4 transformation matrix.
-
- Used to move geometry in space.
- """
- def __init__(self, matrix=None):
- if matrix is None:
- self.wrapped = FreeCAD.Base.Matrix()
- else:
- self.wrapped = matrix
-
- def rotateX(self, angle):
- self.wrapped.rotateX(angle)
-
- def rotateY(self, angle):
- self.wrapped.rotateY(angle)
-
-
-class Plane(object):
- """A 2D coordinate system in space
-
- A 2D coordinate system in space, with the x-y axes on the plane, and a
- particular point as the origin.
-
- A plane allows the use of 2-d coordinates, which are later converted to
- global, 3d coordinates when the operations are complete.
-
- Frequently, it is not necessary to create work planes, as they can be
- created automatically from faces.
- """
-
- @classmethod
- def named(cls, stdName, origin=(0, 0, 0)):
- """Create a predefined Plane based on the conventional names.
-
- :param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom)
- :type stdName: string
- :param origin: the desired origin, specified in global coordinates
- :type origin: 3-tuple of the origin of the new plane, in global coorindates.
-
- Available named planes are as follows. Direction references refer to
- the global directions.
-
- =========== ======= ======= ======
- Name xDir yDir zDir
- =========== ======= ======= ======
- XY +x +y +z
- YZ +y +z +x
- ZX +z +x +y
- XZ +x +z -y
- YX +y +x -z
- ZY +z +y -x
- front +x +y +z
- back -x +y -z
- left +z +y -x
- right -z +y +x
- top +x -z +y
- bottom +x +z -y
- =========== ======= ======= ======
- """
-
- namedPlanes = {
- # origin, xDir, normal
- 'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
- 'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
- 'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
- 'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
- 'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
- 'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
- 'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
- 'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
- 'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
- 'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
- 'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
- 'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
- }
-
- try:
- return namedPlanes[stdName]
- except KeyError:
- raise ValueError('Supported names are {}'.format(
- namedPlanes.keys()))
-
- @classmethod
- def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('XY', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
- plane = Plane.named('YZ', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('ZX', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('XZ', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
- plane = Plane.named('YX', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('ZY', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('front', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
- plane = Plane.named('back', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('left', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
- plane = Plane.named('right', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('top', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('bottom', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- def __init__(self, origin, xDir, normal):
- """Create a Plane with an arbitrary orientation
-
- TODO: project x and y vectors so they work even if not orthogonal
- :param origin: the origin
- :type origin: a three-tuple of the origin, in global coordinates
- :param xDir: a vector representing the xDirection.
- :type xDir: a three-tuple representing a vector, or a FreeCAD Vector
- :param normal: the normal direction for the new plane
- :type normal: a FreeCAD Vector
- :raises: ValueError if the specified xDir is not orthogonal to the provided normal.
- :return: a plane in the global space, with the xDirection of the plane in the specified direction.
- """
- normal = Vector(normal)
- if (normal.Length == 0.0):
- raise ValueError('normal should be non null')
- self.zDir = normal.normalized()
- xDir = Vector(xDir)
- if (xDir.Length == 0.0):
- raise ValueError('xDir should be non null')
- self._setPlaneDir(xDir)
-
- self.invZDir = self.zDir.multiply(-1.0)
-
- self.origin = origin
-
- @property
- def origin(self):
- return self._origin
-
- @origin.setter
- def origin(self, value):
- self._origin = Vector(value)
- self._calcTransforms()
-
- def setOrigin2d(self, x, y):
- """
- Set a new origin in the plane itself
-
- Set a new origin in the plane itself. The plane's orientation and
- xDrection are unaffected.
-
- :param float x: offset in the x direction
- :param float y: offset in the y direction
- :return: void
-
- The new coordinates are specified in terms of the current 2-d system.
- As an example:
-
- p = Plane.XY()
- p.setOrigin2d(2, 2)
- p.setOrigin2d(2, 2)
-
- results in a plane with its origin at (x, y) = (4, 4) in global
- coordinates. Both operations were relative to local coordinates of the
- plane.
- """
- self.origin = self.toWorldCoords((x, y))
-
- def isWireInside(self, baseWire, testWire):
- """Determine if testWire is inside baseWire
-
- Determine if testWire is inside baseWire, after both wires are projected
- into the current plane.
-
- :param baseWire: a reference wire
- :type baseWire: a FreeCAD wire
- :param testWire: another wire
- :type testWire: a FreeCAD wire
- :return: True if testWire is inside baseWire, otherwise False
-
- If either wire does not lie in the current plane, it is projected into
- the plane first.
-
- *WARNING*: This method is not 100% reliable. It uses bounding box
- tests, but needs more work to check for cases when curves are complex.
-
- Future Enhancements:
- * Discretizing points along each curve to provide a more reliable
- test.
- """
- # TODO: also use a set of points along the wire to test as well.
- # TODO: would it be more efficient to create objects in the local
- # coordinate system, and then transform to global
- # coordinates upon extrusion?
-
- tBaseWire = baseWire.transformGeometry(self.fG)
- tTestWire = testWire.transformGeometry(self.fG)
-
- # These bounding boxes will have z=0, since we transformed them into the
- # space of the plane.
- bb = tBaseWire.BoundingBox()
- tb = tTestWire.BoundingBox()
-
- # findOutsideBox actually inspects both ways, here we only want to
- # know if one is inside the other
- return bb == BoundBox.findOutsideBox2D(bb, tb)
-
- def toLocalCoords(self, obj):
- """Project the provided coordinates onto this plane
-
- :param obj: an object or vector to convert
- :type vector: a vector or shape
- :return: an object of the same type, but converted to local coordinates
-
-
- Most of the time, the z-coordinate returned will be zero, because most
- operations based on a plane are all 2-d. Occasionally, though, 3-d
- points outside of the current plane are transformed. One such example is
- :py:meth:`Workplane.box`, where 3-d corners of a box are transformed to
- orient the box in space correctly.
-
- """
- if isinstance(obj, Vector):
- return Vector(self.fG.multiply(obj.wrapped))
- elif isinstance(obj, cadquery.Shape):
- return obj.transformShape(self.rG)
- else:
- raise ValueError(
- "Don't know how to convert type {} to local coordinates".format(
- type(obj)))
-
- def toWorldCoords(self, tuplePoint):
- """Convert a point in local coordinates to global coordinates
-
- :param tuplePoint: point in local coordinates to convert.
- :type tuplePoint: a 2 or three tuple of float. The third value is taken to be zero if not supplied.
- :return: a Vector in global coordinates
- """
- if isinstance(tuplePoint, Vector):
- v = tuplePoint
- elif len(tuplePoint) == 2:
- v = Vector(tuplePoint[0], tuplePoint[1], 0)
- else:
- v = Vector(tuplePoint)
- return Vector(self.rG.multiply(v.wrapped))
-
- def rotated(self, rotate=(0, 0, 0)):
- """Returns a copy of this plane, rotated about the specified axes
-
- Since the z axis is always normal the plane, rotating around Z will
- always produce a plane that is parallel to this one.
-
- The origin of the workplane is unaffected by the rotation.
-
- Rotations are done in order x, y, z. If you need a different order,
- manually chain together multiple rotate() commands.
-
- :param rotate: Vector [xDegrees, yDegrees, zDegrees]
- :return: a copy of this plane rotated as requested.
- """
- rotate = Vector(rotate)
- # Convert to radians.
- rotate = rotate.multiply(math.pi / 180.0)
-
- # Compute rotation matrix.
- m = FreeCAD.Base.Matrix()
- m.rotateX(rotate.x)
- m.rotateY(rotate.y)
- m.rotateZ(rotate.z)
-
- # Compute the new plane.
- newXdir = Vector(m.multiply(self.xDir.wrapped))
- newZdir = Vector(m.multiply(self.zDir.wrapped))
-
- return Plane(self.origin, newXdir, newZdir)
-
- def rotateShapes(self, listOfShapes, rotationMatrix):
- """Rotate the listOfShapes by the supplied rotationMatrix
-
- @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 coordinates.
-
- resultWires = []
- for w in listOfShapes:
- mirrored = w.transformGeometry(rotationMatrix.wrapped)
-
- # If the first vertex of the second wire is not coincident with the
- # first or last vertices of the first wire we have to fix the wire
- # so that it will mirror correctly.
- if ((mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and
- mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and
- mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or
- (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and
- mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and
- mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z)):
-
- resultWires.append(mirrored)
- else:
- # Make sure that our mirrored edges meet up and are ordered
- # properly.
- aEdges = w.wrapped.Edges
- aEdges.extend(mirrored.wrapped.Edges)
- comp = FreeCADPart.Compound(aEdges)
- mirroredWire = comp.connectEdgesToWires(False).Wires[0]
-
- resultWires.append(cadquery.Shape.cast(mirroredWire))
-
- return resultWires
-
- def _setPlaneDir(self, xDir):
- """Set the vectors parallel to the plane, i.e. xDir and yDir"""
- if (self.zDir.dot(xDir) > 1e-5):
- raise ValueError('xDir must be parralel to the plane')
- xDir = Vector(xDir)
- self.xDir = xDir.normalized()
- self.yDir = self.zDir.cross(self.xDir).normalized()
-
- def _calcTransforms(self):
- """Computes transformation matrices to convert between coordinates
-
- Computes transformation matrices to convert between local and global
- coordinates.
- """
- # r is the forward transformation matrix from world to local coordinates
- # ok i will be really honest, i cannot understand exactly why this works
- # something bout the order of the translation and the rotation.
- # the double-inverting is strange, and I don't understand it.
- r = FreeCAD.Base.Matrix()
-
- # Forward transform must rotate and adjust for origin.
- (r.A11, r.A12, r.A13) = (self.xDir.x, self.xDir.y, self.xDir.z)
- (r.A21, r.A22, r.A23) = (self.yDir.x, self.yDir.y, self.yDir.z)
- (r.A31, r.A32, r.A33) = (self.zDir.x, self.zDir.y, self.zDir.z)
-
- invR = r.inverse()
- invR.A14 = self.origin.x
- invR.A24 = self.origin.y
- invR.A34 = self.origin.z
-
- self.rG = invR
- self.fG = invR.inverse()
-
- def computeTransform(self, tMatrix):
- """Computes the 2-d projection of the supplied matrix"""
-
- return Matrix(self.fG.multiply(tMatrix.wrapped).multiply(self.rG))
-
-
-class BoundBox(object):
- """A BoundingBox for an object or set of objects. Wraps the FreeCAD one"""
- def __init__(self, bb):
- self.wrapped = bb
- self.xmin = bb.XMin
- self.xmax = bb.XMax
- self.xlen = bb.XLength
- self.ymin = bb.YMin
- self.ymax = bb.YMax
- self.ylen = bb.YLength
- self.zmin = bb.ZMin
- self.zmax = bb.ZMax
- self.zlen = bb.ZLength
- self.center = Vector(bb.Center)
- self.DiagonalLength = bb.DiagonalLength
-
- def add(self, obj):
- """Returns a modified (expanded) bounding box
-
- obj can be one of several things:
- 1. a 3-tuple corresponding to x,y, and z amounts to add
- 2. a vector, containing the x,y,z values to add
- 3. another bounding box, where a new box will be created that
- encloses both.
-
- This bounding box is not changed.
- """
- tmp = FreeCAD.Base.BoundBox(self.wrapped)
- if isinstance(obj, tuple):
- tmp.add(obj[0], obj[1], obj[2])
- elif isinstance(obj, Vector):
- tmp.add(obj.fV)
- elif isinstance(obj, BoundBox):
- tmp.add(obj.wrapped)
-
- return BoundBox(tmp)
-
- @classmethod
- def findOutsideBox2D(cls, b1, b2):
- """Compares bounding boxes
-
- Compares bounding boxes. Returns none if neither is inside the other.
- Returns the outer one if either is outside the other.
-
- BoundBox.isInside works in 3d, but this is a 2d bounding box, so it
- doesn't work correctly plus, there was all kinds of rounding error in
- the built-in implementation i do not understand.
- """
- fc_bb1 = b1.wrapped
- fc_bb2 = b2.wrapped
- if (fc_bb1.XMin < fc_bb2.XMin and
- fc_bb1.XMax > fc_bb2.XMax and
- fc_bb1.YMin < fc_bb2.YMin and
- fc_bb1.YMax > fc_bb2.YMax):
- return b1
-
- if (fc_bb2.XMin < fc_bb1.XMin and
- fc_bb2.XMax > fc_bb1.XMax and
- fc_bb2.YMin < fc_bb1.YMin and
- fc_bb2.YMax > fc_bb1.YMax):
- return b2
-
- return None
-
- def isInside(self, anotherBox):
- """Is the provided bounding box inside this one?"""
- return self.wrapped.isInside(anotherBox.wrapped)
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/importers.py b/Libs/cadquery-lib/cadquery/freecad_impl/importers.py
deleted file mode 100644
index 7d4f0a9..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/importers.py
+++ /dev/null
@@ -1,71 +0,0 @@
-
-import cadquery
-from .shapes import Shape
-
-import FreeCAD
-import Part
-import sys
-import os
-import urllib as urlreader
-import tempfile
-
-class ImportTypes:
- STEP = "STEP"
-
-class UNITS:
- MM = "mm"
- IN = "in"
-
-
-def importShape(importType, fileName):
- """
- Imports a file based on the type (STEP, STL, etc)
- :param importType: The type of file that we're importing
- :param fileName: THe name of the file that we're importing
- """
-
- #Check to see what type of file we're working with
- if importType == ImportTypes.STEP:
- return importStep(fileName)
-
-
-#Loads a STEP file into a CQ.Workplane object
-def importStep(fileName):
- """
- Accepts a file name and loads the STEP file into a cadquery shape
- :param fileName: The path and name of the STEP file to be imported
- """
- #Now read and return the shape
- try:
- #print fileName
- rshape = Part.read(fileName)
-
- #Make sure that we extract all the solids
- solids = []
- for solid in rshape.Solids:
- solids.append(Shape.cast(solid))
-
- return cadquery.Workplane("XY").newObject(solids)
- except:
- raise ValueError("STEP File Could not be loaded")
-
-#Loads a STEP file from an URL into a CQ.Workplane object
-def importStepFromURL(url):
- #Now read and return the shape
- try:
- webFile = urlreader.urlopen(url)
- tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
- tempFile.write(webFile.read())
- webFile.close()
- tempFile.close()
-
- rshape = Part.read(tempFile.name)
-
- #Make sure that we extract all the solids
- solids = []
- for solid in rshape.Solids:
- solids.append(Shape.cast(solid))
-
- return cadquery.Workplane("XY").newObject(solids)
- except:
- raise ValueError("STEP File from the URL: " + url + " Could not be loaded")
diff --git a/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py b/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
deleted file mode 100644
index 6f65ccc..0000000
--- a/Libs/cadquery-lib/cadquery/freecad_impl/shapes.py
+++ /dev/null
@@ -1,1044 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-
- Wrapper Classes for FreeCAD
- These classes provide a stable interface for 3d objects,
- independent of the FreeCAD interface.
-
- Future work might include use of pythonOCC, OCC, or even
- another CAD kernel directly, so this interface layer is quite important.
-
- Funny, in java this is one of those few areas where i'd actually spend the time
- to make an interface and an implementation, but for new these are just rolled together
-
- This interface layer provides three distinct values:
-
- 1. It allows us to avoid changing key api points if we change underlying implementations.
- It would be a disaster if script and plugin authors had to change models because we
- changed implementations
-
- 2. Allow better documentation. One of the reasons FreeCAD is no more popular is because
- its docs are terrible. This allows us to provide good documentation via docstrings
- for each wrapper
-
- 3. Work around bugs. there are a quite a feb bugs in free this layer allows fixing them
-
- 4. allows for enhanced functionality. Many objects are missing features we need. For example
- we need a 'forConstruction' flag on the Wire object. this allows adding those kinds of things
-
- 5. allow changing interfaces when we'd like. there are few cases where the FreeCAD api is not
- very user friendly: we like to change those when necessary. As an example, in the FreeCAD api,
- all factory methods are on the 'Part' object, but it is very useful to know what kind of
- object each one returns, so these are better grouped by the type of object they return.
- (who would know that Part.makeCircle() returns an Edge, but Part.makePolygon() returns a Wire ?
-"""
-from cadquery import Vector, BoundBox
-import FreeCAD
-import Part as FreeCADPart
-
-
-class Shape(object):
- """
- Represents a shape in the system.
- Wrappers the FreeCAD api
- """
-
- def __init__(self, obj):
- self.wrapped = obj
- self.forConstruction = False
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- @classmethod
- def cast(cls, obj, forConstruction=False):
- "Returns the right type of wrapper, given a FreeCAD object"
- s = obj.ShapeType
- if type(obj) == FreeCAD.Base.Vector:
- return Vector(obj)
- tr = None
-
- # TODO: there is a clever way to do this i'm sure with a lookup
- # but it is not a perfect mapping, because we are trying to hide
- # a bit of the complexity of Compounds in FreeCAD.
- if s == 'Vertex':
- tr = Vertex(obj)
- elif s == 'Edge':
- tr = Edge(obj)
- elif s == 'Wire':
- tr = Wire(obj)
- elif s == 'Face':
- tr = Face(obj)
- elif s == 'Shell':
- tr = Shell(obj)
- elif s == 'Solid':
- tr = Solid(obj)
- elif s == 'Compound':
- #compound of solids, lets return a solid instead
- if len(obj.Solids) > 1:
- tr = Solid(obj)
- elif len(obj.Solids) == 1:
- tr = Solid(obj.Solids[0])
- elif len(obj.Wires) > 0:
- tr = Wire(obj)
- else:
- tr = Compound(obj)
- else:
- raise ValueError("cast:unknown shape type %s" % s)
-
- tr.forConstruction = forConstruction
- return tr
-
- # TODO: all these should move into the exporters folder.
- # we dont need a bunch of exporting code stored in here!
- #
- def exportStl(self, fileName):
- self.wrapped.exportStl(fileName)
-
- def exportStep(self, fileName):
- self.wrapped.exportStep(fileName)
-
- def exportShape(self, fileName, fileFormat):
- if fileFormat == ExportFormats.STL:
- self.wrapped.exportStl(fileName)
- elif fileFormat == ExportFormats.BREP:
- self.wrapped.exportBrep(fileName)
- elif fileFormat == ExportFormats.STEP:
- self.wrapped.exportStep(fileName)
- elif fileFormat == ExportFormats.AMF:
- # not built into FreeCAD
- #TODO: user selected tolerance
- tess = self.wrapped.tessellate(0.1)
- aw = amfUtils.AMFWriter(tess)
- aw.writeAmf(fileName)
- elif fileFormat == ExportFormats.IGES:
- self.wrapped.exportIges(fileName)
- else:
- raise ValueError("Unknown export format: %s" % format)
-
- def geomType(self):
- """
- Gets the underlying geometry type
- :return: a string according to the geometry type.
-
- Implementations can return any values desired, but the
- values the user uses in type filters should correspond to these.
-
- As an example, if a user does::
-
- CQ(object).faces("%mytype")
-
- The expectation is that the geomType attribute will return 'mytype'
-
- The return values depend on the type of the shape:
-
- Vertex: always 'Vertex'
- Edge: LINE, ARC, CIRCLE, SPLINE
- Face: PLANE, SPHERE, CONE
- Solid: 'Solid'
- Shell: 'Shell'
- Compound: 'Compound'
- Wire: 'Wire'
- """
- return self.wrapped.ShapeType
-
- def isType(self, obj, strType):
- """
- Returns True if the shape is the specified type, false otherwise
-
- contrast with ShapeType, which will raise an exception
- if the provide object is not a shape at all
- """
- if hasattr(obj, 'ShapeType'):
- return obj.ShapeType == strType
- else:
- return False
-
- def hashCode(self):
- return self.wrapped.hashCode()
-
- def isNull(self):
- return self.wrapped.isNull()
-
- def isSame(self, other):
- return self.wrapped.isSame(other.wrapped)
-
- def isEqual(self, other):
- return self.wrapped.isEqual(other.wrapped)
-
- def isValid(self):
- return self.wrapped.isValid()
-
- def BoundingBox(self, tolerance=0.1):
- self.wrapped.tessellate(tolerance)
- return BoundBox(self.wrapped.BoundBox)
-
- def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
- if mirrorPlane == "XY" or mirrorPlane== "YX":
- mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 0, 1)
- elif mirrorPlane == "XZ" or mirrorPlane == "ZX":
- mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 1, 0)
- elif mirrorPlane == "YZ" or mirrorPlane == "ZY":
- mirrorPlaneNormalVector = FreeCAD.Base.Vector(1, 0, 0)
-
- if type(basePointVector) == tuple:
- basePointVector = Vector(basePointVector)
-
- return Shape.cast(self.wrapped.mirror(basePointVector.wrapped, mirrorPlaneNormalVector))
-
- def Center(self):
- # A Part.Shape object doesn't have the CenterOfMass function, but it's wrapped Solid(s) does
- if isinstance(self.wrapped, FreeCADPart.Shape):
- # If there are no Solids, we're probably dealing with a Face or something similar
- if len(self.Solids()) == 0:
- return Vector(self.wrapped.CenterOfMass)
- elif len(self.Solids()) == 1:
- return Vector(self.Solids()[0].wrapped.CenterOfMass)
- elif len(self.Solids()) > 1:
- return self.CombinedCenter(self.Solids())
- elif isinstance(self.wrapped, FreeCADPart.Solid):
- return Vector(self.wrapped.CenterOfMass)
- else:
- raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped)))
-
- def CenterOfBoundBox(self, tolerance = 0.1):
- self.wrapped.tessellate(tolerance)
- if isinstance(self.wrapped, FreeCADPart.Shape):
- # If there are no Solids, we're probably dealing with a Face or something similar
- if len(self.Solids()) == 0:
- return Vector(self.wrapped.BoundBox.Center)
- elif len(self.Solids()) == 1:
- return Vector(self.Solids()[0].wrapped.BoundBox.Center)
- elif len(self.Solids()) > 1:
- return self.CombinedCenterOfBoundBox(self.Solids())
- elif isinstance(self.wrapped, FreeCADPart.Solid):
- return Vector(self.wrapped.BoundBox.Center)
- else:
- raise ValueError("Cannot find the center(BoundBox's) of %s object type" % str(type(self.Solids()[0].wrapped)))
-
- @staticmethod
- def CombinedCenter(objects):
- """
- Calculates the center of mass of multiple objects.
-
- :param objects: a list of objects with mass
- """
- total_mass = sum(Shape.computeMass(o) for o in objects)
- weighted_centers = [o.wrapped.CenterOfMass.multiply(Shape.computeMass(o)) for o in objects]
-
- sum_wc = weighted_centers[0]
- for wc in weighted_centers[1:] :
- sum_wc = sum_wc.add(wc)
-
- return Vector(sum_wc.multiply(1./total_mass))
-
- @staticmethod
- def computeMass(object):
- """
- Calculates the 'mass' of an object. in FreeCAD < 15, all objects had a mass.
- in FreeCAD >=15, faces no longer have mass, but instead have area.
- """
- if object.wrapped.ShapeType == 'Face':
- return object.wrapped.Area
- else:
- return object.wrapped.Mass
-
- @staticmethod
- def CombinedCenterOfBoundBox(objects, tolerance = 0.1):
- """
- Calculates the center of BoundBox of multiple objects.
-
- :param objects: a list of objects with mass 1
- """
- total_mass = len(objects)
-
- weighted_centers = []
- for o in objects:
- o.wrapped.tessellate(tolerance)
- weighted_centers.append(o.wrapped.BoundBox.Center.multiply(1.0))
-
- sum_wc = weighted_centers[0]
- for wc in weighted_centers[1:] :
- sum_wc = sum_wc.add(wc)
-
- return Vector(sum_wc.multiply(1./total_mass))
-
- def Closed(self):
- return self.wrapped.Closed
-
- def ShapeType(self):
- return self.wrapped.ShapeType
-
- def Vertices(self):
- return [Vertex(i) for i in self.wrapped.Vertexes]
-
- def Edges(self):
- return [Edge(i) for i in self.wrapped.Edges]
-
- def Compounds(self):
- return [Compound(i) for i in self.wrapped.Compounds]
-
- def Wires(self):
- return [Wire(i) for i in self.wrapped.Wires]
-
- def Faces(self):
- return [Face(i) for i in self.wrapped.Faces]
-
- def Shells(self):
- return [Shell(i) for i in self.wrapped.Shells]
-
- def Solids(self):
- return [Solid(i) for i in self.wrapped.Solids]
-
- def Area(self):
- return self.wrapped.Area
-
- def Length(self):
- return self.wrapped.Length
-
- def rotate(self, startVector, endVector, angleDegrees):
- """
- Rotates a shape around an axis
- :param startVector: start point of rotation axis either a 3-tuple or a Vector
- :param endVector: end point of rotation axis, either a 3-tuple or a Vector
- :param angleDegrees: angle to rotate, in degrees
- :return: a copy of the shape, rotated
- """
- if type(startVector) == tuple:
- startVector = Vector(startVector)
-
- if type(endVector) == tuple:
- endVector = Vector(endVector)
-
- tmp = self.wrapped.copy()
- tmp.rotate(startVector.wrapped, endVector.wrapped, angleDegrees)
- return Shape.cast(tmp)
-
- def translate(self, vector):
-
- if type(vector) == tuple:
- vector = Vector(vector)
- tmp = self.wrapped.copy()
- tmp.translate(vector.wrapped)
- return Shape.cast(tmp)
-
- def scale(self, factor):
- tmp = self.wrapped.copy()
- tmp.scale(factor)
- return Shape.cast(tmp)
-
- def copy(self):
- return Shape.cast(self.wrapped.copy())
-
- def transformShape(self, tMatrix):
- """
- tMatrix is a matrix object.
- returns a copy of the ojbect, transformed by the provided matrix,
- with all objects keeping their type
- """
- tmp = self.wrapped.copy()
- tmp.transformShape(tMatrix)
- r = Shape.cast(tmp)
- r.forConstruction = self.forConstruction
- return r
-
- def transformGeometry(self, tMatrix):
- """
- tMatrix is a matrix object.
-
- returns a copy of the object, but with geometry transformed insetad of just
- rotated.
-
- WARNING: transformGeometry will sometimes convert lines and circles to splines,
- but it also has the ability to handle skew and stretching transformations.
-
- If your transformation is only translation and rotation, it is safer to use transformShape,
- which doesnt change the underlying type of the geometry, but cannot handle skew transformations
- """
- tmp = self.wrapped.copy()
- tmp = tmp.transformGeometry(tMatrix)
- return Shape.cast(tmp)
-
- def __hash__(self):
- return self.wrapped.hashCode()
-
-
-class Vertex(Shape):
- """
- A Single Point in Space
- """
-
- def __init__(self, obj, forConstruction=False):
- """
- Create a vertex from a FreeCAD Vertex
- """
- self.wrapped = obj
- self.forConstruction = forConstruction
- self.X = obj.X
- self.Y = obj.Y
- self.Z = obj.Z
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- def toTuple(self):
- return (self.X, self.Y, self.Z)
-
- def Center(self):
- """
- The center of a vertex is itself!
- """
- return Vector(self.wrapped.Point)
-
-
-class Edge(Shape):
- """
- A trimmed curve that represents the border of a face
- """
-
- def __init__(self, obj):
- """
- An Edge
- """
- self.wrapped = obj
- # self.startPoint = None
- # self.endPoint = None
-
- self.edgetypes = {
- 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 = ""
-
- def geomType(self):
- t = type(self.wrapped.Curve)
- if self.edgetypes.has_key(t):
- return self.edgetypes[t]
- else:
- return "Unknown Edge Curve Type: %s" % str(t)
-
- def startPoint(self):
- """
-
- :return: a vector representing the start poing of this edge
-
- Note, circles may have the start and end points the same
- """
- # work around freecad bug where valueAt is unreliable
- curve = self.wrapped.Curve
- return Vector(curve.value(self.wrapped.ParameterRange[0]))
-
- def endPoint(self):
- """
-
- :return: a vector representing the end point of this edge.
-
- Note, circles may have the start and end points the same
-
- """
- # warning: easier syntax in freecad of .valueAt(.ParameterRange[1]) has
- # a bug with curves other than arcs, but using the underlying curve directly seems to work
- # that's the solution i'm using below
- curve = self.wrapped.Curve
- v = Vector(curve.value(self.wrapped.ParameterRange[1]))
- return v
-
- def tangentAt(self, locationVector=None):
- """
- Compute tangent vector at the specified location.
- :param locationVector: location to use. Use the center point if None
- :return: tangent vector
- """
- if locationVector is None:
- locationVector = self.Center()
-
- p = self.wrapped.Curve.parameter(locationVector.wrapped)
- return Vector(self.wrapped.tangentAt(p))
-
- @classmethod
- def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
- center = Vector(pnt)
- normal = Vector(dir)
- return Edge(FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped, angle1, angle2))
-
- @classmethod
- def makeSpline(cls, listOfVector):
- """
- Interpolate a spline through the provided points.
- :param cls:
- :param listOfVector: a list of Vectors that represent the points
- :return: an Edge
- """
- vecs = [v.wrapped for v in listOfVector]
-
- spline = FreeCADPart.BSplineCurve()
- spline.interpolate(vecs, False)
- return Edge(spline.toShape())
-
- @classmethod
- def makeThreePointArc(cls, v1, v2, v3):
- """
- Makes a three point arc through the provided points
- :param cls:
- :param v1: start vector
- :param v2: middle vector
- :param v3: end vector
- :return: an edge object through the three points
- """
- arc = FreeCADPart.Arc(v1.wrapped, v2.wrapped, v3.wrapped)
- e = Edge(arc.toShape())
- return e # arcane and undocumented, this creates an Edge object
-
- @classmethod
- def makeLine(cls, v1, v2):
- """
- Create a line between two points
- :param v1: Vector that represents the first point
- :param v2: Vector that represents the second point
- :return: A linear edge between the two provided points
- """
- return Edge(FreeCADPart.makeLine(v1.toTuple(), v2.toTuple()))
-
-
-class Wire(Shape):
- """
- A series of connected, ordered Edges, that typically bounds a Face
- """
-
- def __init__(self, obj):
- """
- A Wire
- """
- self.wrapped = obj
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- @classmethod
- def combine(cls, listOfWires):
- """
- Attempt to combine a list of wires into a new wire.
- the wires are returned in a list.
- :param cls:
- :param listOfWires:
- :return:
- """
- return Shape.cast(FreeCADPart.Wire([w.wrapped for w in listOfWires]))
-
- @classmethod
- def assembleEdges(cls, listOfEdges):
- """
- Attempts to build a wire that consists of the edges in the provided list
- :param cls:
- :param listOfEdges: a list of Edge objects
- :return: a wire with the edges assembled
- """
- fCEdges = [a.wrapped for a in listOfEdges]
-
- wa = Wire(FreeCADPart.Wire(fCEdges))
- return wa
-
- @classmethod
- def makeCircle(cls, radius, center, normal):
- """
- Makes a Circle centered at the provided point, having normal in the provided direction
- :param radius: floating point radius of the circle, must be > 0
- :param center: vector representing the center of the circle
- :param normal: vector representing the direction of the plane the circle should lie in
- :return:
- """
- 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(FreeCADPart.makePolygon([i.wrapped for i in listOfVertices]))
- w.forConstruction = forConstruction
- return w
-
- @classmethod
- def makeHelix(cls, pitch, height, radius, angle=360.0):
- """
- Make a helix with a given pitch, height and radius
- 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(FreeCADPart.makeHelix(pitch, height, radius, angle))
-
- def clean(self):
- """This method is not implemented yet."""
- return self
-
-class Face(Shape):
- """
- a bounded surface that represents part of the boundary of a solid
- """
- def __init__(self, obj):
-
- self.wrapped = obj
-
- self.facetypes = {
- # TODO: bezier,bspline etc
- FreeCADPart.Plane: 'PLANE',
- FreeCADPart.Sphere: 'SPHERE',
- FreeCADPart.Cone: 'CONE'
- }
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- def geomType(self):
- t = type(self.wrapped.Surface)
- if self.facetypes.has_key(t):
- return self.facetypes[t]
- else:
- return "Unknown Face Surface Type: %s" % str(t)
-
- def normalAt(self, locationVector=None):
- """
- Computes the normal vector at the desired location on the face.
-
- :returns: a vector representing the direction
- :param locationVector: the location to compute the normal at. If none, the center of the face is used.
- :type locationVector: a vector that lies on the surface.
- """
- if locationVector == None:
- locationVector = self.Center()
- (u, v) = self.wrapped.Surface.parameter(locationVector.wrapped)
-
- return Vector(self.wrapped.normalAt(u, v).normalize())
-
- @classmethod
- def makePlane(cls, length, width, basePnt=(0, 0, 0), dir=(0, 0, 1)):
- basePnt = Vector(basePnt)
- dir = Vector(dir)
- return Face(FreeCADPart.makePlane(length, width, basePnt.wrapped, dir.wrapped))
-
- @classmethod
- def makeRuledSurface(cls, edgeOrWire1, edgeOrWire2, dist=None):
- """
- 'makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface
- Create a ruled surface out of two edges or wires. If wires are used then
- these must have the same
- """
- return Shape.cast(FreeCADPart.makeRuledSurface(edgeOrWire1.obj, edgeOrWire2.obj, dist))
-
- def cut(self, faceToCut):
- "Remove a face from another one"
- return Shape.cast(self.obj.cut(faceToCut.obj))
-
- def fuse(self, faceToJoin):
- return Shape.cast(self.obj.fuse(faceToJoin.obj))
-
- def intersect(self, faceToIntersect):
- """
- computes the intersection between the face and the supplied one.
- The result could be a face or a compound of faces
- """
- return Shape.cast(self.obj.common(faceToIntersect.obj))
-
-
-class Shell(Shape):
- """
- the outer boundary of a surface
- """
- def __init__(self, wrapped):
- """
- A Shell
- """
- self.wrapped = wrapped
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- @classmethod
- def makeShell(cls, listOfFaces):
- return Shell(FreeCADPart.makeShell([i.obj for i in listOfFaces]))
-
-
-class Solid(Shape):
- """
- a single solid
- """
- def __init__(self, obj):
- """
- A Solid
- """
- self.wrapped = obj
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- @classmethod
- def isSolid(cls, obj):
- """
- Returns true if the object is a FreeCAD solid, false otherwise
- """
- if hasattr(obj, 'ShapeType'):
- if obj.ShapeType == 'Solid' or \
- (obj.ShapeType == 'Compound' and len(obj.Solids) > 0):
- return True
- return False
-
- @classmethod
- def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)):
- """
- makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height)
- By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
- """
- 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):
- """
- Make a cone with given radii and height
- By default pnt=Vector(0,0,0),
- dir=Vector(0,0,1) and angle=360'
- """
- 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):
- """
- makeCylinder(radius,height,[pnt,dir,angle]) --
- 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(FreeCADPart.makeCylinder(radius, height, pnt.wrapped, dir.wrapped, angleDegrees))
-
- @classmethod
- def makeTorus(cls, radius1, radius2, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None):
- """
- makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) --
- Make a torus with agiven radii and angles
- By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0
- ,angle1=360 and angle=360'
- """
- return Shape.cast(FreeCADPart.makeTorus(radius1, radius2, pnt, dir, angleDegrees1, angleDegrees2))
-
- @classmethod
- def sweep(cls, profileWire, pathWire):
- """
- make a solid by sweeping the profileWire along the specified path
- :param cls:
- :param profileWire:
- :param pathWire:
- :return:
- """
- # needs to use freecad wire.makePipe or makePipeShell
- # needs to allow free-space wires ( those not made from a workplane )
-
- @classmethod
- def makeLoft(cls, listOfWire, ruled=False):
- """
- 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 FreeCADPart.
- """
- # the True flag requests building a solid instead of a shell.
-
- return Shape.cast(FreeCADPart.makeLoft([i.wrapped for i in listOfWire], True, ruled))
-
- @classmethod
- def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
- """
- Make a wedge located in pnt
- By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)
- """
- return Shape.cast(
- FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
-
- @classmethod
- def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
- """
- Make a sphere with a given radius
- By default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360
- """
- return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
-
- @classmethod
- def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees):
- """
- Creates a 'twisted prism' by extruding, while simultaneously rotating around the extrusion vector.
-
- Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the
- construction methods used here are different enough that they should be separate.
-
- At a high level, the steps followed are:
- (1) accept a set of wires
- (2) create another set of wires like this one, but which are transformed and rotated
- (3) create a ruledSurface between the sets of wires
- (4) create a shell and compute the resulting object
-
- :param outerWire: the outermost wire, a cad.Wire
- :param innerWires: a list of inner wires, a list of cad.Wire
- :param vecCenter: the center point about which to rotate. the axis of rotation is defined by
- vecNormal, located at vecCenter. ( a cad.Vector )
- :param vecNormal: a vector along which to extrude the wires ( a cad.Vector )
- :param angleDegrees: the angle to rotate through while extruding
- :return: a cad.Solid object
- """
-
- # from this point down we are dealing with FreeCAD wires not cad.wires
- startWires = [outerWire.wrapped] + [i.wrapped for i in innerWires]
- endWires = []
- p1 = vecCenter.wrapped
- p2 = vecCenter.add(vecNormal).wrapped
-
- # make translated and rotated copy of each wire
- for w in startWires:
- w2 = w.copy()
- w2.translate(vecNormal.wrapped)
- w2.rotate(p1, p2, angleDegrees)
- endWires.append(w2)
-
- # make a ruled surface for each set of wires
- sides = []
- for w1, w2 in zip(startWires, endWires):
- rs = FreeCADPart.makeRuledSurface(w1, w2)
- sides.append(rs)
-
- #make faces for the top and bottom
- startFace = FreeCADPart.Face(startWires)
- endFace = FreeCADPart.Face(endWires)
-
- #collect all the faces from the sides
- faceList = [startFace]
- for s in sides:
- faceList.extend(s.Faces)
- faceList.append(endFace)
-
- shell = FreeCADPart.makeShell(faceList)
- solid = FreeCADPart.makeSolid(shell)
- return Shape.cast(solid)
-
- @classmethod
- def extrudeLinear(cls, outerWire, innerWires, vecNormal):
- """
- Attempt to extrude the list of wires into a prismatic solid in the provided direction
-
- :param outerWire: the outermost wire
- :param innerWires: a list of inner wires
- :param vecNormal: a vector along which to extrude the wires
- :return: a Solid object
-
- The wires must not intersect
-
- Extruding wires is very non-trivial. Nested wires imply very different geometry, and
- there are many geometries that are invalid. In general, the following conditions must be met:
-
- * all wires must be closed
- * there cannot be any intersecting or self-intersecting wires
- * wires must be listed from outside in
- * more than one levels of nesting is not supported reliably
-
- This method will attempt to sort the wires, but there is much work remaining to make this method
- reliable.
- """
-
- # one would think that fusing faces into a compound and then extruding would work,
- # but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc),
- # but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- #FreeCAD allows this in one operation, but others might not
- freeCADWires = [outerWire.wrapped]
- for w in innerWires:
- freeCADWires.append(w.wrapped)
-
- f = FreeCADPart.Face(freeCADWires)
- result = f.extrude(vecNormal.wrapped)
-
- return Shape.cast(result)
-
- @classmethod
- def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd):
- """
- Attempt to revolve the list of wires into a solid in the provided direction
-
- :param outerWire: the outermost wire
- :param innerWires: a list of inner wires
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :return: a Solid object
-
- The wires must not intersect
-
- * all wires must be closed
- * there cannot be any intersecting or self-intersecting wires
- * wires must be listed from outside in
- * more than one levels of nesting is not supported reliably
- * the wire(s) that you're revolving cannot be centered
-
- This method will attempt to sort the wires, but there is much work remaining to make this method
- reliable.
- """
- freeCADWires = [outerWire.wrapped]
-
- for w in innerWires:
- freeCADWires.append(w.wrapped)
-
- f = FreeCADPart.Face(freeCADWires)
-
- rotateCenter = FreeCAD.Base.Vector(axisStart)
- rotateAxis = FreeCAD.Base.Vector(axisEnd)
-
- #Convert our axis end vector into to something FreeCAD will understand (an axis specification vector)
- rotateAxis = rotateCenter.sub(rotateAxis)
-
- #FreeCAD wants a rotation center and then an axis to rotate around rather than an axis of rotation
- result = f.revolve(rotateCenter, rotateAxis, angleDegrees)
-
- return Shape.cast(result)
-
- @classmethod
- def sweep(cls, outerWire, innerWires, path, makeSolid=True, isFrenet=False):
- """
- Attempt to sweep the list of wires into a prismatic solid along the provided path
-
- :param outerWire: the outermost wire
- :param innerWires: a list of inner wires
- :param path: The wire to sweep the face resulting from the wires over
- :return: a Solid object
- """
-
- # FreeCAD allows this in one operation, but others might not
- freeCADWires = [outerWire.wrapped]
- for w in innerWires:
- freeCADWires.append(w.wrapped)
-
- # f = FreeCADPart.Face(freeCADWires)
- wire = FreeCADPart.Wire([path.wrapped])
- result = wire.makePipeShell(freeCADWires, makeSolid, isFrenet)
-
- return Shape.cast(result)
-
- def tessellate(self, tolerance):
- return self.wrapped.tessellate(tolerance)
-
- def intersect(self, toIntersect):
- """
- computes the intersection between this solid and the supplied one
- The result could be a face or a compound of faces
- """
- return Shape.cast(self.wrapped.common(toIntersect.wrapped))
-
- def cut(self, solidToCut):
- "Remove a solid from another one"
- return Shape.cast(self.wrapped.cut(solidToCut.wrapped))
-
- def fuse(self, solidToJoin):
- return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped))
-
- def clean(self):
- """Clean faces by removing splitter edges."""
- r = self.wrapped.removeSplitter()
- # removeSplitter() returns a generic Shape type, cast to actual type of object
- r = FreeCADPart.cast_to_shape(r)
- return Shape.cast(r)
-
- def fillet(self, radius, edgeList):
- """
- Fillets the specified edges of this solid.
- :param radius: float > 0, the radius of the fillet
- :param edgeList: a list of Edge objects, which must belong to this solid
- :return: Filleted solid
- """
- nativeEdges = [e.wrapped for e in edgeList]
- return Shape.cast(self.wrapped.makeFillet(radius, nativeEdges))
-
- def chamfer(self, length, length2, edgeList):
- """
- Chamfers the specified edges of this solid.
- :param length: length > 0, the length (length) of the chamfer
- :param length2: length2 > 0, optional parameter for asymmetrical chamfer. Should be `None` if not required.
- :param edgeList: a list of Edge objects, which must belong to this solid
- :return: Chamfered solid
- """
- nativeEdges = [e.wrapped for e in edgeList]
- # note: we prefer 'length' word to 'radius' as opposed to FreeCAD's API
- if length2:
- return Shape.cast(self.wrapped.makeChamfer(length, length2, nativeEdges))
- else:
- return Shape.cast(self.wrapped.makeChamfer(length, nativeEdges))
-
- def shell(self, faceList, thickness, tolerance=0.0001):
- """
- make a shelled solid of given by removing the list of faces
-
- :param faceList: list of face objects, which must be part of the solid.
- :param thickness: floating point thickness. positive shells outwards, negative shells inwards
- :param tolerance: modelling tolerance of the method, default=0.0001
- :return: a shelled solid
-
- **WARNING** The underlying FreeCAD implementation can very frequently have problems
- with shelling complex geometries!
- """
- nativeFaces = [f.wrapped for f in faceList]
- return Shape.cast(self.wrapped.makeThickness(nativeFaces, thickness, tolerance))
-
-
-class Compound(Shape):
- """
- a collection of disconnected solids
- """
-
- def __init__(self, obj):
- """
- An Edge
- """
- self.wrapped = obj
-
- # Helps identify this solid through the use of an ID
- self.label = ""
-
- def Center(self):
- return self.Center()
-
- @classmethod
- def makeCompound(cls, listOfShapes):
- """
- Create a compound out of a list of shapes
- """
- solids = [s.wrapped for s in listOfShapes]
- c = FreeCADPart.Compound(solids)
- return Shape.cast(c)
-
- def fuse(self, toJoin):
- return Shape.cast(self.wrapped.fuse(toJoin.wrapped))
-
- def tessellate(self, tolerance):
- return self.wrapped.tessellate(tolerance)
-
- def clean(self):
- """This method is not implemented yet."""
- return self
diff --git a/Libs/cadquery-lib/cadquery/plugins/__init__.py b/Libs/cadquery-lib/cadquery/plugins/__init__.py
deleted file mode 100644
index 3697b9f..0000000
--- a/Libs/cadquery-lib/cadquery/plugins/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- CadQuery
- Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
-
- This library 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.
-
- This library 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, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""
diff --git a/Libs/cadquery-lib/cadquery/selectors.py b/Libs/cadquery-lib/cadquery/selectors.py
deleted file mode 100644
index a2d476e..0000000
--- a/Libs/cadquery-lib/cadquery/selectors.py
+++ /dev/null
@@ -1,663 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- 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
-"""
-
-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
-
-
-class Selector(object):
- """
- Filters a list of objects
-
- Filters must provide a single method that filters objects.
- """
- def filter(self,objectList):
- """
- Filter the provided list
- :param objectList: list to filter
- :type objectList: list of FreeCAD primatives
- :return: filtered list
-
- The default implementation returns the original list unfiltered
-
- """
- return objectList
-
- def __and__(self, other):
- return AndSelector(self, other)
-
- def __add__(self, other):
- return SumSelector(self, other)
-
- def __sub__(self, other):
- return SubtractSelector(self, other)
-
- def __neg__(self):
- return InverseSelector(self)
-
-class NearestToPointSelector(Selector):
- """
- Selects object nearest the provided point.
-
- If the object is a vertex or point, the distance
- is used. For other kinds of shapes, the center of mass
- is used to to compute which is closest.
-
- Applicability: All Types of Shapes
-
- Example::
-
- CQ(aCube).vertices(NearestToPointSelector((0,1,0))
-
- returns the vertex of the unit cube closest to the point x=0,y=1,z=0
-
- """
- def __init__(self,pnt ):
- self.pnt = pnt
- def filter(self,objectList):
-
- def dist(tShape):
- return tShape.Center().sub(Vector(*self.pnt)).Length
- #if tShape.ShapeType == 'Vertex':
- # return tShape.Point.sub(toVector(self.pnt)).Length
- #else:
- # return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
-
- return [ min(objectList,key=dist) ]
-
-class BoxSelector(Selector):
- """
- Selects objects inside the 3D box defined by 2 points.
-
- If `boundingbox` is True only the objects that have their bounding
- box inside the given box is selected. Otherwise only center point
- of the object is tested.
-
- Applicability: all types of shapes
-
- Example::
-
- CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
- """
- def __init__(self, point0, point1, boundingbox=False):
- self.p0 = Vector(*point0)
- self.p1 = Vector(*point1)
- self.test_boundingbox = boundingbox
-
- def filter(self, objectList):
-
- result = []
- x0, y0, z0 = self.p0.toTuple()
- x1, y1, z1 = self.p1.toTuple()
-
- def isInsideBox(p):
- # using XOR for checking if x/y/z is in between regardless
- # of order of x/y/z0 and x/y/z1
- return ((p.x < x0) ^ (p.x < x1)) and \
- ((p.y < y0) ^ (p.y < y1)) and \
- ((p.z < z0) ^ (p.z < z1))
-
- for o in objectList:
- if self.test_boundingbox:
- bb = o.BoundingBox()
- if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
- isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
- result.append(o)
- else:
- if isInsideBox(o.Center()):
- result.append(o)
-
- return result
-
-class BaseDirSelector(Selector):
- """
- A selector that handles selection on the basis of a single
- direction vector
- """
- def __init__(self,vector,tolerance=0.0001 ):
- self.direction = vector
- self.TOLERANCE = tolerance
-
- def test(self,vec):
- "Test a specified vector. Subclasses override to provide other implementations"
- return True
-
- def filter(self,objectList):
- """
- There are lots of kinds of filters, but
- for planes they are always based on the normal of the plane,
- and for edges on the tangent vector along the edge
- """
- r = []
- for o in objectList:
- #no really good way to avoid a switch here, edges and faces are simply different!
-
- if type(o) == Face:
- # a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
- normal = o.normalAt(None)
-
- if self.test(normal):
- r.append(o)
- elif type(o) == Edge and o.geomType() == 'LINE':
- #an edge is parallel to a direction if it is a line, and the line is parallel to the dir
- tangent = o.tangentAt(None)
- if self.test(tangent):
- r.append(o)
-
- return r
-
-class ParallelDirSelector(BaseDirSelector):
- """
- Selects objects parallel with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut \|(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(ParallelDirSelector((0,0,1))
-
- selects faces with a normals in the z direction, and is equivalent to::
-
- CQ(aCube).faces("|Z")
- """
-
- def test(self,vec):
- return self.direction.cross(vec).Length < self.TOLERANCE
-
-class DirectionSelector(BaseDirSelector):
- """
- Selects objects aligned with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut +/-(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(DirectionSelector((0,0,1))
-
- selects faces with a normals in the z direction, and is equivalent to::
-
- CQ(aCube).faces("+Z")
- """
-
- def test(self,vec):
- return abs(self.direction.getAngle(vec) < self.TOLERANCE)
-
-class PerpendicularDirSelector(BaseDirSelector):
- """
- Selects objects perpendicular with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut #(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(PerpendicularDirSelector((0,0,1))
-
- selects faces with a normals perpendicular to the z direction, and is equivalent to::
-
- CQ(aCube).faces("#Z")
- """
-
- def test(self,vec):
- angle = self.direction.getAngle(vec)
- r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE )
- return not r
-
-
-class TypeSelector(Selector):
- """
- Selects objects of the prescribed topological type.
-
- Applicability:
- Faces: Plane,Cylinder,Sphere
- Edges: Line,Circle,Arc
-
- You can use the shortcut selector %(PLANE|SPHERE|CONE) for faces,
- and %(LINE|ARC|CIRCLE) for edges.
-
- For example this::
-
- CQ(aCube).faces ( TypeSelector("PLANE") )
-
- will select 6 faces, and is equivalent to::
-
- CQ(aCube).faces( "%PLANE" )
-
- """
- def __init__(self,typeString):
- self.typeString = typeString.upper()
-
- def filter(self,objectList):
- r = []
- for o in objectList:
- if o.geomType() == self.typeString:
- r.append(o)
- return r
-
-class DirectionMinMaxSelector(Selector):
- """
- Selects objects closest or farthest in the specified direction
- Used for faces, points, and edges
-
- Applicability:
- All object types. for a vertex, its point is used. for all other kinds
- of objects, the center of mass of the object is used.
-
- You can use the string shortcuts >(X|Y|Z) or <(X|Y|Z) if you want to
- select based on a cardinal direction.
-
- For example this::
-
- CQ(aCube).faces ( DirectionMinMaxSelector((0,0,1),True )
-
- Means to select the face having the center of mass farthest in the positive z direction,
- and is the same as:
-
- CQ(aCube).faces( ">Z" )
-
- """
- def __init__(self, vector, directionMax=True, tolerance=0.0001):
- self.vector = vector
- self.max = max
- self.directionMax = directionMax
- self.TOLERANCE = tolerance
- def filter(self,objectList):
-
- def distance(tShape):
- return tShape.Center().dot(self.vector)
-
- # import OrderedDict
- from collections import OrderedDict
- #make and distance to object dict
- objectDict = {distance(el) : el for el in objectList}
- #transform it into an ordered dict
- objectDict = OrderedDict(sorted(objectDict.items(),
- key=lambda x: x[0]))
-
- # find out the max/min distance
- if self.directionMax:
- d = objectDict.keys()[-1]
- else:
- d = objectDict.keys()[0]
-
- # return all objects at the max/min distance (within a tolerance)
- return filter(lambda o: abs(d - distance(o)) < self.TOLERANCE, objectList)
-
-class DirectionNthSelector(ParallelDirSelector):
- """
- Selects nth object parallel (or normal) to the specified direction
- Used for faces and edges
-
- Applicability:
- Linear Edges
- Planar Faces
- """
- def __init__(self, vector, n, directionMax=True, tolerance=0.0001):
- self.direction = vector
- self.max = max
- self.directionMax = directionMax
- self.TOLERANCE = tolerance
- 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)
-
- #calculate how many digits of precision do we need
- digits = int(1/self.TOLERANCE)
-
- #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_distance = sorted(objectDict.keys(),
- reverse=not self.directionMax)[self.N]
-
- # map back to original objects and return
- return objectDict[nth_distance]
-
-class BinarySelector(Selector):
- """
- Base class for selectors that operates with two other
- selectors. Subclass must implement the :filterResults(): method.
- """
- def __init__(self, left, right):
- self.left = left
- self.right = right
-
- def filter(self, objectList):
- return self.filterResults(self.left.filter(objectList),
- self.right.filter(objectList))
-
- def filterResults(self, r_left, r_right):
- raise NotImplementedError
-
-class AndSelector(BinarySelector):
- """
- Intersection selector. Returns objects that is selected by both selectors.
- """
- def filterResults(self, r_left, r_right):
- # return intersection of lists
- return list(set(r_left) & set(r_right))
-
-class SumSelector(BinarySelector):
- """
- Union selector. Returns the sum of two selectors results.
- """
- def filterResults(self, r_left, r_right):
- # return the union (no duplicates) of lists
- return list(set(r_left + r_right))
-
-class SubtractSelector(BinarySelector):
- """
- Difference selector. Substract results of a selector from another
- selectors results.
- """
- def filterResults(self, r_left, r_right):
- return list(set(r_left) - set(r_right))
-
-class InverseSelector(Selector):
- """
- Inverts the selection of given selector. In other words, selects
- all objects that is not selected by given selector.
- """
- def __init__(self, selector):
- self.selector = selector
-
- def filter(self, objectList):
- # note that Selector() selects everything
- return SubtractSelector(Selector(), self.selector).filter(objectList)
-
-
-def _makeGrammar():
- """
- Define the simple string selector grammar using PyParsing
- """
-
- #float definition
- point = Literal('.')
- plusmin = Literal('+') | Literal('-')
- number = Word(nums)
- integer = Combine(Optional(plusmin) + number)
- floatn = Combine(integer + Optional(point + Optional(number)))
-
- #vector definition
- lbracket = Literal('(')
- rbracket = Literal(')')
- comma = Literal(',')
- vector = Combine(lbracket + floatn('x') + comma + \
- floatn('y') + comma + floatn('z') + rbracket)
-
- #direction definition
- simple_dir = oneOf(['X','Y','Z','XY','XZ','YZ'])
- direction = simple_dir('simple_dir') | vector('vector_dir')
-
- #CQ type definition
- cqtype = oneOf(['Plane','Cylinder','Sphere','Cone','Line','Circle','Arc'],
- caseless=True)
- cqtype = cqtype.setParseAction(upcaseTokens)
-
- #type operator
- type_op = Literal('%')
-
- #direction operator
- direction_op = oneOf(['>','<'])
-
- #index definition
- ix_number = Group(Optional('-')+Word(nums))
- lsqbracket = Literal('[').suppress()
- rsqbracket = Literal(']').suppress()
-
- index = lsqbracket + ix_number('index') + rsqbracket
-
- #other operators
- other_op = oneOf(['|','#','+','-'])
-
- #named view
- named_view = oneOf(['front','back','left','right','top','bottom'])
-
- return direction('only_dir') | \
- (type_op('type_op') + cqtype('cq_type')) | \
- (direction_op('dir_op') + direction('dir') + Optional(index)) | \
- (other_op('other_op') + direction('dir')) | \
- named_view('named_view')
-
-_grammar = _makeGrammar() #make a grammar instance
-
-class _SimpleStringSyntaxSelector(Selector):
- """
- This is a private class that converts a parseResults object into a simple
- selector object
- """
- def __init__(self,parseResults):
-
- #define all token to object mappings
- self.axes = {
- 'X': Vector(1,0,0),
- 'Y': Vector(0,1,0),
- 'Z': Vector(0,0,1),
- 'XY': Vector(1,1,0),
- 'YZ': Vector(0,1,1),
- 'XZ': Vector(1,0,1)
- }
-
- self.namedViews = {
- 'front' : (Vector(0,0,1),True),
- 'back' : (Vector(0,0,1),False),
- 'left' : (Vector(1,0,0),False),
- 'right' : (Vector(1,0,0),True),
- 'top' : (Vector(0,1,0),True),
- 'bottom': (Vector(0,1,0),False)
- }
-
- self.operatorMinMax = {
- '>' : True,
- '<' : False,
- '+' : True,
- '-' : False
- }
-
- self.operator = {
- '+' : DirectionSelector,
- '-' : DirectionSelector,
- '#' : PerpendicularDirSelector,
- '|' : ParallelDirSelector}
-
- self.parseResults = parseResults
- self.mySelector = self._chooseSelector(parseResults)
-
- def _chooseSelector(self,pr):
- """
- Sets up the underlying filters accordingly
- """
- if 'only_dir' in pr:
- vec = self._getVector(pr)
- return DirectionSelector(vec)
-
- elif 'type_op' in pr:
- return TypeSelector(pr.cq_type)
-
- elif 'dir_op' in pr:
- vec = self._getVector(pr)
- minmax = self.operatorMinMax[pr.dir_op]
-
- if 'index' in pr:
- return DirectionNthSelector(vec,int(''.join(pr.index.asList())),minmax)
- else:
- return DirectionMinMaxSelector(vec,minmax)
-
- elif 'other_op' in pr:
- vec = self._getVector(pr)
- return self.operator[pr.other_op](vec)
-
- else:
- args = self.namedViews[pr.named_view]
- return DirectionMinMaxSelector(*args)
-
- def _getVector(self,pr):
- """
- Translate parsed vector string into a CQ Vector
- """
- if 'vector_dir' in pr:
- vec = pr.vector_dir
- return Vector(float(vec.x),float(vec.y),float(vec.z))
- else:
- return self.axes[pr.simple_dir]
-
- def filter(self,objectList):
- """
- selects minimum, maximum, positive or negative values relative to a direction
- [+\|-\|<\|>\|] \
- """
- return self.mySelector.filter(objectList)
-
-def _makeExpressionGrammar(atom):
- """
- Define the complex string selector grammar using PyParsing (which supports
- logical operations and nesting)
- """
-
- #define operators
- and_op = Literal('and')
- or_op = Literal('or')
- delta_op = oneOf(['exc','except'])
- not_op = Literal('not')
-
- def atom_callback(res):
- return _SimpleStringSyntaxSelector(res)
-
- atom.setParseAction(atom_callback) #construct a simple selector from every matched
-
- #define callback functions for all operations
- def and_callback(res):
- items = res.asList()[0][::2] #take every secend items, i.e. all operands
- return reduce(AndSelector,items)
-
- def or_callback(res):
- items = res.asList()[0][::2] #take every secend items, i.e. all operands
- return reduce(SumSelector,items)
-
- def exc_callback(res):
- items = res.asList()[0][::2] #take every secend items, i.e. all operands
- return reduce(SubtractSelector,items)
-
- def not_callback(res):
- right = res.asList()[0][1] #take second item, i.e. the operand
- return InverseSelector(right)
-
- #construct the final grammar and set all the callbacks
- expr = infixNotation(atom,
- [(and_op,2,opAssoc.LEFT,and_callback),
- (or_op,2,opAssoc.LEFT,or_callback),
- (delta_op,2,opAssoc.LEFT,exc_callback),
- (not_op,1,opAssoc.RIGHT,not_callback)])
-
- return expr
-
-_expression_grammar = _makeExpressionGrammar(_grammar)
-
-class StringSyntaxSelector(Selector):
- """
- Filter lists objects using a simple string syntax. All of the filters available in the string syntax
- are also available ( usually with more functionality ) through the creation of full-fledged
- selector objects. see :py:class:`Selector` and its subclasses
-
- Filtering works differently depending on the type of object list being filtered.
-
- :param selectorString: A two-part selector string, [selector][axis]
-
- :return: objects that match the specified selector
-
- ***Modfiers*** are ``('|','+','-','<','>','%')``
-
- :\|:
- parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
- :#:
- perpendicular to (same as :py:class:`PerpendicularDirSelector` )
- :+:
- positive direction (same as :py:class:`DirectionSelector` )
- :-:
- negative direction (same as :py:class:`DirectionSelector` )
- :>:
- maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
- :<:
- minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
- :%:
- curve/surface type (same as :py:class:`TypeSelector`)
-
- ***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ`` or ``(x,y,z)`` which defines an arbitrary direction
-
- It is possible to combine simple selectors together using logical operations.
- The following operations are suuported
-
- :and:
- Logical AND, e.g. >X and >Y
- :or:
- Logical OR, e.g. |X or |Y
- :not:
- Logical NOT, e.g. not #XY
- :exc(ept):
- Set difference (equivalent to AND NOT): |X exc >Z
-
- Finally, it is also possible to use even more complex expressions with nesting
- and arbitrary number of terms, e.g.
-
- (not >X[0] and #XY) or >XY[0]
-
- Selectors are a complex topic: see :ref:`selector_reference` for more information
- """
- def __init__(self,selectorString):
- """
- Feed the input string through the parser and construct an relevant complex selector object
- """
- self.selectorString = selectorString
- parse_result = _expression_grammar.parseString(selectorString,
- parseAll=True)
- self.mySelector = parse_result.asList()[0]
-
- def filter(self,objectList):
- """
- Filter give object list through th already constructed complex selector object
- """
- return self.mySelector.filter(objectList)
\ No newline at end of file
diff --git a/Libs/cadquery-lib/changes.md b/Libs/cadquery-lib/changes.md
deleted file mode 100644
index 24d08ab..0000000
--- a/Libs/cadquery-lib/changes.md
+++ /dev/null
@@ -1,100 +0,0 @@
-Changes
-=======
-
-
-v0.1
------
- * Initial Version
-
-v0.1.6
------
- * Added STEP import and supporting tests
-
-v0.1.7
------
- * Added revolve operation and supporting tests
- * Fixed minor documentation errors
-
-v0.1.8
------
- * Added toFreecad() function as a convenience for val().wrapped
- * Converted all examples to use toFreecad()
- * Updated all version numbers that were missed before
- * Fixed import issues in Windows caused by fc_import
- * Added/fixed Mac OS support
- * Improved STEP import
- * Fixed bug in rotateAboutCenter that negated its effect on solids
- * Added Travis config (thanks @krasin)
- * Removed redundant workplane.py file left over from the PParts.com migration
- * Fixed toWorldCoordinates bug in moveTo (thanks @xix-xeaon)
- * Added new tests for 2D drawing functions
- * Integrated Coveralls.io, with a badge in README.md
- * Integrated version badge in README.md
-
-v0.2.0
------
- * Fixed versioning to match the semantic versioning scheme
- * Added license badge in changes.md
- * Fixed Solid.makeSphere implementation
- * Added CQ.sphere operation that mirrors CQ.box
- * Updated copyright dates
- * Cleaned up spelling and misc errors in docstrings
- * Fixed FreeCAD import error on Arch Linux (thanks @moeb)
- * Made FreeCAD import report import error instead of silently failing (thanks @moeb)
- * Added ruled option for the loft operation (thanks @hyOzd)
- * Fixed close() not working in planes other than XY (thanks @hyOzd)
- * Added box selector with bounding box option (thanks @hyOzd)
- * CQ.translate and CQ.rotate documentation fixes (thanks @hyOzd)
- * Fixed centering of a sphere
- * Increased test coverage
- * Added a clean function to keep some operations from failing on solids that need simplified (thanks @hyOzd)
- * Added a mention of the new Google Group to the readme
-
-v0.3.0
------
- * Fixed a bug where clean() could not be called on appropriate objects other than solids (thanks @hyOzd) #108
- * Implemented new selectors that allow existing selectors to be combined with arithmetic/boolean operations (thanks @hyOzd) #110
- * Fixed a bug where only 1 random edge was returned with multiple min/max selector matches (thanks @hyOzd) #111
- * Implemented the creation of a workplane from multiple co-planar faces (thanks @hyOzd) #113
- * Fixed the operation of Center() when called on a compound with multiple solids
- * Add the named planes ZX YX ZY to define different normals (thanks @galou) #115
- * Code cleanup in accordance with PEP 8 (thanks @galou)
- * Fixed a bug with the close function not resetting the first point of the context correctly (thanks @huskier)
- * Fixed the findSolid function so that it handles compounds #107
- * Changed the polyline function so that it adds edges to the stack instead of a wire #102
- * Add the ability to find the center of the bounding box, rather than the center of mass (thanks @huskier) #122
- * Changed normalize function to normalized to match OCC/PythonOCC nomenclature #124
- * Added a label attribute to all freecad_impl.shapes so that they can have IDs attached to them #124
-
-v0.4.0
-------
- * Added Documentation, which is available on dcowden.github.io/cadquery
- * Added CQGI, an adapter API that standardizes use of cadquery from within structured execution environments
- * Added ability to import STEP files from a web URL (thanks @huskier ) #128
-
-v0.4.1
-------
- * Minor CQGI updates
-
-v0.5.0-stable
-------
- * Configuring Travis to push to PyPI on version releases.
-
-v0.5.1
-------
- * Mirroring fixes (thanks @huskier)
- * Added a mirroring example (thanks @huskier)
-
-v0.5.2
-------
- * Added the sweep operation #33
-
-v1.0.0
-------
- * Added an option to do symmetric extrusion about the workplane (thanks @adam-urbanczyk)
- * Extended selector syntax to include Nth selector and re-implemented selectors using pyparsing (thanks @adam-urbanczyk)
- * Added logical operations to string selectors (thanks @adam-urbanczyk)
- * Cleanup of README.md and changes.md (thanks @baoboa)
- * Fixed bugs with toVector and Face 'Not Defined' errors (thanks @huskier)
- * Refactor of the initialization code for PEP8 compliance and Python 3 compatibility (thanks @Peque)
- * Making sure that the new pyparsing library dependency is handled properly (thanks @Peque)
diff --git a/Libs/cadquery-lib/doc/README b/Libs/cadquery-lib/doc/README
deleted file mode 100644
index b89ab3d..0000000
--- a/Libs/cadquery-lib/doc/README
+++ /dev/null
@@ -1,2 +0,0 @@
-This documentation should be generated with sphinxdoc.
-see ../build-docs.sh
diff --git a/Libs/cadquery-lib/doc/_static/ParametricPulley.PNG b/Libs/cadquery-lib/doc/_static/ParametricPulley.PNG
deleted file mode 100644
index c5e6e873038fd3beb443fa8bfd146c649578e126..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 59186
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