Updated CadQuery library and updated version number to 1.0.0

This commit is contained in:
Jeremy Mack Wright 2017-04-12 22:14:55 -04:00
parent 60f48dfb78
commit eae8efd9ee
14 changed files with 257 additions and 119 deletions

View File

@ -1,7 +1,7 @@
What is a CadQuery?
========================================
[![Travis Build Status](https://travis-ci.org/dcowden/cadquery.svg)](https://travis-ci.org/dcowden/cadquery)
[![Travis Build Status](https://travis-ci.org/dcowden/cadquery.svg?branch=master)](https://travis-ci.org/dcowden/cadquery?branch=master)
[![Coverage Status](https://coveralls.io/repos/dcowden/cadquery/badge.svg)](https://coveralls.io/r/dcowden/cadquery)
[![GitHub version](https://badge.fury.io/gh/dcowden%2Fcadquery.svg)](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
[![License](https://img.shields.io/badge/license-Apache2-blue.svg)](https://github.com/dcowden/cadquery/blob/master/LICENSE)
@ -46,6 +46,7 @@ This resin mold was modeled using cadquery and then created on a CNC machine:
The cadquery script is surprisingly short, and allows easily customizing any of the variables::
```python
import cadquery as cq
from Helpers import show
BS = cq.selectors.BoxSelector
@ -108,7 +109,7 @@ The cadquery script is surprisingly short, and allows easily customizing any of
]).hole(fhd, mw/2.)
show(r)
```
Thanks go to cadquery contributor hyOzd ( Altu Technology ) for the example!
@ -171,26 +172,30 @@ Use these steps if you would like to write CadQuery scripts as a python API. In
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
```python
import sys
sys.path.append('/usr/lib/freecad/lib')
```
or on Windows::
import sys
```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
```
3. 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
@ -204,17 +209,16 @@ It includes a distribution of the latest version of cadquery.
Roadmap/Future Work
=======================
Work is underway on two other installation methods for cadquery:
1. a conda package, which will install CQ and all of its depedencies, if you are using Anaconda
2. a Docker image, which comes ready-to-run after you have installed docker.
Work has also begun on Cadquery 2.0, which will feature:
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?
========================================

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: cadquery
Version: 0.4.0
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

View File

@ -13,9 +13,9 @@ from .cq import *
__all__ = [
'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
'Shape','Vertex','Edge','Wire','Solid','Shell','Compound','exporters', 'importers',
'Shape','Vertex','Edge','Wire','Face','Solid','Shell','Compound','exporters', 'importers',
'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector',
'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins'
]
__version__ = "0.5.1"
__version__ = "1.0.0"

View File

@ -16,23 +16,19 @@
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
"""
import os, sys
import os
import sys
def _fc_path():
"""Find FreeCAD"""
_PATH = ""
if _PATH:
# Look for FREECAD_LIB env variable
_PATH = os.environ.get('FREECAD_LIB', '')
if _PATH and os.path.exists(_PATH):
return _PATH
#look for FREECAD_LIB env variable
if os.environ.has_key('FREECAD_LIB'):
_PATH = os.environ.get('FREECAD_LIB')
if os.path.exists( _PATH):
return _PATH
if sys.platform.startswith('linux'):
#Make some dangerous assumptions...
# Make some dangerous assumptions...
for _PATH in [
os.path.join(os.path.expanduser("~"), "lib/freecad/lib"),
"/usr/local/lib/freecad/lib",
@ -40,12 +36,13 @@ def _fc_path():
"/opt/freecad/lib/",
"/usr/bin/freecad/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
# Try all the usual suspects
for _PATH in [
"c:/Program Files/FreeCAD0.12/bin",
"c:/Program Files/FreeCAD0.13/bin",
@ -86,27 +83,24 @@ def _fc_path():
]:
if os.path.exists(_PATH):
return _PATH
elif sys.platform.startswith('darwin'):
#Assume we're dealing with a Mac
# 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"),
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
no_library_path = ImportError('cadquery was unable to determine freecads library path')
# Make sure that the correct FreeCAD path shows up in Python's system path
try:
import FreeCAD
except ImportError:
path = _fc_path()
if path:
sys.path.insert(0, path)
try:
import FreeCAD
except ImportError:
raise no_library_path
else: raise no_library_path
sys.path.insert(0, path)
import FreeCAD

View File

@ -216,7 +216,8 @@ class Shape(object):
else:
raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped)))
def CenterOfBoundBox(self):
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:
@ -258,14 +259,18 @@ class Shape(object):
return object.wrapped.Mass
@staticmethod
def CombinedCenterOfBoundBox(objects):
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 = [o.wrapped.BoundBox.Center.multiply(1.0) for o in 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:] :
@ -470,7 +475,9 @@ class Edge(Shape):
@classmethod
def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
return Edge(FreeCADPart.makeCircle(radius, toVector(pnt), toVector(dir), angle1, angle2))
center = Vector(pnt)
normal = Vector(dir)
return Edge(FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped, angle1, angle2))
@classmethod
def makeSpline(cls, listOfVector):
@ -621,8 +628,10 @@ class Face(Shape):
return Vector(self.wrapped.normalAt(u, v).normalize())
@classmethod
def makePlane(cls, length, width, basePnt=None, dir=None):
return Face(FreeCADPart.makePlan(length, width, toVector(basePnt), toVector(dir)))
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):

View File

@ -20,8 +20,9 @@
import re
import math
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,\
upcaseTokens,CaselessLiteral,Group
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\
CaselessLiteral,Group,infixNotation,opAssoc,Forward,\
ZeroOrMore,Keyword
class Selector(object):
@ -288,10 +289,6 @@ class DirectionMinMaxSelector(Selector):
CQ(aCube).faces( ">Z" )
Future Enhancements:
provide a nicer way to select in arbitrary directions. IE, a bit more code could
allow '>(0,0,1)' to work.
"""
def __init__(self, vector, directionMax=True, tolerance=0.0001):
self.vector = vector
@ -423,7 +420,7 @@ class InverseSelector(Selector):
def _makeGrammar():
"""
Define the string selector grammar using PyParsing
Define the simple string selector grammar using PyParsing
"""
#float definition
@ -476,44 +473,14 @@ def _makeGrammar():
_grammar = _makeGrammar() #make a grammar instance
class StringSyntaxSelector(Selector):
class _SimpleStringSyntaxSelector(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``
Selectors are a complex topic: see :ref:`selector_reference` for more information
This is a private class that converts a parseResults object into a simple
selector object
"""
def __init__(self,selectorString):
def __init__(self,parseResults):
#define all token to object mappings
self.axes = {
'X': Vector(1,0,0),
'Y': Vector(0,1,0),
@ -545,9 +512,8 @@ class StringSyntaxSelector(Selector):
'#' : PerpendicularDirSelector,
'|' : ParallelDirSelector}
self.selectorString = selectorString
parsing_result = _grammar.parseString(selectorString)
self.mySelector = self._chooseSelector(parsing_result)
self.parseResults = parseResults
self.mySelector = self._chooseSelector(parseResults)
def _chooseSelector(self,pr):
"""
@ -593,3 +559,113 @@ class StringSyntaxSelector(Selector):
[+\|-\|<\|>\|] \<X\|Y\|Z>
"""
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)

View File

@ -4,32 +4,32 @@ Changes
v0.1
-----
* Initial Version
* Initial Version
v0.1.6
-----
* Added STEP import and supporting tests
* Added STEP import and supporting tests
v0.1.7
-----
* Added revolve operation and supporting tests
* Fixed minor documentation errors
* 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
* 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
-----
@ -89,7 +89,12 @@ v0.5.2
------
* Added the sweep operation #33
v1.0.0 (unreleased)
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)

View File

@ -55,9 +55,9 @@ copyright = u'Parametric Products Intellectual Holdings LLC, All Rights Reserved
# built documents.
#
# The short X.Y version.
version = '0.3'
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '0.3.0'
release = '1.0.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -0,0 +1 @@
-e .

View File

@ -19,7 +19,7 @@ from setuptools import setup
version = '0.5-SNAPSHOT'
if 'TRAVIS_TAG' in os.environ.keys():
version= os.environ['TRAVIS_TAG']
setup(
name='cadquery',
@ -31,6 +31,7 @@ setup(
description='CadQuery is a parametric scripting language for creating and traversing CAD models',
long_description=open('README.md').read(),
packages=['cadquery','cadquery.contrib','cadquery.freecad_impl','cadquery.plugins','tests'],
install_requires=['pyparsing'],
include_package_data=True,
zip_safe=False,
platforms='any',

View File

@ -338,6 +338,10 @@ class TestCQSelectors(BaseTest):
# test 'and' (intersection) operator
el = c.edges(S('|X') & BS((-2,-2,0.1), (2,2,2))).vals()
self.assertEqual(2, len(el))
# test using extended string syntax
v = c.vertices(">X and >Y").vals()
self.assertEqual(2, len(v))
def testSumSelector(self):
c = CQ(makeUnitCube())
@ -354,6 +358,12 @@ class TestCQSelectors(BaseTest):
self.assertEqual(2, len(fl))
el = c.edges(S("|X") + S("|Y")).vals()
self.assertEqual(8, len(el))
# test using extended string syntax
fl = c.faces(">Z or <Z").vals()
self.assertEqual(2, len(fl))
el = c.edges("|X or |Y").vals()
self.assertEqual(8, len(el))
def testSubtractSelector(self):
c = CQ(makeUnitCube())
@ -366,6 +376,10 @@ class TestCQSelectors(BaseTest):
# test the subtract operator
fl = c.faces(S("#Z") - S(">X")).vals()
self.assertEqual(3, len(fl))
# test using extended string syntax
fl = c.faces("#Z exc >X").vals()
self.assertEqual(3, len(fl))
def testInverseSelector(self):
c = CQ(makeUnitCube())
@ -382,6 +396,19 @@ class TestCQSelectors(BaseTest):
self.assertEqual(5, len(fl))
el = c.faces('>Z').edges(-S('>X')).vals()
self.assertEqual(3, len(el))
# test using extended string syntax
fl = c.faces('not >Z').vals()
self.assertEqual(5, len(fl))
el = c.faces('>Z').edges('not >X').vals()
self.assertEqual(3, len(el))
def testComplexStringSelector(self):
c = CQ(makeUnitCube())
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
self.assertEqual(4, len(v))
def testFaceCount(self):
c = CQ(makeUnitCube())
@ -406,7 +433,7 @@ class TestCQSelectors(BaseTest):
Test if reasonable string selector expressions parse without an error
"""
gram = selectors._makeGrammar()
gram = selectors._expression_grammar
expressions = ['+X ',
'-Y',
@ -423,7 +450,10 @@ class TestCQSelectors(BaseTest):
'left',
'right',
'top',
'bottom']
for e in expressions: gram.parseString(e)
'bottom',
'not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]',
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)',
'not ( <X or >X or <Y or >Y )']
for e in expressions: gram.parseString(e,parseAll=True)

View File

@ -41,6 +41,24 @@ class TestCadObjects(BaseTest):
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
def testEdgeWrapperMakeCircle(self):
halfCircleEdge = Edge.makeCircle(radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
self.assertTupleAlmostEquals((10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
self.assertTupleAlmostEquals((-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
def testFaceWrapperMakePlane(self):
mplane = Face.makePlane(10,10)
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
def testCenterOfBoundBox(self):
pass
def testCombinedCenterOfBoundBox(self):
pass
def testCompoundCenter(self):
"""
Tests whether or not a proper weighted center can be found for a compound

View File

@ -1,6 +1,6 @@
__author__ = "Jeremy Wright (jmwright)"
__copyright__ = "Copyright 2014-2016"
__copyright__ = "Copyright 2014-2017"
__license__ = "LGPL v3"
__version__ = "0.5.2"
__version__ = "1.0.0"
__maintainer__ = "Jeremy Wright"
__status__ = "Production/Stable"

View File

@ -28,7 +28,7 @@ v0.5.1
* Version updates for CadQuery v0.4.0, v0.4.1, v0.5.0-stable and v0.5.1
* Updated CadQuery license to Apache 2.0
v1.0.0 (Unreleased)
v1.0.0
-----
* Embedded pyparsing package as a supporting library for new selector syntax
* Added a check to remove any disallowed characters from the document name when executing a script