Merge pull request #43 from jmwright/master

Suuuhhhh Weet!  thanks for contributing this feature!
This commit is contained in:
Dave Cowden 2014-10-21 18:23:26 -04:00
commit 8041accf9f
8 changed files with 405 additions and 149 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.pyc
doc/_build/*
dist/*
.idea/*

View File

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

@ -16,6 +16,7 @@ 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/freecad_impl/verutil.py
cadquery/plugins/__init__.py
@ -23,6 +24,7 @@ tests/TestCQSelectors.py
tests/TestCadObjects.py
tests/TestCadQuery.py
tests/TestExporters.py
tests/TestImporters.py
tests/TestImports.py
tests/TestWorkplanes.py
tests/__init__.py

View File

@ -1868,6 +1868,55 @@ class Workplane(CQ):
else:
return self.newObject([r])
def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=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
: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 = 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()
r = self._revolve(angleDegrees, axisStart, axisEnd) # returns a Solid ( or a compound if there were multiple )
if combine:
return self._combineWithBase(r)
else:
return self.newObject([r])
def _combineWithBase2(self,obj):
"""
Combines the provided object with the base solid, if one can be found.
@ -2105,6 +2154,33 @@ class Workplane(CQ):
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 box(self,length,width,height,centered=(True,True,True),combine=True):
"""

View File

@ -51,8 +51,10 @@ from cadquery import Vector,BoundBox
import FreeCAD
from .verutil import fc_import
FreeCADPart = fc_import("FreeCAD.Part")
class Shape(object):
"""
Represents a shape in the system.
@ -101,6 +103,7 @@ class Shape(object):
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!
#
@ -189,6 +192,7 @@ class Shape(object):
return Vector(self.wrapped.CenterOfMass)
except:
pass
def Closed(self):
return self.wrapped.Closed
@ -288,6 +292,7 @@ class Shape(object):
def __hash__(self):
return self.wrapped.hashCode()
class Vertex(Shape):
def __init__(self, obj, forConstruction=False):
"""
@ -308,6 +313,7 @@ class Vertex(Shape):
"""
return Vector(self.wrapped.Point)
class Edge(Shape):
def __init__(self, obj):
"""
@ -631,7 +637,8 @@ class Solid(Shape):
Make a wedge located in pnt\nBy default pnt=Vector(0,0,0) and dir=Vec
tor(0,0,1)'
"""
return Shape.cast(FreeCADPart.makeWedge(xmin,ymin,zmin,z2min,x2min,xmax,ymax,zmax,z2max,x2max,pnt,dir))
return Shape.cast(
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
@classmethod
def makeSphere(cls, radius, pnt=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
@ -737,6 +744,50 @@ class Solid(Shape):
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)
def tessellate(self, tolerance):
return self.wrapped.tessellate(tolerance)
@ -779,6 +830,7 @@ class Solid(Shape):
nativeFaces = [f.wrapped for f in faceList]
return Shape.cast(self.wrapped.makeThickness(nativeFaces, thickness, tolerance))
class Compound(Shape):
def __init__(self, obj):
"""

View File

@ -0,0 +1,44 @@
#File: Ex025_Revolution.py
#To use this example file, you need to first follow the "Using CadQuery From Inside FreeCAD"
#instructions here: https://github.com/dcowden/cadquery#installing----using-cadquery-from-inside-freecad
#You run this example by typing the following in the FreeCAD python console, making sure to change
#the path to this example, and the name of the example appropriately.
#import sys
#sys.path.append('/home/user/Downloads/cadquery/examples/FreeCAD')
#import Ex025_Revolution
#If you need to reload the part after making a change, you can use the following lines within the FreeCAD console.
#reload(Ex025_Revolution)
#You'll need to delete the original shape that was created, and the new shape should be named sequentially (Shape001, etc).
#You can also tie these blocks of code to macros, buttons, and keybindings in FreeCAD for quicker access.
#You can get a more information on this example at http://parametricparts.com/docs/examples.html#an-extruded-prismatic-solid
import cadquery
import Part
#The dimensions of the model. These can be modified rather than changing the shape's code directly.
rectangle_width = 10.0
rectangle_length = 10.0
angle_degrees = 360.0
#Revolve a cylinder from a rectangle
#Switch comments around in this section to try the revolve operation with different parameters
result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
#Revolve a donut with square walls
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
#Get a cadquery solid object
solid = result.val()
#Use the wrapped property of a cadquery primitive to get a FreeCAD solid
Part.show(solid.wrapped)
#Would like to zoom to fit the part here, but FreeCAD doesn't seem to have that scripting functionality

View File

@ -2,7 +2,7 @@ from setuptools import setup
setup(
name='cadquery',
version='0.1.6',
version='0.1.7',
url='https://github.com/dcowden/cadquery',
license='LGPL',
author='David Cowden',

View File

@ -219,6 +219,87 @@ class TestCadQuery(BaseTest):
#self.assertEqual(1,s.solids().size() )
#self.assertEqual(8,s.faces().size() )
def testRevolveCylinder(self):
"""
Test creating a solid using the revolve operation.
:return:
"""
#The dimensions of the model. These can be modified rather than changing the shape's code directly.
rectangle_width = 10.0
rectangle_length = 10.0
angle_degrees = 360.0
#Test revolve without any options for making a cylinder
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
self.assertEqual(3, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
#Test revolve when only setting the angle to revolve through
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
self.assertEqual(3, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(270.0)
self.assertEqual(5, result.faces().size())
self.assertEqual(6, result.vertices().size())
self.assertEqual(9, result.edges().size())
#Test when passing revolve the angle and the axis of revolution's start point
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
self.assertEqual(3, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5))
self.assertEqual(5, result.faces().size())
self.assertEqual(6, result.vertices().size())
self.assertEqual(9, result.edges().size())
#Test when passing revolve the angle and both the start and ends of the axis of revolution
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
self.assertEqual(3, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5, -5),(-5, 5))
self.assertEqual(5, result.faces().size())
self.assertEqual(6, result.vertices().size())
self.assertEqual(9, result.edges().size())
#Testing all of the above without combine
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
self.assertEqual(3, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5),(-5,5), False)
self.assertEqual(5, result.faces().size())
self.assertEqual(6, result.vertices().size())
self.assertEqual(9, result.edges().size())
def testRevolveDonut(self):
"""
Test creating a solid donut shape with square walls
:return:
"""
#The dimensions of the model. These can be modified rather than changing the shape's code directly.
rectangle_width = 10.0
rectangle_length = 10.0
angle_degrees = 360.0
result = Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
self.assertEqual(4, result.faces().size())
self.assertEqual(4, result.vertices().size())
self.assertEqual(6, result.edges().size())
def testRevolveCone(self):
"""
Test creating a solid from a revolved triangle
:return:
"""
result = Workplane("XY").lineTo(0,10).lineTo(5,0).close().revolve()
self.assertEqual(2, result.faces().size())
self.assertEqual(2, result.vertices().size())
self.assertEqual(3, result.edges().size())
def testRectArray(self):
NUMX=3
NUMY=3