Add Black formatting check to CI (#255)
* Add Black formatting check to CI * Add some documentation for code contributors * Use uncompromised code formatting
This commit is contained in:
committed by
Adam Urbańczyk
parent
74573fc3bb
commit
102c16c14e
26
.travis.yml
26
.travis.yml
@ -24,27 +24,31 @@ env:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: PYTHON_VERSION=3.6
|
||||
- name: "Python 3.6 - osx"
|
||||
env: PYTHON_VERSION=3.6
|
||||
os: osx
|
||||
- env: PYTHON_VERSION=3.6
|
||||
- name: "Python 3.6 - linux"
|
||||
env: PYTHON_VERSION=3.6
|
||||
os: linux
|
||||
- env: PYTHON_VERSION=3.7
|
||||
- name: "Python 3.7 - osx"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: osx
|
||||
- env: PYTHON_VERSION=3.7
|
||||
- name: "Python 3.7 - linux"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: linux
|
||||
- name: "Lint"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: linux
|
||||
script:
|
||||
- black . --diff --check
|
||||
|
||||
before_install:
|
||||
- if [[ "$PYTHON_VERSION" == "2.7" ]]; then
|
||||
PY_MAJOR=2 ;
|
||||
else
|
||||
PY_MAJOR=3 ;
|
||||
fi ;
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
OS=Linux ;
|
||||
else
|
||||
OS=MacOSX ;
|
||||
fi ;
|
||||
wget https://repo.continuum.io/miniconda/Miniconda$PY_MAJOR-latest-$OS-x86_64.sh -O miniconda.sh
|
||||
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-$OS-x86_64.sh -O miniconda.sh
|
||||
- bash miniconda.sh -b -p $HOME/miniconda;
|
||||
- export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH";
|
||||
- conda config --set always_yes yes --set changeps1 no;
|
||||
|
32
README.md
32
README.md
@ -123,6 +123,38 @@ You do not need to be a software developer to have a big impact on this project.
|
||||
|
||||
It is asked that all contributions to this project be made in a respectful and considerate way. Please use the [Python Community Code of Conduct's](https://www.python.org/psf/codeofconduct/) guidelines as a reference.
|
||||
|
||||
### Contributing code
|
||||
|
||||
If you are going to contribute code, make sure to follow this steps:
|
||||
|
||||
- Consider opening an issue first to discuss what you have in mind
|
||||
- Try to keep it as short and simple as possible (if you want to change several
|
||||
things, start with just one!)
|
||||
- Fork the CadQuery repository, clone your fork and create a new branch to
|
||||
start working on your changes
|
||||
- Start with the tests! How should CadQuery behave after your changes? Make
|
||||
sure to add some tests to the test suite to ensure proper behavior
|
||||
- Make sure your tests have assertions checking all the expected results
|
||||
- Add a nice docstring to the test indicating what the test is doing; if there
|
||||
is too much to explain, consider splitting the test in two!
|
||||
- Go ahead and implement the changes
|
||||
- Add a nice docstring to the functions/methods/classes you implement
|
||||
describing what they do, what the expected parameters are and what it returns
|
||||
(if anything)
|
||||
- Update the documentation if there is any change to the public API
|
||||
- Consider adding an example to the documentation showing your cool new
|
||||
feature!
|
||||
- Make sure nothing is broken (run the complete test suite with `pytest`)
|
||||
- Run `black` to autoformat your code and make sure your code style complies
|
||||
with CadQuery's
|
||||
- Push the changes to your fork and open a pull-request upstream
|
||||
- Keep an eye on the automated feedback you will receive from the CI pipelines;
|
||||
if there is a test failing or some code is not properly formatted, you will
|
||||
be notified without human intervention
|
||||
- Be prepared for constructive feedback and criticism!
|
||||
- Be patient and respectful, remember that those reviewing your code are also
|
||||
working hard (sometimes reviewing changes is harder than implementing them!)
|
||||
|
||||
### How to Report a Bug
|
||||
When filing a bug report [issue](https://github.com/CadQuery/cadquery/issues), please be sure to answer these questions:
|
||||
|
||||
|
@ -1,25 +1,65 @@
|
||||
# these items point to the OCC implementation
|
||||
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
||||
from .occ_impl.shapes import (Shape, Vertex, Edge, Face, Wire, Solid, Shell,
|
||||
Compound, sortWiresByBuildOrder)
|
||||
from .occ_impl.shapes import (
|
||||
Shape,
|
||||
Vertex,
|
||||
Edge,
|
||||
Face,
|
||||
Wire,
|
||||
Solid,
|
||||
Shell,
|
||||
Compound,
|
||||
sortWiresByBuildOrder,
|
||||
)
|
||||
from .occ_impl import exporters
|
||||
from .occ_impl import importers
|
||||
|
||||
# these items are the common implementation
|
||||
|
||||
# the order of these matter
|
||||
from .selectors import (NearestToPointSelector, ParallelDirSelector,
|
||||
DirectionSelector, PerpendicularDirSelector, TypeSelector,
|
||||
DirectionMinMaxSelector, StringSyntaxSelector, Selector)
|
||||
from .selectors import (
|
||||
NearestToPointSelector,
|
||||
ParallelDirSelector,
|
||||
DirectionSelector,
|
||||
PerpendicularDirSelector,
|
||||
TypeSelector,
|
||||
DirectionMinMaxSelector,
|
||||
StringSyntaxSelector,
|
||||
Selector,
|
||||
)
|
||||
from .cq import CQ, Workplane, selectors
|
||||
from . import plugins
|
||||
|
||||
|
||||
__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'
|
||||
"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__ = "2.0.0dev"
|
||||
|
383
cadquery/cq.py
383
cadquery/cq.py
@ -18,8 +18,19 @@
|
||||
"""
|
||||
|
||||
import math
|
||||
from . import Vector, Plane, Shape, Edge, Wire, Face, Solid, Compound, \
|
||||
sortWiresByBuildOrder, selectors, exporters
|
||||
from . import (
|
||||
Vector,
|
||||
Plane,
|
||||
Shape,
|
||||
Edge,
|
||||
Wire,
|
||||
Face,
|
||||
Solid,
|
||||
Compound,
|
||||
sortWiresByBuildOrder,
|
||||
selectors,
|
||||
exporters,
|
||||
)
|
||||
|
||||
|
||||
class CQContext(object):
|
||||
@ -31,7 +42,9 @@ class CQContext(object):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.pendingWires = [] # a list of wires that have been created and need to be extruded
|
||||
self.pendingWires = (
|
||||
[]
|
||||
) # a list of wires that have been created and need to be extruded
|
||||
# a list of created pending edges that need to be joined into wires
|
||||
self.pendingEdges = []
|
||||
# a reference to the first point for a set of edges.
|
||||
@ -99,8 +112,12 @@ class CQ(object):
|
||||
# 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')():
|
||||
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):
|
||||
@ -259,8 +276,9 @@ class CQ(object):
|
||||
|
||||
return self.objects[0].wrapped
|
||||
|
||||
def workplane(self, offset=0.0, invert=False, centerOption='CenterOfMass',
|
||||
origin=None):
|
||||
def workplane(
|
||||
self, offset=0.0, invert=False, centerOption="CenterOfMass", origin=None
|
||||
):
|
||||
"""
|
||||
Creates a new 2-D workplane, located relative to the first face on the stack.
|
||||
|
||||
@ -310,6 +328,7 @@ class CQ(object):
|
||||
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()
|
||||
@ -318,9 +337,11 @@ class CQ(object):
|
||||
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)):
|
||||
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)
|
||||
@ -339,22 +360,23 @@ class CQ(object):
|
||||
xd = Vector(1, 0, 0)
|
||||
return xd
|
||||
|
||||
if centerOption not in {'CenterOfMass', 'ProjectedOrigin', 'CenterOfBoundBox'}:
|
||||
raise ValueError('Undefined centerOption value provided.')
|
||||
if centerOption not in {"CenterOfMass", "ProjectedOrigin", "CenterOfBoundBox"}:
|
||||
raise ValueError("Undefined centerOption value provided.")
|
||||
|
||||
if len(self.objects) > 1:
|
||||
# are all objects 'PLANE'?
|
||||
if not all(o.geomType() in ('PLANE', 'CIRCLE') for o in self.objects):
|
||||
if not all(o.geomType() in ("PLANE", "CIRCLE") for o in self.objects):
|
||||
raise ValueError(
|
||||
"If multiple objects selected, they all must be planar faces.")
|
||||
"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 in {'CenterOfMass', 'ProjectedOrigin'}:
|
||||
if centerOption in {"CenterOfMass", "ProjectedOrigin"}:
|
||||
center = Shape.CombinedCenter(self.objects)
|
||||
elif centerOption == 'CenterOfBoundBox':
|
||||
elif centerOption == "CenterOfBoundBox":
|
||||
center = Shape.CombinedCenterOfBoundBox(self.objects)
|
||||
|
||||
normal = self.objects[0].normalAt()
|
||||
@ -364,26 +386,27 @@ class CQ(object):
|
||||
obj = self.objects[0]
|
||||
|
||||
if isinstance(obj, Face):
|
||||
if centerOption in {'CenterOfMass', 'ProjectedOrigin'}:
|
||||
if centerOption in {"CenterOfMass", "ProjectedOrigin"}:
|
||||
center = obj.Center()
|
||||
elif centerOption == 'CenterOfBoundBox':
|
||||
elif centerOption == "CenterOfBoundBox":
|
||||
center = obj.CenterOfBoundBox()
|
||||
normal = obj.normalAt(center)
|
||||
xDir = _computeXdir(normal)
|
||||
else:
|
||||
if hasattr(obj, 'Center'):
|
||||
if centerOption in {'CenterOfMass', 'ProjectedOrigin'}:
|
||||
if hasattr(obj, "Center"):
|
||||
if centerOption in {"CenterOfMass", "ProjectedOrigin"}:
|
||||
center = obj.Center()
|
||||
elif centerOption == 'CenterOfBoundBox':
|
||||
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")
|
||||
"Needs a face or a vertex or point on a work plane"
|
||||
)
|
||||
|
||||
# update center to projected origin if desired
|
||||
if centerOption == 'ProjectedOrigin':
|
||||
if centerOption == "ProjectedOrigin":
|
||||
if origin is None:
|
||||
origin = self.plane.origin
|
||||
elif isinstance(origin, tuple):
|
||||
@ -459,9 +482,7 @@ class CQ(object):
|
||||
return rv[0]
|
||||
|
||||
if searchParents and self.parent is not None:
|
||||
return self.parent._findType(types,
|
||||
searchStack=True,
|
||||
searchParents=True)
|
||||
return self.parent._findType(types, searchStack=True, searchParents=True)
|
||||
|
||||
return None
|
||||
|
||||
@ -554,7 +575,7 @@ class CQ(object):
|
||||
:py:class:`StringSyntaxSelector`
|
||||
|
||||
"""
|
||||
return self._selectObjects('Vertices', selector)
|
||||
return self._selectObjects("Vertices", selector)
|
||||
|
||||
def faces(self, selector=None):
|
||||
"""
|
||||
@ -586,7 +607,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Faces', selector)
|
||||
return self._selectObjects("Faces", selector)
|
||||
|
||||
def edges(self, selector=None):
|
||||
"""
|
||||
@ -617,7 +638,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Edges', selector)
|
||||
return self._selectObjects("Edges", selector)
|
||||
|
||||
def wires(self, selector=None):
|
||||
"""
|
||||
@ -640,7 +661,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Wires', selector)
|
||||
return self._selectObjects("Wires", selector)
|
||||
|
||||
def solids(self, selector=None):
|
||||
"""
|
||||
@ -666,7 +687,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Solids', selector)
|
||||
return self._selectObjects("Solids", selector)
|
||||
|
||||
def shells(self, selector=None):
|
||||
"""
|
||||
@ -686,7 +707,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Shells', selector)
|
||||
return self._selectObjects("Shells", selector)
|
||||
|
||||
def compounds(self, selector=None):
|
||||
"""
|
||||
@ -704,7 +725,7 @@ class CQ(object):
|
||||
|
||||
See more about selectors HERE
|
||||
"""
|
||||
return self._selectObjects('Compounds', selector)
|
||||
return self._selectObjects("Compounds", selector)
|
||||
|
||||
def toSvg(self, opts=None):
|
||||
"""
|
||||
@ -777,8 +798,9 @@ class CQ(object):
|
||||
:type angleDegrees: float
|
||||
:returns: a CQ object
|
||||
"""
|
||||
return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees)
|
||||
for o in self.objects])
|
||||
return self.newObject(
|
||||
[o.rotate(axisStartPoint, axisEndPoint, angleDegrees) for o in self.objects]
|
||||
)
|
||||
|
||||
def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
|
||||
"""
|
||||
@ -789,8 +811,7 @@ class CQ(object):
|
||||
:param basePointVector: the base point to mirror about
|
||||
:type basePointVector: tuple
|
||||
"""
|
||||
newS = self.newObject(
|
||||
[self.objects[0].mirror(mirrorPlane, basePointVector)])
|
||||
newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
|
||||
return newS.first()
|
||||
|
||||
def translate(self, vec):
|
||||
@ -943,7 +964,7 @@ class Workplane(CQ):
|
||||
:py:meth:`CQ.workplane`
|
||||
"""
|
||||
|
||||
FOR_CONSTRUCTION = 'ForConstruction'
|
||||
FOR_CONSTRUCTION = "ForConstruction"
|
||||
|
||||
def __init__(self, inPlane, origin=(0, 0, 0), obj=None):
|
||||
"""
|
||||
@ -967,7 +988,7 @@ class Workplane(CQ):
|
||||
the *current point* is on the origin.
|
||||
"""
|
||||
|
||||
if inPlane.__class__.__name__ == 'Plane':
|
||||
if inPlane.__class__.__name__ == "Plane":
|
||||
tmpPlane = inPlane
|
||||
elif isinstance(inPlane, str) or isinstance(inPlane, str):
|
||||
tmpPlane = Plane.named(inPlane, origin)
|
||||
@ -976,7 +997,8 @@ class Workplane(CQ):
|
||||
|
||||
if tmpPlane is None:
|
||||
raise ValueError(
|
||||
'Provided value {} is not a valid work plane'.format(inPlane))
|
||||
"Provided value {} is not a valid work plane".format(inPlane)
|
||||
)
|
||||
|
||||
self.obj = obj
|
||||
self.plane = tmpPlane
|
||||
@ -999,10 +1021,10 @@ class Workplane(CQ):
|
||||
"""
|
||||
|
||||
# old api accepted a vector, so we'll check for that.
|
||||
if rotate.__class__.__name__ == 'Vector':
|
||||
if rotate.__class__.__name__ == "Vector":
|
||||
rotate = rotate.toTuple()
|
||||
|
||||
if offset.__class__.__name__ == 'Vector':
|
||||
if offset.__class__.__name__ == "Vector":
|
||||
offset = offset.toTuple()
|
||||
|
||||
p = self.plane.rotated(rotate)
|
||||
@ -1060,8 +1082,7 @@ class Workplane(CQ):
|
||||
elif isinstance(obj, Vector):
|
||||
p = obj
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Cannot convert object type '%s' to vector " % type(obj))
|
||||
raise RuntimeError("Cannot convert object type '%s' to vector " % type(obj))
|
||||
|
||||
if useLocalCoords:
|
||||
return self.plane.toLocalCoords(p)
|
||||
@ -1367,28 +1388,35 @@ class Workplane(CQ):
|
||||
:return: A CQ object representing a slot
|
||||
"""
|
||||
|
||||
radius = diameter/2
|
||||
radius = diameter / 2
|
||||
|
||||
p1 = pnt + Vector((-length/2) + radius, diameter/2)
|
||||
p1 = pnt + Vector((-length / 2) + radius, diameter / 2)
|
||||
p2 = p1 + Vector(length - diameter, 0)
|
||||
p3 = p1 + Vector(length - diameter, -diameter)
|
||||
p4 = p1 + Vector(0, -diameter)
|
||||
arc1 = p2 + Vector(radius, -radius)
|
||||
arc2 = p4 + Vector(-radius, radius)
|
||||
|
||||
edges=[(Edge.makeLine(p1,p2))]
|
||||
edges = [(Edge.makeLine(p1, p2))]
|
||||
edges.append(Edge.makeThreePointArc(p2, arc1, p3))
|
||||
edges.append(Edge.makeLine(p3, p4))
|
||||
edges.append(Edge.makeThreePointArc(p4, arc2, p1))
|
||||
|
||||
slot = Wire.assembleEdges(edges)
|
||||
|
||||
return slot.rotate(pnt, pnt + Vector(0,0,1), angle)
|
||||
return slot.rotate(pnt, pnt + Vector(0, 0, 1), angle)
|
||||
|
||||
return self.eachpoint(_makeslot, True)
|
||||
|
||||
def spline(self, listOfXYTuple, tangents=None, periodic=False,
|
||||
forConstruction=False, includeCurrent=False, makeWire=False):
|
||||
def spline(
|
||||
self,
|
||||
listOfXYTuple,
|
||||
tangents=None,
|
||||
periodic=False,
|
||||
forConstruction=False,
|
||||
includeCurrent=False,
|
||||
makeWire=False,
|
||||
):
|
||||
"""
|
||||
Create a spline interpolated through the provided points.
|
||||
|
||||
@ -1435,8 +1463,7 @@ class Workplane(CQ):
|
||||
|
||||
if tangents:
|
||||
t1, t2 = tangents
|
||||
tangents = (self.plane.toWorldCoords(t1),
|
||||
self.plane.toWorldCoords(t2))
|
||||
tangents = (self.plane.toWorldCoords(t1), self.plane.toWorldCoords(t2))
|
||||
|
||||
e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic)
|
||||
|
||||
@ -1464,9 +1491,9 @@ class Workplane(CQ):
|
||||
|
||||
"""
|
||||
|
||||
allPoints = [func(start+stop*t/N) for t in range(N+1)]
|
||||
allPoints = [func(start + stop * t / N) for t in range(N + 1)]
|
||||
|
||||
return self.spline(allPoints,includeCurrent=False,makeWire=True)
|
||||
return self.spline(allPoints, includeCurrent=False, makeWire=True)
|
||||
|
||||
def threePointArc(self, point1, point2, forConstruction=False):
|
||||
"""
|
||||
@ -1516,10 +1543,16 @@ class Workplane(CQ):
|
||||
midPoint = endPoint.add(startPoint).multiply(0.5)
|
||||
|
||||
sagVector = endPoint.sub(startPoint).normalized().multiply(abs(sag))
|
||||
if(sag > 0):
|
||||
sagVector.x, sagVector.y = -sagVector.y, sagVector.x # Rotate sagVector +90 deg
|
||||
if sag > 0:
|
||||
sagVector.x, sagVector.y = (
|
||||
-sagVector.y,
|
||||
sagVector.x,
|
||||
) # Rotate sagVector +90 deg
|
||||
else:
|
||||
sagVector.x, sagVector.y = sagVector.y, -sagVector.x # Rotate sagVector -90 deg
|
||||
sagVector.x, sagVector.y = (
|
||||
sagVector.y,
|
||||
-sagVector.x,
|
||||
) # Rotate sagVector -90 deg
|
||||
|
||||
sagPoint = midPoint.add(sagVector)
|
||||
|
||||
@ -1545,7 +1578,7 @@ class Workplane(CQ):
|
||||
# Calculate the sagitta from the radius
|
||||
length = endPoint.sub(startPoint).Length / 2.0
|
||||
try:
|
||||
sag = abs(radius) - math.sqrt(radius**2 - length**2)
|
||||
sag = abs(radius) - math.sqrt(radius ** 2 - length ** 2)
|
||||
except ValueError:
|
||||
raise ValueError("Arc radius is not large enough to reach the end point.")
|
||||
|
||||
@ -1580,8 +1613,7 @@ class Workplane(CQ):
|
||||
# attempt to consolidate wires together.
|
||||
consolidated = n.consolidateWires()
|
||||
|
||||
rotatedWires = self.plane.rotateShapes(
|
||||
consolidated.wires().vals(), matrix)
|
||||
rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(), matrix)
|
||||
|
||||
for w in rotatedWires:
|
||||
consolidated.objects.append(w)
|
||||
@ -1616,8 +1648,7 @@ class Workplane(CQ):
|
||||
# attempt to consolidate wires together.
|
||||
consolidated = n.consolidateWires()
|
||||
|
||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
||||
'Y')
|
||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(), "Y")
|
||||
|
||||
for w in mirroredWires:
|
||||
consolidated.objects.append(w)
|
||||
@ -1646,8 +1677,7 @@ class Workplane(CQ):
|
||||
# attempt to consolidate wires together.
|
||||
consolidated = n.consolidateWires()
|
||||
|
||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
||||
'X')
|
||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(), "X")
|
||||
|
||||
for w in mirroredWires:
|
||||
consolidated.objects.append(w)
|
||||
@ -1859,6 +1889,7 @@ class Workplane(CQ):
|
||||
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()
|
||||
@ -1909,6 +1940,7 @@ class Workplane(CQ):
|
||||
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
|
||||
@ -1927,19 +1959,25 @@ class Workplane(CQ):
|
||||
: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))
|
||||
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,
|
||||
includeCurrent=False):
|
||||
def polyline(self, listOfXYTuple, forConstruction=False, includeCurrent=False):
|
||||
"""
|
||||
Create a polyline from a list of points
|
||||
|
||||
@ -2089,11 +2127,11 @@ class Workplane(CQ):
|
||||
boreDir = Vector(0, 0, -1)
|
||||
# first make the hole
|
||||
hole = Solid.makeCylinder(
|
||||
diameter / 2.0, depth, center, boreDir) # local coordianates!
|
||||
diameter / 2.0, depth, center, boreDir
|
||||
) # local coordianates!
|
||||
|
||||
# add the counter bore
|
||||
cbore = Solid.makeCylinder(
|
||||
cboreDiameter / 2.0, cboreDepth, center, boreDir)
|
||||
cbore = Solid.makeCylinder(cboreDiameter / 2.0, cboreDepth, center, boreDir)
|
||||
r = hole.fuse(cbore)
|
||||
return r
|
||||
|
||||
@ -2142,7 +2180,8 @@ class Workplane(CQ):
|
||||
|
||||
# first make the hole
|
||||
hole = Solid.makeCylinder(
|
||||
diameter / 2.0, depth, center, boreDir) # local coords!
|
||||
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)
|
||||
@ -2191,7 +2230,8 @@ class Workplane(CQ):
|
||||
boreDir = Vector(0, 0, -1)
|
||||
# first make the hole
|
||||
hole = Solid.makeCylinder(
|
||||
diameter / 2.0, depth, center, boreDir) # local coordinates!
|
||||
diameter / 2.0, depth, center, boreDir
|
||||
) # local coordinates!
|
||||
return hole
|
||||
|
||||
return self.cutEach(_makeHole, True, clean)
|
||||
@ -2235,8 +2275,9 @@ class Workplane(CQ):
|
||||
# are multiple sets
|
||||
r = None
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin,
|
||||
eDir, angleDegrees)
|
||||
thisObj = Solid.extrudeLinearWithRotation(
|
||||
ws[0], ws[1:], self.plane.origin, eDir, angleDegrees
|
||||
)
|
||||
if r is None:
|
||||
r = thisObj
|
||||
else:
|
||||
@ -2277,7 +2318,8 @@ class Workplane(CQ):
|
||||
selected may not be planar
|
||||
"""
|
||||
r = self._extrude(
|
||||
distance, both=both, taper=taper) # returns a Solid (or a compound if there were multiple)
|
||||
distance, both=both, taper=taper
|
||||
) # returns a Solid (or a compound if there were multiple)
|
||||
|
||||
if combine:
|
||||
newS = self._combineWithBase(r)
|
||||
@ -2287,7 +2329,9 @@ class Workplane(CQ):
|
||||
newS = newS.clean()
|
||||
return newS
|
||||
|
||||
def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True):
|
||||
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.
|
||||
|
||||
@ -2344,8 +2388,17 @@ class Workplane(CQ):
|
||||
newS = newS.clean()
|
||||
return newS
|
||||
|
||||
def sweep(self, path, multisection=False, sweepAlongWires=None, makeSolid=True, isFrenet=False,
|
||||
combine=True, clean=True, transition='right'):
|
||||
def sweep(
|
||||
self,
|
||||
path,
|
||||
multisection=False,
|
||||
sweepAlongWires=None,
|
||||
makeSolid=True,
|
||||
isFrenet=False,
|
||||
combine=True,
|
||||
clean=True,
|
||||
transition="right",
|
||||
):
|
||||
"""
|
||||
Use all un-extruded wires in the parent chain to create a swept solid.
|
||||
|
||||
@ -2362,20 +2415,25 @@ class Workplane(CQ):
|
||||
"""
|
||||
|
||||
if not sweepAlongWires is None:
|
||||
multisection=sweepAlongWires
|
||||
multisection = sweepAlongWires
|
||||
|
||||
from warnings import warn
|
||||
warn('sweepAlongWires keyword argument is is depracated and will '\
|
||||
'be removed in the next version; use multisection instead',
|
||||
DeprecationWarning)
|
||||
|
||||
r = self._sweep(path.wire(), multisection, makeSolid, isFrenet,
|
||||
transition) # returns a Solid (or a compound if there were multiple)
|
||||
warn(
|
||||
"sweepAlongWires keyword argument is is depracated and will "
|
||||
"be removed in the next version; use multisection instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
r = self._sweep(
|
||||
path.wire(), multisection, makeSolid, isFrenet, transition
|
||||
) # 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()
|
||||
if clean:
|
||||
newS = newS.clean()
|
||||
return newS
|
||||
|
||||
def _combineWithBase(self, obj):
|
||||
@ -2442,7 +2500,8 @@ class Workplane(CQ):
|
||||
solids = toUnion.solids().vals()
|
||||
if len(solids) < 1:
|
||||
raise ValueError(
|
||||
"CQ object must have at least one solid on the stack to union!")
|
||||
"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)
|
||||
@ -2522,7 +2581,8 @@ class Workplane(CQ):
|
||||
|
||||
newS = solidRef.intersect(solidToIntersect)
|
||||
|
||||
if clean: newS = newS.clean()
|
||||
if clean:
|
||||
newS = newS.clean()
|
||||
|
||||
return self.newObject([newS])
|
||||
|
||||
@ -2580,19 +2640,18 @@ class Workplane(CQ):
|
||||
solidRef = self.findSolid()
|
||||
faceRef = self.findFace()
|
||||
|
||||
#if no faces on the stack take the nearest face parallel to the plane zDir
|
||||
# if no faces on the stack take the nearest face parallel to the plane zDir
|
||||
if not faceRef:
|
||||
#first select all with faces with good orietation
|
||||
# first select all with faces with good orietation
|
||||
sel = selectors.PerpendicularDirSelector(self.plane.zDir)
|
||||
faces = sel.filter(solidRef.Faces())
|
||||
#then select the closest
|
||||
# then select the closest
|
||||
sel = selectors.NearestToPointSelector(self.plane.origin.toTuple())
|
||||
faceRef = sel.filter(faces)[0]
|
||||
|
||||
rv = []
|
||||
for solid in solidRef.Solids():
|
||||
s = solid.dprism(faceRef, wires, thruAll=True, additive=False,
|
||||
taper=-taper)
|
||||
s = solid.dprism(faceRef, wires, thruAll=True, additive=False, taper=-taper)
|
||||
|
||||
if clean:
|
||||
s = s.clean()
|
||||
@ -2636,8 +2695,7 @@ class Workplane(CQ):
|
||||
# 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), [])
|
||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), [])
|
||||
# now all of the wires have been used to create an extrusion
|
||||
self.ctx.pendingWires = []
|
||||
|
||||
@ -2655,18 +2713,17 @@ class Workplane(CQ):
|
||||
toFuse = []
|
||||
|
||||
if taper:
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinear(ws[0], [], eDir, taper)
|
||||
toFuse.append(thisObj)
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinear(ws[0], [], eDir, taper)
|
||||
toFuse.append(thisObj)
|
||||
else:
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
|
||||
toFuse.append(thisObj)
|
||||
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)
|
||||
if both:
|
||||
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir.multiply(-1.0))
|
||||
toFuse.append(thisObj)
|
||||
|
||||
return Compound.makeCompound(toFuse)
|
||||
|
||||
@ -2685,8 +2742,7 @@ class Workplane(CQ):
|
||||
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))
|
||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))
|
||||
|
||||
# Mark that all of the wires have been used to create a revolution
|
||||
self.ctx.pendingWires = []
|
||||
@ -2694,14 +2750,19 @@ class Workplane(CQ):
|
||||
# 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)
|
||||
thisObj = Solid.revolve(ws[0], ws[1:], angleDegrees, axisStart, axisEnd)
|
||||
toFuse.append(thisObj)
|
||||
|
||||
return Compound.makeCompound(toFuse)
|
||||
|
||||
def _sweep(self, path, multisection=False, makeSolid=True, isFrenet=False,
|
||||
transition='right'):
|
||||
def _sweep(
|
||||
self,
|
||||
path,
|
||||
multisection=False,
|
||||
makeSolid=True,
|
||||
isFrenet=False,
|
||||
transition="right",
|
||||
):
|
||||
"""
|
||||
Makes a swept solid from an existing set of pending wires.
|
||||
|
||||
@ -2716,8 +2777,9 @@ class Workplane(CQ):
|
||||
if not multisection:
|
||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))
|
||||
for ws in wireSets:
|
||||
thisObj = Solid.sweep(ws[0], ws[1:], path.val(), makeSolid,
|
||||
isFrenet, transition)
|
||||
thisObj = Solid.sweep(
|
||||
ws[0], ws[1:], path.val(), makeSolid, isFrenet, transition
|
||||
)
|
||||
toFuse.append(thisObj)
|
||||
else:
|
||||
sections = self.ctx.pendingWires
|
||||
@ -2728,7 +2790,15 @@ class Workplane(CQ):
|
||||
|
||||
return Compound.makeCompound(toFuse)
|
||||
|
||||
def box(self, length, width, height, centered=(True, True, True), combine=True, clean=True):
|
||||
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.
|
||||
|
||||
@ -2773,14 +2843,14 @@ class Workplane(CQ):
|
||||
|
||||
def _makebox(pnt):
|
||||
|
||||
#(xp,yp,zp) = self.plane.toLocalCoords(pnt)
|
||||
# (xp,yp,zp) = self.plane.toLocalCoords(pnt)
|
||||
(xp, yp, zp) = pnt.toTuple()
|
||||
if centered[0]:
|
||||
xp -= (length / 2.0)
|
||||
xp -= length / 2.0
|
||||
if centered[1]:
|
||||
yp -= (width / 2.0)
|
||||
yp -= width / 2.0
|
||||
if centered[2]:
|
||||
zp -= (height / 2.0)
|
||||
zp -= height / 2.0
|
||||
|
||||
return Solid.makeBox(length, width, height, Vector(xp, yp, zp))
|
||||
|
||||
@ -2793,8 +2863,17 @@ class Workplane(CQ):
|
||||
# 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):
|
||||
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
|
||||
|
||||
@ -2851,7 +2930,9 @@ class Workplane(CQ):
|
||||
if not centered[2]:
|
||||
zp += radius
|
||||
|
||||
return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3)
|
||||
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)
|
||||
@ -2862,8 +2943,21 @@ class Workplane(CQ):
|
||||
else:
|
||||
return self.union(spheres, clean=clean)
|
||||
|
||||
def wedge(self, dx, dy, dz, xmin, zmin, xmax, zmax, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1),
|
||||
centered=(True, True, True), combine=True, clean=True):
|
||||
def wedge(
|
||||
self,
|
||||
dx,
|
||||
dy,
|
||||
dz,
|
||||
xmin,
|
||||
zmin,
|
||||
xmax,
|
||||
zmax,
|
||||
pnt=Vector(0, 0, 0),
|
||||
dir=Vector(0, 0, 1),
|
||||
centered=(True, True, True),
|
||||
combine=True,
|
||||
clean=True,
|
||||
):
|
||||
"""
|
||||
:param dx: Distance along the X axis
|
||||
:param dy: Distance along the Y axis
|
||||
@ -2901,15 +2995,17 @@ class Workplane(CQ):
|
||||
(xp, yp, zp) = pnt.toTuple()
|
||||
|
||||
if not centered[0]:
|
||||
xp += dx / 2.
|
||||
xp += dx / 2.0
|
||||
|
||||
if not centered[1]:
|
||||
yp += dy / 2.
|
||||
yp += dy / 2.0
|
||||
|
||||
if not centered[2]:
|
||||
zp += dx / 2.
|
||||
zp += dx / 2.0
|
||||
|
||||
return Solid.makeWedge(dx, dy, dz, xmin, zmin, xmax, zmax, Vector(xp, yp, zp), dir)
|
||||
return Solid.makeWedge(
|
||||
dx, dy, dz, xmin, zmin, xmax, zmax, Vector(xp, yp, zp), dir
|
||||
)
|
||||
|
||||
# We want a wedge for each point on the workplane
|
||||
wedges = self.eachpoint(_makewedge)
|
||||
@ -2945,11 +3041,23 @@ class Workplane(CQ):
|
||||
cleanObjects = [obj.clean() for obj in self.objects]
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
"%s object doesn't support `clean()` method!" % obj.ShapeType())
|
||||
"%s object doesn't support `clean()` method!" % obj.ShapeType()
|
||||
)
|
||||
return self.newObject(cleanObjects)
|
||||
|
||||
def text(self, txt, fontsize, distance, cut=True, combine=False, clean=True,
|
||||
font="Arial", kind='regular',halign='center',valign='center'):
|
||||
def text(
|
||||
self,
|
||||
txt,
|
||||
fontsize,
|
||||
distance,
|
||||
cut=True,
|
||||
combine=False,
|
||||
clean=True,
|
||||
font="Arial",
|
||||
kind="regular",
|
||||
halign="center",
|
||||
valign="center",
|
||||
):
|
||||
"""
|
||||
Create a 3D text
|
||||
|
||||
@ -2976,8 +3084,16 @@ class Workplane(CQ):
|
||||
and the resulting solid becomes the new context solid.
|
||||
|
||||
"""
|
||||
r = Compound.makeText(txt,fontsize,distance,font=font,kind=kind,
|
||||
halign=halign, valign=valign, position=self.plane)
|
||||
r = Compound.makeText(
|
||||
txt,
|
||||
fontsize,
|
||||
distance,
|
||||
font=font,
|
||||
kind=kind,
|
||||
halign=halign,
|
||||
valign=valign,
|
||||
position=self.plane,
|
||||
)
|
||||
|
||||
if cut:
|
||||
newS = self._cutFromBase(r)
|
||||
@ -2995,7 +3111,6 @@ class Workplane(CQ):
|
||||
"""
|
||||
|
||||
if type(self.objects[0]) is Vector:
|
||||
return '< {} >'.format(self.__repr__()[1:-1])
|
||||
return "< {} >".format(self.__repr__()[1:-1])
|
||||
else:
|
||||
return Compound.makeCompound(self.objects)._repr_html_()
|
||||
|
||||
|
@ -20,13 +20,22 @@ template = """
|
||||
</div>
|
||||
|
||||
"""
|
||||
template_content_indent = ' '
|
||||
template_content_indent = " "
|
||||
|
||||
|
||||
def cq_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
def cq_directive(
|
||||
name,
|
||||
arguments,
|
||||
options,
|
||||
content,
|
||||
lineno,
|
||||
content_offset,
|
||||
block_text,
|
||||
state,
|
||||
state_machine,
|
||||
):
|
||||
# only consider inline snippets
|
||||
plot_code = '\n'.join(content)
|
||||
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
|
||||
@ -52,22 +61,20 @@ def cq_directive(name, arguments, options, content, lineno,
|
||||
lines = []
|
||||
|
||||
# get rid of new lines
|
||||
out_svg = out_svg.replace('\n', '')
|
||||
out_svg = out_svg.replace("\n", "")
|
||||
|
||||
txt_align = "left"
|
||||
if "align" in options:
|
||||
txt_align = options['align']
|
||||
txt_align = options["align"]
|
||||
|
||||
lines.extend((template % locals()).split('\n'))
|
||||
lines.extend((template % locals()).split("\n"))
|
||||
|
||||
lines.extend(['::', ''])
|
||||
lines.extend([' %s' % row.rstrip()
|
||||
for row in plot_code.split('\n')])
|
||||
lines.append('')
|
||||
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))
|
||||
state_machine.insert_input(lines, state_machine.input_lines.source(0))
|
||||
|
||||
return []
|
||||
|
||||
@ -77,9 +84,10 @@ def setup(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
|
||||
}
|
||||
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)
|
||||
app.add_directive("cq_plot", cq_directive, True, (0, 2, 0), **options)
|
||||
|
142
cadquery/cqgi.py
142
cadquery/cqgi.py
@ -9,6 +9,7 @@ import cadquery
|
||||
|
||||
CQSCRIPT = "<cqscript>"
|
||||
|
||||
|
||||
def parse(script_source):
|
||||
"""
|
||||
Parses the script as a model, and returns a model.
|
||||
@ -34,6 +35,7 @@ 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.
|
||||
@ -100,16 +102,20 @@ class CQModel(object):
|
||||
try:
|
||||
self.set_param_values(build_parameters)
|
||||
collector = ScriptCallback()
|
||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||
.add_entry("__name__", "__cqgi__") \
|
||||
.add_entry("show_object", collector.show_object) \
|
||||
.add_entry("debug", collector.debug) \
|
||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
||||
env = (
|
||||
EnvironmentBuilder()
|
||||
.with_real_builtins()
|
||||
.with_cadquery_objects()
|
||||
.add_entry("__name__", "__cqgi__")
|
||||
.add_entry("show_object", collector.show_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 )
|
||||
c = compile(self.ast_tree, CQSCRIPT, "exec")
|
||||
exec(c, env)
|
||||
result.set_debug(collector.debugObjects)
|
||||
result.set_success_result(collector.outputObjects)
|
||||
|
||||
except Exception as ex:
|
||||
@ -124,7 +130,9 @@ class CQModel(object):
|
||||
|
||||
for k, v in params.items():
|
||||
if k not in model_parameters:
|
||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '%s': not a parameter of the model." % k
|
||||
)
|
||||
|
||||
p = model_parameters[k]
|
||||
p.set_value(v)
|
||||
@ -134,10 +142,12 @@ 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,10 +159,11 @@ class BuildResult(object):
|
||||
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.results = [] # list of ShapeResult
|
||||
self.debugObjects = [] # list of ShapeResult
|
||||
self.first_result = None
|
||||
self.success = False
|
||||
self.exception = None
|
||||
@ -176,13 +187,14 @@ 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):
|
||||
def add_parameter_description(self, name, description):
|
||||
p = self.parameters[name]
|
||||
p.desc = description
|
||||
|
||||
@ -214,6 +226,7 @@ class InputParameter:
|
||||
provide additional metadata
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
#: the default value for the variable.
|
||||
@ -234,7 +247,9 @@ class InputParameter:
|
||||
self.ast_node = None
|
||||
|
||||
@staticmethod
|
||||
def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
|
||||
def create(
|
||||
ast_node, var_name, var_type, default_value, valid_values=None, desc=None
|
||||
):
|
||||
|
||||
if valid_values is None:
|
||||
valid_values = []
|
||||
@ -251,8 +266,10 @@ class InputParameter:
|
||||
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)))
|
||||
"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:
|
||||
@ -265,28 +282,33 @@ class InputParameter:
|
||||
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))
|
||||
"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:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = True
|
||||
else:
|
||||
self.ast_node.id = 'True'
|
||||
self.ast_node.id = "True"
|
||||
else:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = False
|
||||
else:
|
||||
self.ast_node.id = 'False'
|
||||
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))
|
||||
self.name,
|
||||
str(self.varType),
|
||||
str(self.default_value),
|
||||
)
|
||||
|
||||
|
||||
class ScriptCallback(object):
|
||||
@ -295,22 +317,23 @@ class ScriptCallback(object):
|
||||
the show_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 show_object(self, shape,options={}):
|
||||
def show_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 environment
|
||||
"""
|
||||
o = ShapeResult()
|
||||
o.options=options
|
||||
o.options = options
|
||||
o.shape = shape
|
||||
self.outputObjects.append(o)
|
||||
|
||||
def debug(self,obj,args={}):
|
||||
def debug(self, obj, args={}):
|
||||
"""
|
||||
Debug print/output an object, with optional arguments.
|
||||
"""
|
||||
@ -319,7 +342,7 @@ class ScriptCallback(object):
|
||||
s.options = args
|
||||
self.debugObjects.append(s)
|
||||
|
||||
def describe_parameter(self,var_data ):
|
||||
def describe_parameter(self, var_data):
|
||||
"""
|
||||
Do Nothing-- we parsed the ast ahead of execution to get what we need.
|
||||
"""
|
||||
@ -335,12 +358,12 @@ class ScriptCallback(object):
|
||||
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
|
||||
|
||||
|
||||
@ -349,6 +372,7 @@ class NoOutputError(Exception):
|
||||
Raised when the script does not execute the show_object() method to
|
||||
return a solid
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -386,6 +410,7 @@ class EnvironmentBuilder(object):
|
||||
The environment includes the builtins, as well as
|
||||
the other methods the script will need.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
@ -393,12 +418,12 @@ class EnvironmentBuilder(object):
|
||||
return self.with_builtins(__builtins__)
|
||||
|
||||
def with_builtins(self, env_dict):
|
||||
self.env['__builtins__'] = env_dict
|
||||
self.env["__builtins__"] = env_dict
|
||||
return self
|
||||
|
||||
def with_cadquery_objects(self):
|
||||
self.env['cadquery'] = cadquery
|
||||
self.env['cq'] = cadquery
|
||||
self.env["cadquery"] = cadquery
|
||||
self.env["cq"] = cadquery
|
||||
return self
|
||||
|
||||
def add_entry(self, name, value):
|
||||
@ -408,30 +433,33 @@ class EnvironmentBuilder(object):
|
||||
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):
|
||||
"""
|
||||
def visit_Call(self, node):
|
||||
"""
|
||||
Called when we see a function call. Is it describe_parameter?
|
||||
"""
|
||||
try:
|
||||
if node.func.id == '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)
|
||||
self.cqModel.add_parameter_description(varname, desc)
|
||||
|
||||
except:
|
||||
#print "Unable to handle function call"
|
||||
except:
|
||||
# print "Unable to handle function call"
|
||||
pass
|
||||
return node
|
||||
return node
|
||||
|
||||
|
||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
"""
|
||||
@ -446,24 +474,42 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
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))
|
||||
InputParameter.create(
|
||||
value_node, var_name, StringParameterType, value_node.s
|
||||
)
|
||||
)
|
||||
elif type(value_node) == ast.Name:
|
||||
if value_node.id == 'True':
|
||||
if value_node.id == "True":
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.id == 'False':
|
||||
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, False))
|
||||
elif hasattr(ast, 'NameConstant') and type(value_node) == ast.NameConstant:
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
elif hasattr(ast, "NameConstant") and type(value_node) == ast.NameConstant:
|
||||
if value_node.value == True:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, True
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
except:
|
||||
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||
pass
|
||||
@ -479,7 +525,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
# Handle the NamedConstant type that is only present in Python 3
|
||||
astTypes = [ast.Num, ast.Str, ast.Name]
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
astTypes.append(ast.NameConstant)
|
||||
|
||||
if type(node.value) in astTypes:
|
||||
|
@ -4,6 +4,7 @@ from OCC.Core.Visualization import Tesselator
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
import cStringIO as StringIO
|
||||
else:
|
||||
@ -116,7 +117,7 @@ def readAndDeleteFile(fileName):
|
||||
return the contents as a string
|
||||
"""
|
||||
res = ""
|
||||
with open(fileName, 'r') as f:
|
||||
with open(fileName, "r") as f:
|
||||
res = "{}".format(f.read())
|
||||
|
||||
os.remove(fileName)
|
||||
@ -152,34 +153,34 @@ class AmfWriter(object):
|
||||
self.tessellation = tessellation
|
||||
|
||||
def writeAmf(self, outFile):
|
||||
amf = ET.Element('amf', units=self.units)
|
||||
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')
|
||||
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 i_vert in range(self.tessellation.ObjGetVertexCount()):
|
||||
v = self.tessellation.GetVertex(i_vert)
|
||||
vtx = ET.SubElement(vertices, 'vertex')
|
||||
coord = ET.SubElement(vtx, 'coordinates')
|
||||
x = ET.SubElement(coord, 'x')
|
||||
vtx = ET.SubElement(vertices, "vertex")
|
||||
coord = ET.SubElement(vtx, "coordinates")
|
||||
x = ET.SubElement(coord, "x")
|
||||
x.text = str(v[0])
|
||||
y = ET.SubElement(coord, 'y')
|
||||
y = ET.SubElement(coord, "y")
|
||||
y.text = str(v[1])
|
||||
z = ET.SubElement(coord, 'z')
|
||||
z = ET.SubElement(coord, "z")
|
||||
z.text = str(v[2])
|
||||
|
||||
# add triangles
|
||||
for i_tr in range(self.tessellation.ObjGetTriangleCount()):
|
||||
t = self.tessellation.GetTriangleIndex(i_tr)
|
||||
triangle = ET.SubElement(volume, 'triangle')
|
||||
v1 = ET.SubElement(triangle, 'v1')
|
||||
triangle = ET.SubElement(volume, "triangle")
|
||||
v1 = ET.SubElement(triangle, "v1")
|
||||
v1.text = str(t[0])
|
||||
v2 = ET.SubElement(triangle, 'v2')
|
||||
v2 = ET.SubElement(triangle, "v2")
|
||||
v2.text = str(t[1])
|
||||
v3 = ET.SubElement(triangle, 'v3')
|
||||
v3 = ET.SubElement(triangle, "v3")
|
||||
v3.text = str(t[2])
|
||||
|
||||
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
|
||||
@ -217,11 +218,11 @@ class JsonMesh(object):
|
||||
|
||||
def toJson(self):
|
||||
return JSON_TEMPLATE % {
|
||||
'vertices': str(self.vertices),
|
||||
'faces': str(self.faces),
|
||||
'nVertices': self.nVertices,
|
||||
'nFaces': self.nFaces
|
||||
};
|
||||
"vertices": str(self.vertices),
|
||||
"faces": str(self.faces),
|
||||
"nVertices": self.nVertices,
|
||||
"nFaces": self.nFaces,
|
||||
}
|
||||
|
||||
|
||||
def makeSVGedge(e):
|
||||
@ -235,20 +236,16 @@ def makeSVGedge(e):
|
||||
start = curve.FirstParameter()
|
||||
end = curve.LastParameter()
|
||||
|
||||
points = GCPnts_QuasiUniformDeflection(curve,
|
||||
DISCRETIZATION_TOLERANCE,
|
||||
start,
|
||||
end)
|
||||
points = GCPnts_QuasiUniformDeflection(curve, DISCRETIZATION_TOLERANCE, start, end)
|
||||
|
||||
if points.IsDone():
|
||||
point_it = (points.Value(i + 1) for i in
|
||||
range(points.NbPoints()))
|
||||
point_it = (points.Value(i + 1) for i in range(points.NbPoints()))
|
||||
|
||||
p = next(point_it)
|
||||
cs.write('M{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("M{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
for p in point_it:
|
||||
cs.write('L{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("L{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
return cs.getvalue()
|
||||
|
||||
@ -277,7 +274,7 @@ def getSVG(shape, opts=None):
|
||||
Export a shape to SVG
|
||||
"""
|
||||
|
||||
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
||||
d = {"width": 800, "height": 240, "marginLeft": 200, "marginTop": 20}
|
||||
|
||||
if opts:
|
||||
d.update(opts)
|
||||
@ -285,17 +282,15 @@ def getSVG(shape, opts=None):
|
||||
# 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'])
|
||||
width = float(d["width"])
|
||||
height = float(d["height"])
|
||||
marginLeft = float(d["marginLeft"])
|
||||
marginTop = float(d["marginTop"])
|
||||
|
||||
hlr = HLRBRep_Algo()
|
||||
hlr.Add(shape.wrapped)
|
||||
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
||||
DEFAULT_DIR)
|
||||
)
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(), DEFAULT_DIR))
|
||||
|
||||
hlr.Projector(projector)
|
||||
hlr.Update()
|
||||
@ -336,8 +331,7 @@ def getSVG(shape, opts=None):
|
||||
# convert to native CQ objects
|
||||
visible = list(map(Shape, visible))
|
||||
hidden = list(map(Shape, hidden))
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible,
|
||||
hidden)
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible, hidden)
|
||||
|
||||
# get bounding box -- these are all in 2-d space
|
||||
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
||||
@ -346,8 +340,10 @@ def getSVG(shape, opts=None):
|
||||
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
||||
|
||||
# compute amount to translate-- move the top left into view
|
||||
(xTranslate, yTranslate) = ((0 - bb.xmin) + marginLeft /
|
||||
unitScale, (0 - bb.ymax) - marginTop / unitScale)
|
||||
(xTranslate, yTranslate) = (
|
||||
(0 - bb.xmin) + marginLeft / unitScale,
|
||||
(0 - bb.ymax) - marginTop / unitScale,
|
||||
)
|
||||
|
||||
# compute paths ( again -- had to strip out freecad crap )
|
||||
hiddenContent = ""
|
||||
@ -362,19 +358,19 @@ def getSVG(shape, opts=None):
|
||||
{
|
||||
"unitScale": str(unitScale),
|
||||
"strokeWidth": str(1.0 / unitScale),
|
||||
"hiddenContent": hiddenContent,
|
||||
"hiddenContent": hiddenContent,
|
||||
"visibleContent": visibleContent,
|
||||
"xTranslate": str(xTranslate),
|
||||
"yTranslate": str(yTranslate),
|
||||
"width": str(width),
|
||||
"height": str(height),
|
||||
"textboxY": str(height - 30),
|
||||
"uom": str(uom)
|
||||
"uom": str(uom),
|
||||
}
|
||||
)
|
||||
# svg = SVG_TEMPLATE % (
|
||||
# {"content": projectedContent}
|
||||
#)
|
||||
# )
|
||||
return svg
|
||||
|
||||
|
||||
@ -386,7 +382,7 @@ def exportSVG(shape, fileName):
|
||||
"""
|
||||
|
||||
svg = getSVG(shape.val())
|
||||
f = open(fileName, 'w')
|
||||
f = open(fileName, "w")
|
||||
f.write(svg)
|
||||
f.close()
|
||||
|
||||
@ -471,4 +467,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||
PATHTEMPLATE = '\t\t\t<path d="%s" />\n'
|
||||
|
@ -1,6 +1,16 @@
|
||||
import math
|
||||
|
||||
from OCC.Core.gp import gp_Vec, gp_Ax1, gp_Ax3, gp_Pnt, gp_Dir, gp_Trsf, gp_GTrsf, gp, gp_XYZ
|
||||
from OCC.Core.gp import (
|
||||
gp_Vec,
|
||||
gp_Ax1,
|
||||
gp_Ax3,
|
||||
gp_Pnt,
|
||||
gp_Dir,
|
||||
gp_Trsf,
|
||||
gp_GTrsf,
|
||||
gp,
|
||||
gp_XYZ,
|
||||
)
|
||||
from OCC.Core.Bnd import Bnd_Box
|
||||
from OCC.Core.BRepBndLib import brepbndlib_Add # brepbndlib_AddOptimal
|
||||
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
|
||||
@ -27,16 +37,16 @@ class Vector(object):
|
||||
if len(args) == 3:
|
||||
fV = gp_Vec(*args)
|
||||
elif len(args) == 2:
|
||||
fV = gp_Vec(*args,0)
|
||||
fV = gp_Vec(*args, 0)
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], Vector):
|
||||
fV = gp_Vec(args[0].wrapped.XYZ())
|
||||
elif isinstance(args[0], (tuple, list)):
|
||||
arg = args[0]
|
||||
if len(arg)==3:
|
||||
if len(arg) == 3:
|
||||
fV = gp_Vec(*arg)
|
||||
elif len(arg)==2:
|
||||
fV = gp_Vec(*arg,0)
|
||||
elif len(arg) == 2:
|
||||
fV = gp_Vec(*arg, 0)
|
||||
elif isinstance(args[0], (gp_Vec, gp_Pnt, gp_Dir)):
|
||||
fV = gp_Vec(args[0].XYZ())
|
||||
elif isinstance(args[0], gp_XYZ):
|
||||
@ -55,7 +65,7 @@ class Vector(object):
|
||||
return self.wrapped.X()
|
||||
|
||||
@x.setter
|
||||
def x(self,value):
|
||||
def x(self, value):
|
||||
self.wrapped.SetX(value)
|
||||
|
||||
@property
|
||||
@ -63,7 +73,7 @@ class Vector(object):
|
||||
return self.wrapped.Y()
|
||||
|
||||
@y.setter
|
||||
def y(self,value):
|
||||
def y(self, value):
|
||||
self.wrapped.SetY(value)
|
||||
|
||||
@property
|
||||
@ -71,7 +81,7 @@ class Vector(object):
|
||||
return self.wrapped.Z()
|
||||
|
||||
@z.setter
|
||||
def z(self,value):
|
||||
def z(self, value):
|
||||
self.wrapped.SetZ(value)
|
||||
|
||||
@property
|
||||
@ -132,16 +142,13 @@ class Vector(object):
|
||||
return self.wrapped.Angle(v.wrapped)
|
||||
|
||||
def distanceToLine(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
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!")
|
||||
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!")
|
||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
||||
|
||||
def projectToPlane(self, plane):
|
||||
"""
|
||||
@ -154,7 +161,7 @@ class Vector(object):
|
||||
base = plane.origin
|
||||
normal = plane.zDir
|
||||
|
||||
return self-normal*(((self-base).dot(normal))/normal.Length**2)
|
||||
return self - normal * (((self - base).dot(normal)) / normal.Length ** 2)
|
||||
|
||||
def __neg__(self):
|
||||
return self * -1
|
||||
@ -163,18 +170,19 @@ class Vector(object):
|
||||
return self.Length
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __str__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001)
|
||||
'''
|
||||
|
||||
"""
|
||||
is not implemented in OCC
|
||||
def __ne__(self, other):
|
||||
return self.wrapped.__ne__(other)
|
||||
'''
|
||||
"""
|
||||
|
||||
def toPnt(self):
|
||||
|
||||
@ -222,44 +230,48 @@ class Matrix:
|
||||
elif isinstance(matrix, (list, tuple)):
|
||||
# Validate matrix size & 4x4 last row value
|
||||
valid_sizes = all(
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4))
|
||||
for row in matrix
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4)) for row in matrix
|
||||
) and len(matrix) in (3, 4)
|
||||
if not valid_sizes:
|
||||
raise TypeError("Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(matrix))
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0,0,0,1)):
|
||||
raise ValueError("Expected the last row to be [0,0,0,1], but got: {!r}".format(matrix[3]))
|
||||
raise TypeError(
|
||||
"Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(
|
||||
matrix
|
||||
)
|
||||
)
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0, 0, 0, 1)):
|
||||
raise ValueError(
|
||||
"Expected the last row to be [0,0,0,1], but got: {!r}".format(
|
||||
matrix[3]
|
||||
)
|
||||
)
|
||||
|
||||
# Assign values to matrix
|
||||
self.wrapped = gp_GTrsf()
|
||||
[self.wrapped.SetValue(i+1,j+1,e)
|
||||
for i,row in enumerate(matrix[:3])
|
||||
for j,e in enumerate(row)]
|
||||
[
|
||||
self.wrapped.SetValue(i + 1, j + 1, e)
|
||||
for i, row in enumerate(matrix[:3])
|
||||
for j, e in enumerate(row)
|
||||
]
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid param to matrix constructor: {}".format(matrix))
|
||||
raise TypeError("Invalid param to matrix constructor: {}".format(matrix))
|
||||
|
||||
def rotateX(self, angle):
|
||||
|
||||
self._rotate(gp.OX(),
|
||||
angle)
|
||||
self._rotate(gp.OX(), angle)
|
||||
|
||||
def rotateY(self, angle):
|
||||
|
||||
self._rotate(gp.OY(),
|
||||
angle)
|
||||
self._rotate(gp.OY(), angle)
|
||||
|
||||
def rotateZ(self, angle):
|
||||
|
||||
self._rotate(gp.OZ(),
|
||||
angle)
|
||||
self._rotate(gp.OZ(), angle)
|
||||
|
||||
def _rotate(self, direction, angle):
|
||||
|
||||
new = gp_Trsf()
|
||||
new.SetRotation(direction,
|
||||
angle)
|
||||
new.SetRotation(direction, angle)
|
||||
|
||||
self.wrapped = self.wrapped * gp_GTrsf(new)
|
||||
|
||||
@ -279,8 +291,9 @@ class Matrix:
|
||||
"""
|
||||
|
||||
trsf = self.wrapped
|
||||
data = [[trsf.Value(i,j) for j in range(1,5)] for i in range(1,4)] + \
|
||||
[[0.,0.,0.,1.]]
|
||||
data = [[trsf.Value(i, j) for j in range(1, 5)] for i in range(1, 4)] + [
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
]
|
||||
|
||||
return [data[j][i] for i in range(4) for j in range(4)]
|
||||
|
||||
@ -298,7 +311,7 @@ class Matrix:
|
||||
else:
|
||||
# gp_GTrsf doesn't provide access to the 4th row because it has
|
||||
# an implied value as below:
|
||||
return [0., 0., 0., 1.][c]
|
||||
return [0.0, 0.0, 0.0, 1.0][c]
|
||||
else:
|
||||
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
||||
|
||||
@ -352,95 +365,94 @@ class Plane(object):
|
||||
|
||||
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))
|
||||
"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(
|
||||
list(namedPlanes.keys())))
|
||||
raise ValueError("Supported names are {}".format(list(namedPlanes.keys())))
|
||||
|
||||
@classmethod
|
||||
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XY', origin)
|
||||
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = Plane.named("bottom", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@ -458,12 +470,12 @@ class Plane(object):
|
||||
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
||||
"""
|
||||
zDir = Vector(normal)
|
||||
if (zDir.Length == 0.0):
|
||||
raise ValueError('normal should be non null')
|
||||
if zDir.Length == 0.0:
|
||||
raise ValueError("normal should be non null")
|
||||
|
||||
xDir = Vector(xDir)
|
||||
if (xDir.Length == 0.0):
|
||||
raise ValueError('xDir should be non null')
|
||||
if xDir.Length == 0.0:
|
||||
raise ValueError("xDir should be non null")
|
||||
|
||||
self.zDir = zDir.normalized()
|
||||
self._setPlaneDir(xDir)
|
||||
@ -489,7 +501,8 @@ class Plane(object):
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
@ -545,7 +558,7 @@ class Plane(object):
|
||||
|
||||
pass
|
||||
|
||||
'''
|
||||
"""
|
||||
# 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
|
||||
@ -562,7 +575,7 @@ class Plane(object):
|
||||
# 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
|
||||
@ -588,7 +601,9 @@ class Plane(object):
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know how to convert type {} to local coordinates".format(
|
||||
type(obj)))
|
||||
type(obj)
|
||||
)
|
||||
)
|
||||
|
||||
def toWorldCoords(self, tuplePoint):
|
||||
"""Convert a point in local coordinates to global coordinates
|
||||
@ -655,7 +670,7 @@ class Plane(object):
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
'''
|
||||
"""
|
||||
resultWires = []
|
||||
for w in listOfShapes:
|
||||
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
||||
@ -681,21 +696,19 @@ class Plane(object):
|
||||
|
||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||
|
||||
return resultWires'''
|
||||
return resultWires"""
|
||||
|
||||
def mirrorInPlane(self, listOfShapes, axis='X'):
|
||||
def mirrorInPlane(self, listOfShapes, axis="X"):
|
||||
|
||||
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
||||
self.zDir.toDir(),
|
||||
self.xDir.toDir())
|
||||
local_coord_system = gp_Ax3(
|
||||
self.origin.toPnt(), self.zDir.toDir(), self.xDir.toDir()
|
||||
)
|
||||
T = gp_Trsf()
|
||||
|
||||
if axis == 'X':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.XDirection()))
|
||||
elif axis == 'Y':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.YDirection()))
|
||||
if axis == "X":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.XDirection()))
|
||||
elif axis == "Y":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.YDirection()))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -731,17 +744,16 @@ class Plane(object):
|
||||
inverseT = gp_Trsf()
|
||||
|
||||
global_coord_system = gp_Ax3()
|
||||
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple())
|
||||
)
|
||||
local_coord_system = gp_Ax3(
|
||||
gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple()),
|
||||
)
|
||||
|
||||
forwardT.SetTransformation(global_coord_system,
|
||||
local_coord_system)
|
||||
forwardT.SetTransformation(global_coord_system, local_coord_system)
|
||||
forward.wrapped = gp_GTrsf(forwardT)
|
||||
|
||||
inverseT.SetTransformation(local_coord_system,
|
||||
global_coord_system)
|
||||
inverseT.SetTransformation(local_coord_system, global_coord_system)
|
||||
inverse.wrapped = gp_GTrsf(inverseT)
|
||||
|
||||
# TODO verify if this is OK
|
||||
@ -767,11 +779,9 @@ class BoundBox(object):
|
||||
self.zmax = ZMax
|
||||
self.zlen = ZMax - ZMin
|
||||
|
||||
self.center = Vector((XMax + XMin) / 2,
|
||||
(YMax + YMin) / 2,
|
||||
(ZMax + ZMin) / 2)
|
||||
self.center = Vector((XMax + XMin) / 2, (YMax + YMin) / 2, (ZMax + ZMin) / 2)
|
||||
|
||||
self.DiagonalLength = self.wrapped.SquareExtent()**0.5
|
||||
self.DiagonalLength = self.wrapped.SquareExtent() ** 0.5
|
||||
|
||||
def add(self, obj, tol=1e-8):
|
||||
"""Returns a modified (expanded) bounding box
|
||||
@ -810,25 +820,29 @@ class BoundBox(object):
|
||||
the built-in implementation i do not understand.
|
||||
"""
|
||||
|
||||
if (bb1.XMin < bb2.XMin and
|
||||
bb1.XMax > bb2.XMax and
|
||||
bb1.YMin < bb2.YMin and
|
||||
bb1.YMax > bb2.YMax):
|
||||
if (
|
||||
bb1.XMin < bb2.XMin
|
||||
and bb1.XMax > bb2.XMax
|
||||
and bb1.YMin < bb2.YMin
|
||||
and bb1.YMax > bb2.YMax
|
||||
):
|
||||
return bb1
|
||||
|
||||
if (bb2.XMin < bb1.XMin and
|
||||
bb2.XMax > bb1.XMax and
|
||||
bb2.YMin < bb1.YMin and
|
||||
bb2.YMax > bb1.YMax):
|
||||
if (
|
||||
bb2.XMin < bb1.XMin
|
||||
and bb2.XMax > bb1.XMax
|
||||
and bb2.YMin < bb1.YMin
|
||||
and bb2.YMax > bb1.YMax
|
||||
):
|
||||
return bb2
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _fromTopoDS(cls, shape, tol=None, optimal=False):
|
||||
'''
|
||||
"""
|
||||
Constructs a bounding box from a TopoDS_Shape
|
||||
'''
|
||||
"""
|
||||
tol = TOL if tol is None else tol # tol = TOL (by default)
|
||||
bbox = Bnd_Box()
|
||||
bbox.SetGap(tol)
|
||||
@ -845,12 +859,14 @@ class BoundBox(object):
|
||||
|
||||
def isInside(self, b2):
|
||||
"""Is the provided bounding box inside this one?"""
|
||||
if (b2.xmin > self.xmin and
|
||||
b2.ymin > self.ymin and
|
||||
b2.zmin > self.zmin and
|
||||
b2.xmax < self.xmax and
|
||||
b2.ymax < self.ymax and
|
||||
b2.zmax < self.zmax):
|
||||
if (
|
||||
b2.xmin > self.xmin
|
||||
and b2.ymin > self.ymin
|
||||
and b2.zmin > self.zmin
|
||||
and b2.xmax < self.xmax
|
||||
and b2.ymax < self.ymax
|
||||
and b2.zmax < self.zmax
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -41,7 +41,7 @@ def importStep(fileName):
|
||||
if readStatus != OCC.Core.IFSelect.IFSelect_RetDone:
|
||||
raise ValueError("STEP File could not be loaded")
|
||||
for i in range(reader.NbRootsForTransfer()):
|
||||
reader.TransferRoot(i+1)
|
||||
reader.TransferRoot(i + 1)
|
||||
|
||||
occ_shapes = []
|
||||
for i in range(reader.NbShapes()):
|
||||
|
@ -6,8 +6,7 @@ from xml.etree import ElementTree
|
||||
|
||||
from .geom import BoundBox
|
||||
|
||||
BOILERPLATE = \
|
||||
'''
|
||||
BOILERPLATE = """
|
||||
<link rel='stylesheet' type='text/css' href='http://www.x3dom.org/download/x3dom.css'></link>
|
||||
<div style='height: {height}px; width: 100%;' width='100%' height='{height}px'>
|
||||
<x3d style='height: {height}px; width: 100%;' id='{id}' width='100%' height='{height}px'>
|
||||
@ -35,69 +34,86 @@ BOILERPLATE = \
|
||||
|
||||
//document.getElementById('{id}').runtime.fitAll()
|
||||
</script>
|
||||
'''
|
||||
"""
|
||||
|
||||
#https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
|
||||
#better if else
|
||||
# https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
|
||||
# better if else
|
||||
|
||||
ROT = (0.77,0.3,0.55,1.28)
|
||||
ROT = (0.,0,0,1.)
|
||||
ROT = (0.77, 0.3, 0.55, 1.28)
|
||||
ROT = (0.0, 0, 0, 1.0)
|
||||
FOV = 0.2
|
||||
|
||||
def add_x3d_boilerplate(src, height=400, center=(0,0,0), d=(0,0,15), fov=FOV, rot='{} {} {} {} '.format(*ROT)):
|
||||
|
||||
return BOILERPLATE.format(src=src,
|
||||
id=uuid4(),
|
||||
height=height,
|
||||
x=d[0],
|
||||
y=d[1],
|
||||
z=d[2],
|
||||
x0=center[0],
|
||||
y0=center[1],
|
||||
z0=center[2],
|
||||
fov=fov,
|
||||
rot=rot)
|
||||
def add_x3d_boilerplate(
|
||||
src,
|
||||
height=400,
|
||||
center=(0, 0, 0),
|
||||
d=(0, 0, 15),
|
||||
fov=FOV,
|
||||
rot="{} {} {} {} ".format(*ROT),
|
||||
):
|
||||
|
||||
def x3d_display(shape,
|
||||
vertex_shader=None,
|
||||
fragment_shader=None,
|
||||
export_edges=True,
|
||||
color=(1,1,0),
|
||||
specular_color=(1,1,1),
|
||||
shininess=0.4,
|
||||
transparency=0.4,
|
||||
line_color=(0,0,0),
|
||||
line_width=2.,
|
||||
mesh_quality=.3):
|
||||
return BOILERPLATE.format(
|
||||
src=src,
|
||||
id=uuid4(),
|
||||
height=height,
|
||||
x=d[0],
|
||||
y=d[1],
|
||||
z=d[2],
|
||||
x0=center[0],
|
||||
y0=center[1],
|
||||
z0=center[2],
|
||||
fov=fov,
|
||||
rot=rot,
|
||||
)
|
||||
|
||||
# Export to XML <Scene> tag
|
||||
exporter = X3DExporter(shape,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
export_edges,
|
||||
color,
|
||||
specular_color,
|
||||
shininess,
|
||||
transparency,
|
||||
line_color,
|
||||
line_width,
|
||||
mesh_quality)
|
||||
|
||||
exporter.compute()
|
||||
x3d_str = exporter.to_x3dfile_string(shape_id=0)
|
||||
xml_et = ElementTree.fromstring(x3d_str)
|
||||
scene_tag = xml_et.find('./Scene')
|
||||
def x3d_display(
|
||||
shape,
|
||||
vertex_shader=None,
|
||||
fragment_shader=None,
|
||||
export_edges=True,
|
||||
color=(1, 1, 0),
|
||||
specular_color=(1, 1, 1),
|
||||
shininess=0.4,
|
||||
transparency=0.4,
|
||||
line_color=(0, 0, 0),
|
||||
line_width=2.0,
|
||||
mesh_quality=0.3,
|
||||
):
|
||||
|
||||
# Viewport Parameters
|
||||
bb = BoundBox._fromTopoDS(shape)
|
||||
d = max(bb.xlen,bb.ylen,bb.zlen)
|
||||
c = bb.center
|
||||
# Export to XML <Scene> tag
|
||||
exporter = X3DExporter(
|
||||
shape,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
export_edges,
|
||||
color,
|
||||
specular_color,
|
||||
shininess,
|
||||
transparency,
|
||||
line_color,
|
||||
line_width,
|
||||
mesh_quality,
|
||||
)
|
||||
|
||||
vec = gp_Vec(0,0,d/1.5/tan(FOV/2))
|
||||
quat = gp_Quaternion(*ROT)
|
||||
vec = quat*(vec) + c.wrapped
|
||||
exporter.compute()
|
||||
x3d_str = exporter.to_x3dfile_string(shape_id=0)
|
||||
xml_et = ElementTree.fromstring(x3d_str)
|
||||
scene_tag = xml_et.find("./Scene")
|
||||
|
||||
# return boilerplate + Scene
|
||||
return add_x3d_boilerplate(ElementTree.tostring(scene_tag).decode('utf-8'),
|
||||
d=(vec.X(),vec.Y(),vec.Z()),
|
||||
center=(c.x,c.y,c.z))
|
||||
# Viewport Parameters
|
||||
bb = BoundBox._fromTopoDS(shape)
|
||||
d = max(bb.xlen, bb.ylen, bb.zlen)
|
||||
c = bb.center
|
||||
|
||||
vec = gp_Vec(0, 0, d / 1.5 / tan(FOV / 2))
|
||||
quat = gp_Quaternion(*ROT)
|
||||
vec = quat * (vec) + c.wrapped
|
||||
|
||||
# return boilerplate + Scene
|
||||
return add_x3d_boilerplate(
|
||||
ElementTree.tostring(scene_tag).decode("utf-8"),
|
||||
d=(vec.X(), vec.Y(), vec.Z()),
|
||||
center=(c.x, c.y, c.z),
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,22 @@ 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
|
||||
from pyparsing import (
|
||||
Literal,
|
||||
Word,
|
||||
nums,
|
||||
Optional,
|
||||
Combine,
|
||||
oneOf,
|
||||
upcaseTokens,
|
||||
CaselessLiteral,
|
||||
Group,
|
||||
infixNotation,
|
||||
opAssoc,
|
||||
Forward,
|
||||
ZeroOrMore,
|
||||
Keyword,
|
||||
)
|
||||
from functools import reduce
|
||||
|
||||
|
||||
@ -81,7 +94,6 @@ class NearestToPointSelector(Selector):
|
||||
self.pnt = pnt
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
def dist(tShape):
|
||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||
# if tShape.ShapeType == 'Vertex':
|
||||
@ -121,15 +133,18 @@ class BoxSelector(Selector):
|
||||
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))
|
||||
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)):
|
||||
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()):
|
||||
@ -168,7 +183,9 @@ class BaseDirSelector(Selector):
|
||||
|
||||
if self.test(normal):
|
||||
r.append(o)
|
||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
||||
elif type(o) == Edge and (
|
||||
o.geomType() == "LINE" or o.geomType() == "PLANE"
|
||||
):
|
||||
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||
tangent = o.tangentAt()
|
||||
if self.test(tangent):
|
||||
@ -247,8 +264,7 @@ class PerpendicularDirSelector(BaseDirSelector):
|
||||
|
||||
def test(self, vec):
|
||||
angle = self.direction.getAngle(vec)
|
||||
r = (abs(angle) < self.TOLERANCE) or (
|
||||
abs(angle - math.pi) < self.TOLERANCE)
|
||||
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE)
|
||||
return not r
|
||||
|
||||
|
||||
@ -314,17 +330,16 @@ class DirectionMinMaxSelector(Selector):
|
||||
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(list(objectDict.items()),
|
||||
key=lambda x: x[0]))
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()), key=lambda x: x[0]))
|
||||
|
||||
# find out the max/min distance
|
||||
if self.directionMax:
|
||||
@ -370,8 +385,9 @@ class DirectionNthSelector(ParallelDirSelector):
|
||||
objectDict[round(distance(el), digits)].append(el)
|
||||
|
||||
# choose the Nth unique rounded distance
|
||||
nth_distance = sorted(list(objectDict.keys()),
|
||||
reverse=not self.directionMax)[self.N]
|
||||
nth_distance = sorted(list(objectDict.keys()), reverse=not self.directionMax)[
|
||||
self.N
|
||||
]
|
||||
|
||||
# map back to original objects and return
|
||||
return objectDict[nth_distance]
|
||||
@ -388,8 +404,9 @@ class BinarySelector(Selector):
|
||||
self.right = right
|
||||
|
||||
def filter(self, objectList):
|
||||
return self.filterResults(self.left.filter(objectList),
|
||||
self.right.filter(objectList))
|
||||
return self.filterResults(
|
||||
self.left.filter(objectList), self.right.filter(objectList)
|
||||
)
|
||||
|
||||
def filterResults(self, r_left, r_right):
|
||||
raise NotImplementedError
|
||||
@ -445,52 +462,56 @@ def _makeGrammar():
|
||||
"""
|
||||
|
||||
# float definition
|
||||
point = Literal('.')
|
||||
plusmin = Literal('+') | Literal('-')
|
||||
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)
|
||||
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')
|
||||
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 = oneOf(
|
||||
["Plane", "Cylinder", "Sphere", "Cone", "Line", "Circle", "Arc"], caseless=True
|
||||
)
|
||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||
|
||||
# type operator
|
||||
type_op = Literal('%')
|
||||
type_op = Literal("%")
|
||||
|
||||
# direction operator
|
||||
direction_op = oneOf(['>', '<'])
|
||||
direction_op = oneOf([">", "<"])
|
||||
|
||||
# index definition
|
||||
ix_number = Group(Optional('-') + Word(nums))
|
||||
lsqbracket = Literal('[').suppress()
|
||||
rsqbracket = Literal(']').suppress()
|
||||
ix_number = Group(Optional("-") + Word(nums))
|
||||
lsqbracket = Literal("[").suppress()
|
||||
rsqbracket = Literal("]").suppress()
|
||||
|
||||
index = lsqbracket + ix_number('index') + rsqbracket
|
||||
index = lsqbracket + ix_number("index") + rsqbracket
|
||||
|
||||
# other operators
|
||||
other_op = oneOf(['|', '#', '+', '-'])
|
||||
other_op = oneOf(["|", "#", "+", "-"])
|
||||
|
||||
# named view
|
||||
named_view = oneOf(['front', 'back', 'left', 'right', 'top', 'bottom'])
|
||||
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')
|
||||
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
|
||||
@ -506,33 +527,34 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
|
||||
# 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)
|
||||
"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)
|
||||
"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,
|
||||
'-': lambda v: DirectionSelector(-v),
|
||||
'#': PerpendicularDirSelector,
|
||||
'|': ParallelDirSelector}
|
||||
"+": DirectionSelector,
|
||||
"-": lambda v: DirectionSelector(-v),
|
||||
"#": PerpendicularDirSelector,
|
||||
"|": ParallelDirSelector,
|
||||
}
|
||||
|
||||
self.parseResults = parseResults
|
||||
self.mySelector = self._chooseSelector(parseResults)
|
||||
@ -541,23 +563,25 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Sets up the underlying filters accordingly
|
||||
"""
|
||||
if 'only_dir' in pr:
|
||||
if "only_dir" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return DirectionSelector(vec)
|
||||
|
||||
elif 'type_op' in pr:
|
||||
elif "type_op" in pr:
|
||||
return TypeSelector(pr.cq_type)
|
||||
|
||||
elif 'dir_op' in pr:
|
||||
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)
|
||||
if "index" in pr:
|
||||
return DirectionNthSelector(
|
||||
vec, int("".join(pr.index.asList())), minmax
|
||||
)
|
||||
else:
|
||||
return DirectionMinMaxSelector(vec, minmax)
|
||||
|
||||
elif 'other_op' in pr:
|
||||
elif "other_op" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return self.operator[pr.other_op](vec)
|
||||
|
||||
@ -569,7 +593,7 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Translate parsed vector string into a CQ Vector
|
||||
"""
|
||||
if 'vector_dir' in pr:
|
||||
if "vector_dir" in pr:
|
||||
vec = pr.vector_dir
|
||||
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
||||
else:
|
||||
@ -590,10 +614,10 @@ def _makeExpressionGrammar(atom):
|
||||
"""
|
||||
|
||||
# define operators
|
||||
and_op = Literal('and')
|
||||
or_op = Literal('or')
|
||||
delta_op = oneOf(['exc', 'except'])
|
||||
not_op = Literal('not')
|
||||
and_op = Literal("and")
|
||||
or_op = Literal("or")
|
||||
delta_op = oneOf(["exc", "except"])
|
||||
not_op = Literal("not")
|
||||
|
||||
def atom_callback(res):
|
||||
return _SimpleStringSyntaxSelector(res)
|
||||
@ -622,11 +646,15 @@ def _makeExpressionGrammar(atom):
|
||||
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)])
|
||||
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
|
||||
|
||||
@ -690,8 +718,7 @@ class StringSyntaxSelector(Selector):
|
||||
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)
|
||||
parse_result = _expression_grammar.parseString(selectorString, parseAll=True)
|
||||
self.mySelector = parse_result.asList()[0]
|
||||
|
||||
def filter(self, objectList):
|
||||
|
146
doc/conf.py
146
doc/conf.py
@ -13,71 +13,77 @@
|
||||
|
||||
import sys, os
|
||||
import os.path
|
||||
#print "working path is %s" % os.getcwd()
|
||||
#sys.path.append("../cadquery")
|
||||
|
||||
# print "working path is %s" % os.getcwd()
|
||||
# sys.path.append("../cadquery")
|
||||
import cadquery
|
||||
|
||||
#settings._target = None
|
||||
# settings._target = None
|
||||
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary','cadquery.cq_directive']
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.autosummary",
|
||||
"cadquery.cq_directive",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'CadQuery'
|
||||
copyright = u'Parametric Products Intellectual Holdings LLC, All Rights Reserved'
|
||||
project = u"CadQuery"
|
||||
copyright = u"Parametric Products Intellectual Holdings LLC, All Rights Reserved"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
version = "1.0"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0.0'
|
||||
release = "1.0.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
@ -85,27 +91,27 @@ add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#html_theme = 'timlinux-linfiniti-sphinx'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = 'timlinux-linfiniti-sphinx'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {
|
||||
# html_theme_options = {
|
||||
# "headerfont": "'Open Sans',Arial,sans-serif",
|
||||
# #"bodyfont:": "'Open Sans',Arial,sans-serif",
|
||||
# #"headerbg" : "{image: url('/img/bg/body.jpg');color:#000000;}",
|
||||
@ -115,9 +121,9 @@ html_theme = 'sphinx_rtd_theme'
|
||||
## "headercolor1": "#13171A;",
|
||||
# "headercolor2": "#444;",
|
||||
# "headerlinkcolor" : "#13171A;",
|
||||
#}
|
||||
# }
|
||||
|
||||
#agogo options
|
||||
# agogo options
|
||||
"""
|
||||
bodyfont (CSS font family): Font for normal text.
|
||||
headerfont (CSS font family): Font for headings.
|
||||
@ -133,14 +139,14 @@ html_theme = 'sphinx_rtd_theme'
|
||||
textalign (CSS text-align value): Text alignment for the body, default is justify.
|
||||
"""
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = "CadQuery Documentation"
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
@ -149,36 +155,36 @@ html_logo = "_static/cqlogo.png"
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
@ -187,72 +193,66 @@ html_show_sourcelink = False
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'CadQuerydoc'
|
||||
htmlhelp_basename = "CadQuerydoc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'CadQuery.tex', u'CadQuery Documentation',
|
||||
u'David Cowden', 'manual'),
|
||||
("index", "CadQuery.tex", u"CadQuery Documentation", u"David Cowden", "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'cadquery', u'CadQuery Documentation',
|
||||
[u'David Cowden'], 1)
|
||||
]
|
||||
man_pages = [("index", "cadquery", u"CadQuery Documentation", [u"David Cowden"], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
@ -261,16 +261,22 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'CadQuery', u'CadQuery Documentation',
|
||||
u'David Cowden', 'CadQuery', 'A Fluent CAD api',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
"index",
|
||||
"CadQuery",
|
||||
u"CadQuery Documentation",
|
||||
u"David Cowden",
|
||||
"CadQuery",
|
||||
"A Fluent CAD api",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
@ -9,6 +9,7 @@ dependencies:
|
||||
- pyparsing
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
- black
|
||||
- codecov
|
||||
- pytest
|
||||
- pytest-cov
|
||||
|
@ -1,9 +1,9 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
|
||||
# Create a 3D block based on the dimension variables above.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
|
@ -1,10 +1,10 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
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 block based on the dimensions above and add a 22mm center hole.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -13,8 +13,13 @@ center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
# 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))
|
||||
result = (
|
||||
cq.Workplane("XY")
|
||||
.box(length, height, thickness)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.hole(center_hole_dia)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,15 +1,15 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
width = 60.0 # Width of the block
|
||||
height = 100.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_inset = 12.0 # How far from the edge the cbored holes are set
|
||||
cbore_diameter = 4.4 # Bolt head pocket hole diameter
|
||||
cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
length = 80.0 # Length of the block
|
||||
width = 60.0 # Width of the block
|
||||
height = 100.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_inset = 12.0 # How far from the edge the cbored holes are set
|
||||
cbore_diameter = 4.4 # Bolt head pocket hole diameter
|
||||
cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
|
||||
# Create a 3D block based on the dimensions above and add a 22mm center hold
|
||||
# and 4 counterbored holes for bolts
|
||||
@ -26,12 +26,20 @@ cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
# 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()
|
||||
result = (
|
||||
cq.Workplane("XY")
|
||||
.box(length, height, thickness)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.hole(center_hole_dia)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(length - cbore_inset, height - cbore_inset, forConstruction=True)
|
||||
.vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
|
||||
.edges("|Z").fillet(2.0))
|
||||
.vertices()
|
||||
.cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
|
||||
.edges("|Z")
|
||||
.fillet(2.0)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,10 +1,10 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
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.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -21,9 +21,12 @@ rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
||||
# 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))
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.circle(circle_radius)
|
||||
.rect(rectangle_width, rectangle_length)
|
||||
.extrude(thickness)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
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
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -34,12 +34,16 @@ thickness = 0.25 # Thickness of the plate
|
||||
# 7a. Without the close(), the 2D sketch will be left open and the extrude
|
||||
# operation will provide unpredictable results.
|
||||
# 8. 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))
|
||||
.sagittaArc((-0.5, 1.0), 0.2)
|
||||
.radiusArc((-0.7, -0.2), -1.5)
|
||||
.close().extrude(thickness))
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.lineTo(width, 0)
|
||||
.lineTo(width, 1.0)
|
||||
.threePointArc((1.0, 1.5), (0.0, 1.0))
|
||||
.sagittaArc((-0.5, 1.0), 0.2)
|
||||
.radiusArc((-0.7, -0.2), -1.5)
|
||||
.close()
|
||||
.extrude(thickness)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
circle_radius = 3.0 # The outside radius of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
|
||||
# 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.
|
||||
|
@ -1,9 +1,9 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
plate_radius = 2.0 # The radius of the plate that will be extruded
|
||||
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
|
||||
thickness = 0.125 # The thickness of the plate that will be extruded
|
||||
|
||||
# Make a plate with 4 holes in it at various points in a polar arrangement from
|
||||
# the center of the workplane.
|
||||
|
@ -1,11 +1,11 @@
|
||||
import cadquery as cq
|
||||
|
||||
# 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
|
||||
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
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -30,10 +30,13 @@ polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
||||
# 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())
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(width, height, thickness)
|
||||
.pushPoints([(0, 0.75), (0, -0.75)])
|
||||
.polygon(polygon_sides, polygon_dia)
|
||||
.cutThruAll()
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -6,13 +6,13 @@ import cadquery as cq
|
||||
|
||||
# Define the points that the polyline will be drawn to/thru
|
||||
pts = [
|
||||
(W/2.0, H/2.0),
|
||||
(W/2.0, (H/2.0 - t)),
|
||||
(t/2.0, (H/2.0-t)),
|
||||
(t/2.0, (t - H/2.0)),
|
||||
(W/2.0, (t - H/2.0)),
|
||||
(W/2.0, H/-2.0),
|
||||
(0, H/-2.0)
|
||||
(W / 2.0, H / 2.0),
|
||||
(W / 2.0, (H / 2.0 - t)),
|
||||
(t / 2.0, (H / 2.0 - t)),
|
||||
(t / 2.0, (t - H / 2.0)),
|
||||
(W / 2.0, (t - H / 2.0)),
|
||||
(W / 2.0, H / -2.0),
|
||||
(0, H / -2.0),
|
||||
]
|
||||
|
||||
# We generate half of the I-beam outline and then mirror it to create the full
|
||||
@ -30,10 +30,7 @@ pts = [
|
||||
# 3. Only half of the I-beam profile has been drawn so far. That half is
|
||||
# mirrored around the Y-axis to create the complete I-beam profile.
|
||||
# 4. The I-beam profile is extruded to the final length of the beam.
|
||||
result = (cq.Workplane("front").moveTo(0, H/2.0)
|
||||
.polyline(pts)
|
||||
.mirrorY()
|
||||
.extrude(L))
|
||||
result = cq.Workplane("front").moveTo(0, H / 2.0).polyline(pts).mirrorY().extrude(L)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -13,7 +13,7 @@ sPnts = [
|
||||
(1.5, 1.0),
|
||||
(1.0, 1.25),
|
||||
(0.5, 1.0),
|
||||
(0, 1.0)
|
||||
(0, 1.0),
|
||||
]
|
||||
|
||||
# 2. Generate our plate with the spline feature and make sure it is a
|
||||
|
@ -13,10 +13,16 @@ import cadquery as cq
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
# 7a. Since the workplane is rotated, this results in angled holes in the face.
|
||||
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z")
|
||||
.workplane()
|
||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0))
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25))
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(4.0, 4.0, 0.25)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0))
|
||||
.rect(1.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.hole(0.25)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -12,10 +12,15 @@ import cadquery as cq
|
||||
# other geometry.
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
result = (cq.Workplane("front").box(2, 2, 0.5)
|
||||
.faces(">Z").workplane()
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices()
|
||||
.hole(0.125))
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(2, 2, 0.5)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(1.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.hole(0.125)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -11,10 +11,15 @@ import cadquery as cq
|
||||
# 5. Creates a workplane 3 mm above the face the circle was drawn on.
|
||||
# 6. Draws a 2D circle on the new, offset workplane.
|
||||
# 7. Creates a loft between the circle and the rectangle.
|
||||
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z")
|
||||
.circle(1.5).workplane(offset=3.0)
|
||||
.rect(0.75, 0.5)
|
||||
.loft(combine=True))
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(4.0, 4.0, 0.25)
|
||||
.faces(">Z")
|
||||
.circle(1.5)
|
||||
.workplane(offset=3.0)
|
||||
.rect(0.75, 0.5)
|
||||
.loft(combine=True)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -11,9 +11,15 @@ import cadquery as cq
|
||||
# function.
|
||||
# 5a. When the depth of the counter-sink hole is set to None, the hole will be
|
||||
# cut through.
|
||||
result = (cq.Workplane(cq.Plane.XY()).box(4, 2, 0.5).faces(">Z")
|
||||
.workplane().rect(3.5, 1.5, forConstruction=True)
|
||||
.vertices().cskHole(0.125, 0.25, 82.0, depth=None))
|
||||
result = (
|
||||
cq.Workplane(cq.Plane.XY())
|
||||
.box(4, 2, 0.5)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(3.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.cskHole(0.125, 0.25, 82.0, depth=None)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -9,8 +9,7 @@ import cadquery as cq
|
||||
# that new geometry can be built on.
|
||||
# 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
|
||||
# all the way through the box.
|
||||
c = (cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane()
|
||||
.circle(0.25).cutThruAll())
|
||||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
||||
|
||||
# 5. Selects the face furthest away from the origin in the +Y axis direction.
|
||||
# 6. Creates an offset workplane that is set in the center of the object.
|
||||
|
@ -9,13 +9,13 @@ 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 = cq.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)
|
||||
# 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))
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,11 +1,7 @@
|
||||
import cadquery as cq
|
||||
|
||||
# Points we will use to create spline and polyline paths to sweep over
|
||||
pts = [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(2, 4)
|
||||
]
|
||||
pts = [(0, 1), (1, 2), (2, 4)]
|
||||
|
||||
# Spline path generated from our list of points (tuples)
|
||||
path = cq.Workplane("XZ").spline(pts)
|
||||
|
@ -4,37 +4,80 @@ import cadquery as cq
|
||||
path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0)
|
||||
|
||||
# Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0
|
||||
defaultSweep = (cq.Workplane("YZ").workplane(offset=-10.0).circle(2.0).
|
||||
workplane(offset=10.0).circle(1.0).
|
||||
workplane(offset=10.0).circle(2.0).sweep(path, multisection=True))
|
||||
defaultSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.circle(2.0)
|
||||
.workplane(offset=10.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=10.0)
|
||||
.circle(2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
# We can sweep thrue different shapes
|
||||
recttocircleSweep = (cq.Workplane("YZ").workplane(offset=-10.0).rect(2.0, 2.0).
|
||||
workplane(offset=8.0).circle(1.0).workplane(offset=4.0).circle(1.0).
|
||||
workplane(offset=8.0).rect(2.0, 2.0).sweep(path, multisection=True))
|
||||
recttocircleSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=8.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=4.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=8.0)
|
||||
.rect(2.0, 2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
circletorectSweep = (cq.Workplane("YZ").workplane(offset=-10.0).circle(1.0).
|
||||
workplane(offset=7.0).rect(2.0, 2.0).workplane(offset=6.0).rect(2.0, 2.0).
|
||||
workplane(offset=7.0).circle(1.0).sweep(path, multisection=True))
|
||||
circletorectSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=7.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=6.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=7.0)
|
||||
.circle(1.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
|
||||
# Placement of the Shape is important otherwise could produce unexpected shape
|
||||
specialSweep = (cq.Workplane("YZ").circle(1.0).workplane(offset=10.0).rect(2.0, 2.0).
|
||||
sweep(path, multisection=True))
|
||||
specialSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.circle(1.0)
|
||||
.workplane(offset=10.0)
|
||||
.rect(2.0, 2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
# Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0
|
||||
path = (cq.Workplane("XZ").moveTo(-5, 4).lineTo(0, 4).
|
||||
threePointArc((4, 0), (0, -4)).lineTo(-5, -4))
|
||||
path = (
|
||||
cq.Workplane("XZ")
|
||||
.moveTo(-5, 4)
|
||||
.lineTo(0, 4)
|
||||
.threePointArc((4, 0), (0, -4))
|
||||
.lineTo(-5, -4)
|
||||
)
|
||||
|
||||
# Placement of different shapes should follow the path
|
||||
# cylinder r=1.5 along first line
|
||||
# then sweep allong arc from r=1.5 to r=1.0
|
||||
# then cylinder r=1.0 along last line
|
||||
arcSweep = (cq.Workplane("YZ").workplane(offset=-5).moveTo(0, 4).circle(1.5).
|
||||
workplane(offset=5).circle(1.5).
|
||||
moveTo(0, -8).circle(1.0).
|
||||
workplane(offset=-5).circle(1.0).
|
||||
sweep(path, multisection=True))
|
||||
arcSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-5)
|
||||
.moveTo(0, 4)
|
||||
.circle(1.5)
|
||||
.workplane(offset=5)
|
||||
.circle(1.5)
|
||||
.moveTo(0, -8)
|
||||
.circle(1.0)
|
||||
.workplane(offset=-5)
|
||||
.circle(1.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
|
||||
# Translate the resulting solids so that they do not overlap and display them left to right
|
||||
@ -43,5 +86,3 @@ show_object(circletorectSweep.translate((0, 5, 0)))
|
||||
show_object(recttocircleSweep.translate((0, 10, 0)))
|
||||
show_object(specialSweep.translate((0, 15, 0)))
|
||||
show_object(arcSweep.translate((0, -5, 0)))
|
||||
|
||||
|
||||
|
@ -4,8 +4,8 @@ import cadquery as cq
|
||||
#####
|
||||
# Inputs
|
||||
######
|
||||
lbumps = 1 # number of bumps long
|
||||
wbumps = 1 # number of bumps wide
|
||||
lbumps = 1 # number of bumps long
|
||||
wbumps = 1 # number of bumps wide
|
||||
thin = True # True for thin, False for thick
|
||||
|
||||
#
|
||||
@ -22,8 +22,8 @@ else:
|
||||
|
||||
t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||
postDiam = pitch - t # works out to 6.5
|
||||
total_length = lbumps*pitch - 2.0*clearance
|
||||
total_width = wbumps*pitch - 2.0*clearance
|
||||
total_length = lbumps * pitch - 2.0 * clearance
|
||||
total_width = wbumps * pitch - 2.0 * clearance
|
||||
|
||||
# make the base
|
||||
s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||
@ -32,23 +32,37 @@ s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||
s = s.faces("<Z").shell(-1.0 * t)
|
||||
|
||||
# make the bumps on the top
|
||||
s = (s.faces(">Z").workplane().
|
||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0)
|
||||
.extrude(bumpHeight))
|
||||
s = (
|
||||
s.faces(">Z")
|
||||
.workplane()
|
||||
.rarray(pitch, pitch, lbumps, wbumps, True)
|
||||
.circle(bumpDiam / 2.0)
|
||||
.extrude(bumpHeight)
|
||||
)
|
||||
|
||||
# add posts on the bottom. posts are different diameter depending on geometry
|
||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||
tmp = s.faces("<Z").workplane(invert=True)
|
||||
|
||||
if lbumps > 1 and wbumps > 1:
|
||||
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True).
|
||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t))
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True)
|
||||
.circle(postDiam / 2.0)
|
||||
.circle(bumpDiam / 2.0)
|
||||
.extrude(height - t)
|
||||
)
|
||||
elif lbumps > 1:
|
||||
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True).
|
||||
circle(t).extrude(height - t))
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True)
|
||||
.circle(t)
|
||||
.extrude(height - t)
|
||||
)
|
||||
elif wbumps > 1:
|
||||
tmp = (tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True).
|
||||
circle(t).extrude(height - t))
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True)
|
||||
.circle(t)
|
||||
.extrude(height - t)
|
||||
)
|
||||
else:
|
||||
tmp = s
|
||||
|
||||
|
65
setup.py
65
setup.py
@ -15,43 +15,48 @@ import os
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
#if we are building in travis, use the build number as the sub-minor version
|
||||
version = '0.5-SNAPSHOT'
|
||||
if 'TRAVIS_TAG' in os.environ.keys():
|
||||
version= os.environ['TRAVIS_TAG']
|
||||
# if we are building in travis, use the build number as the sub-minor version
|
||||
version = "0.5-SNAPSHOT"
|
||||
if "TRAVIS_TAG" in os.environ.keys():
|
||||
version = os.environ["TRAVIS_TAG"]
|
||||
|
||||
|
||||
setup(
|
||||
name='cadquery',
|
||||
name="cadquery",
|
||||
version=version,
|
||||
url='https://github.com/dcowden/cadquery',
|
||||
license='Apache Public License 2.0',
|
||||
author='David Cowden',
|
||||
author_email='dave.cowden@gmail.com',
|
||||
description='CadQuery is a parametric scripting language for creating and traversing CAD models',
|
||||
long_description=open('README.md').read(),
|
||||
packages=['cadquery','cadquery.contrib','cadquery.occ_impl','cadquery.plugins','tests'],
|
||||
url="https://github.com/dcowden/cadquery",
|
||||
license="Apache Public License 2.0",
|
||||
author="David Cowden",
|
||||
author_email="dave.cowden@gmail.com",
|
||||
description="CadQuery is a parametric scripting language for creating and traversing CAD models",
|
||||
long_description=open("README.md").read(),
|
||||
packages=[
|
||||
"cadquery",
|
||||
"cadquery.contrib",
|
||||
"cadquery.occ_impl",
|
||||
"cadquery.plugins",
|
||||
"tests",
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
test_suite='tests',
|
||||
|
||||
platforms="any",
|
||||
test_suite="tests",
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
#'Development Status :: 6 - Mature',
|
||||
#'Development Status :: 7 - Inactive',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Science/Research',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: MacOS',
|
||||
'Operating System :: Unix',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Scientific/Engineering'
|
||||
]
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Internet",
|
||||
"Topic :: Scientific/Engineering",
|
||||
],
|
||||
)
|
||||
|
@ -6,21 +6,23 @@ import os
|
||||
|
||||
|
||||
def readFileAsString(fileName):
|
||||
f = open(fileName, 'r')
|
||||
f = open(fileName, "r")
|
||||
s = f.read()
|
||||
f.close()
|
||||
return s
|
||||
|
||||
|
||||
def writeStringToFile(strToWrite, fileName):
|
||||
f = open(fileName, 'w')
|
||||
f = open(fileName, "w")
|
||||
f.write(strToWrite)
|
||||
f.close()
|
||||
|
||||
|
||||
def makeUnitSquareWire():
|
||||
V = Vector
|
||||
return Wire.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)])
|
||||
return Wire.makePolygon(
|
||||
[V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)]
|
||||
)
|
||||
|
||||
|
||||
def makeUnitCube():
|
||||
@ -38,26 +40,24 @@ def toTuple(v):
|
||||
elif type(v) == Vector:
|
||||
return v.toTuple()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"dont know how to convert type %s to tuple" % str(type(v)))
|
||||
raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)))
|
||||
|
||||
|
||||
class BaseTest(unittest.TestCase):
|
||||
|
||||
def assertTupleAlmostEquals(self, expected, actual, places):
|
||||
for i, j in zip(actual, expected):
|
||||
self.assertAlmostEqual(i, j, places)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'TestCadObjects',
|
||||
'TestCadQuery',
|
||||
'TestCQGI',
|
||||
'TestCQSelectors',
|
||||
'TestCQSelectors',
|
||||
'TestExporters',
|
||||
'TestImporters',
|
||||
'TestJupyter',
|
||||
'TestWorkplanes',
|
||||
'TestAssembleEdges',
|
||||
"TestCadObjects",
|
||||
"TestCadQuery",
|
||||
"TestCQGI",
|
||||
"TestCQSelectors",
|
||||
"TestCQSelectors",
|
||||
"TestExporters",
|
||||
"TestImporters",
|
||||
"TestJupyter",
|
||||
"TestWorkplanes",
|
||||
"TestAssembleEdges",
|
||||
]
|
||||
|
@ -3,9 +3,11 @@ import sys
|
||||
import unittest
|
||||
from tests import BaseTest
|
||||
from OCC.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_DZ, gp_XYZ
|
||||
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
||||
BRepBuilderAPI_MakeEdge,
|
||||
BRepBuilderAPI_MakeFace)
|
||||
from OCC.BRepBuilderAPI import (
|
||||
BRepBuilderAPI_MakeVertex,
|
||||
BRepBuilderAPI_MakeEdge,
|
||||
BRepBuilderAPI_MakeFace,
|
||||
)
|
||||
|
||||
from OCC.GC import GC_MakeCircle
|
||||
|
||||
@ -13,26 +15,24 @@ from cadquery import *
|
||||
|
||||
|
||||
class TestCadObjects(BaseTest):
|
||||
|
||||
def _make_circle(self):
|
||||
|
||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()),
|
||||
2.)
|
||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 2.0)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||
|
||||
def testVectorConstructors(self):
|
||||
v1 = Vector(1, 2, 3)
|
||||
v2 = Vector((1, 2, 3))
|
||||
v3 = Vector(gp_Vec(1, 2, 3))
|
||||
v4 = Vector([1,2,3])
|
||||
v5 = Vector(gp_XYZ(1,2,3))
|
||||
v4 = Vector([1, 2, 3])
|
||||
v5 = Vector(gp_XYZ(1, 2, 3))
|
||||
|
||||
for v in [v1, v2, v3, v4, v5]:
|
||||
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
|
||||
|
||||
v6 = Vector((1,2))
|
||||
v7 = Vector([1,2])
|
||||
v8 = Vector(1,2)
|
||||
v6 = Vector((1, 2))
|
||||
v7 = Vector([1, 2])
|
||||
v8 = Vector(1, 2)
|
||||
|
||||
for v in [v6, v7, v8]:
|
||||
self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)
|
||||
@ -40,9 +40,9 @@ class TestCadObjects(BaseTest):
|
||||
v9 = Vector()
|
||||
self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)
|
||||
|
||||
v9.x = 1.
|
||||
v9.y = 2.
|
||||
v9.z = 3.
|
||||
v9.x = 1.0
|
||||
v9.y = 2.0
|
||||
v9.z = 3.0
|
||||
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
||||
|
||||
def testVertex(self):
|
||||
@ -70,20 +70,22 @@ 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)
|
||||
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((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
|
||||
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
|
||||
(-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)
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
||||
|
||||
def testCenterOfBoundBox(self):
|
||||
pass
|
||||
@ -109,12 +111,15 @@ class TestCadObjects(BaseTest):
|
||||
Workplane.cyl = cylinders
|
||||
|
||||
# Now test. here we want weird workplane to see if the objects are transformed right
|
||||
s = Workplane("XY").rect(
|
||||
2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
|
||||
s = (
|
||||
Workplane("XY")
|
||||
.rect(2.0, 3.0, forConstruction=True)
|
||||
.vertices()
|
||||
.cyl(0.25, 0.5)
|
||||
)
|
||||
|
||||
self.assertEqual(4, len(s.val().Solids()))
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||
|
||||
def testDot(self):
|
||||
v1 = Vector(2, 2, 2)
|
||||
@ -142,7 +147,7 @@ class TestCadObjects(BaseTest):
|
||||
|
||||
self.assertEqual(0, abs(Vector(0, 0, 0)))
|
||||
self.assertEqual(1, abs(Vector(1, 0, 0)))
|
||||
self.assertEqual((1+4+9)**0.5, abs(Vector(1, 2, 3)))
|
||||
self.assertEqual((1 + 4 + 9) ** 0.5, abs(Vector(1, 2, 3)))
|
||||
|
||||
def testVectorEquals(self):
|
||||
a = Vector(1, 2, 3)
|
||||
@ -163,25 +168,31 @@ class TestCadObjects(BaseTest):
|
||||
|
||||
# test passing Plane object
|
||||
point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
|
||||
self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7),
|
||||
decimal_places)
|
||||
self.assertTupleAlmostEquals(
|
||||
point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
|
||||
)
|
||||
|
||||
def testMatrixCreationAndAccess(self):
|
||||
def matrix_vals(m):
|
||||
return [[m[r,c] for c in range(4)] for r in range(4)]
|
||||
return [[m[r, c] for c in range(4)] for r in range(4)]
|
||||
|
||||
# default constructor creates a 4x4 identity matrix
|
||||
m = Matrix()
|
||||
identity = [[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.]]
|
||||
identity = [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
self.assertEqual(identity, matrix_vals(m))
|
||||
|
||||
vals4x4 = [[1., 0., 0., 1.],
|
||||
[0., 1., 0., 2.],
|
||||
[0., 0., 1., 3.],
|
||||
[0., 0., 0., 1.]]
|
||||
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
||||
vals4x4 = [
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 2.0],
|
||||
[0.0, 0.0, 1.0, 3.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
||||
|
||||
# test constructor with 16-value input
|
||||
m = Matrix(vals4x4)
|
||||
@ -197,10 +208,12 @@ class TestCadObjects(BaseTest):
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
|
||||
# Test 16-value input with invalid values for the last 4
|
||||
invalid = [[1., 0., 0., 1.],
|
||||
[0., 1., 0., 2.],
|
||||
[0., 0., 1., 3.],
|
||||
[1., 2., 3., 4.]]
|
||||
invalid = [
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 2.0],
|
||||
[0.0, 0.0, 1.0, 3.0],
|
||||
[1.0, 2.0, 3.0, 4.0],
|
||||
]
|
||||
with self.assertRaises(ValueError):
|
||||
Matrix(invalid)
|
||||
|
||||
@ -208,11 +221,11 @@ class TestCadObjects(BaseTest):
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([1,2,3])
|
||||
Matrix([1, 2, 3])
|
||||
|
||||
# Invalid sub-type
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], 'abc', [1, 2, 3, 4]])
|
||||
Matrix([[1, 2, 3, 4], "abc", [1, 2, 3, 4]])
|
||||
|
||||
# test out-of-bounds access
|
||||
m = Matrix()
|
||||
@ -221,8 +234,7 @@ class TestCadObjects(BaseTest):
|
||||
with self.assertRaises(IndexError):
|
||||
m[4, 0]
|
||||
with self.assertRaises(IndexError):
|
||||
m['ab']
|
||||
|
||||
m["ab"]
|
||||
|
||||
def testTranslate(self):
|
||||
e = Edge.makeCircle(2, (1, 2, 3))
|
||||
@ -231,54 +243,53 @@ class TestCadObjects(BaseTest):
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
||||
|
||||
def testVertices(self):
|
||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0),
|
||||
gp_Pnt(1, 1, 0)).Edge())
|
||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
|
||||
self.assertEqual(2, len(e.Vertices()))
|
||||
|
||||
def testPlaneEqual(self):
|
||||
# default orientation
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved origin
|
||||
self.assertEqual(
|
||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1))
|
||||
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved x-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved z-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
)
|
||||
|
||||
def testPlaneNotEqual(self):
|
||||
# type difference
|
||||
for value in [None, 0, 1, 'abc']:
|
||||
for value in [None, 0, 1, "abc"]:
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
value
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)), value
|
||||
)
|
||||
# origin difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,1), xDir=(1,0,0), normal=(0,0,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# x-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# z-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,8 +42,9 @@ class TestCQGI(BaseTest):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
metadata = model.metadata
|
||||
|
||||
self.assertEqual(set(metadata.parameters.keys()), {
|
||||
'height', 'width', 'a', 'b', 'foo'})
|
||||
self.assertEqual(
|
||||
set(metadata.parameters.keys()), {"height", "width", "a", "b", "foo"}
|
||||
)
|
||||
|
||||
def test_build_with_debug(self):
|
||||
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
||||
@ -51,7 +52,7 @@ class TestCQGI(BaseTest):
|
||||
debugItems = result.debugObjects
|
||||
self.assertTrue(len(debugItems) == 2)
|
||||
self.assertTrue(debugItems[0].shape == "bar")
|
||||
self.assertTrue(debugItems[0].options == {"color": 'yellow'})
|
||||
self.assertTrue(debugItems[0].options == {"color": "yellow"})
|
||||
self.assertTrue(debugItems[1].shape == 2.0)
|
||||
self.assertTrue(debugItems[1].options == {})
|
||||
|
||||
@ -65,7 +66,7 @@ class TestCQGI(BaseTest):
|
||||
|
||||
def test_build_with_different_params(self):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
result = model.build({'height': 3.0})
|
||||
result = model.build({"height": 3.0})
|
||||
self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0")
|
||||
|
||||
def test_describe_parameters(self):
|
||||
@ -76,9 +77,9 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
model = cqgi.CQModel(script)
|
||||
a_param = model.metadata.parameters['a']
|
||||
a_param = model.metadata.parameters["a"]
|
||||
self.assertTrue(a_param.default_value == 2.0)
|
||||
self.assertTrue(a_param.desc == 'FirstLetter')
|
||||
self.assertTrue(a_param.desc == "FirstLetter")
|
||||
self.assertTrue(a_param.varType == cqgi.NumberParameterType)
|
||||
|
||||
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
||||
@ -89,8 +90,8 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
model = cqgi.CQModel(script)
|
||||
a_param = model.metadata.parameters['a']
|
||||
self.assertTrue(a_param.name == 'a')
|
||||
a_param = model.metadata.parameters["a"]
|
||||
self.assertTrue(a_param.name == "a")
|
||||
|
||||
def test_build_with_exception(self):
|
||||
badscript = textwrap.dedent(
|
||||
@ -115,7 +116,7 @@ class TestCQGI(BaseTest):
|
||||
with self.assertRaises(Exception) as context:
|
||||
model = cqgi.CQModel(badscript)
|
||||
|
||||
self.assertTrue('invalid syntax' in context.exception.args)
|
||||
self.assertTrue("invalid syntax" in context.exception.args)
|
||||
|
||||
def test_that_two_results_are_returned(self):
|
||||
script = textwrap.dedent(
|
||||
@ -140,7 +141,7 @@ class TestCQGI(BaseTest):
|
||||
show_object(h)
|
||||
"""
|
||||
)
|
||||
result = cqgi.parse(script).build({'h': 33.33})
|
||||
result = cqgi.parse(script).build({"h": 33.33})
|
||||
self.assertEqual(result.results[0].shape, "33.33")
|
||||
|
||||
def test_that_assigning_string_to_number_fails(self):
|
||||
@ -150,9 +151,8 @@ class TestCQGI(BaseTest):
|
||||
show_object(h)
|
||||
"""
|
||||
)
|
||||
result = cqgi.parse(script).build({'h': "a string"})
|
||||
self.assertTrue(isinstance(result.exception,
|
||||
cqgi.InvalidParameterError))
|
||||
result = cqgi.parse(script).build({"h": "a string"})
|
||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||
|
||||
def test_that_assigning_unknown_var_fails(self):
|
||||
script = textwrap.dedent(
|
||||
@ -162,9 +162,8 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
|
||||
result = cqgi.parse(script).build({'w': "var is not there"})
|
||||
self.assertTrue(isinstance(result.exception,
|
||||
cqgi.InvalidParameterError))
|
||||
result = cqgi.parse(script).build({"w": "var is not there"})
|
||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||
|
||||
def test_that_cq_objects_are_visible(self):
|
||||
script = textwrap.dedent(
|
||||
@ -198,10 +197,10 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
|
||||
result = cqgi.parse(script).build({'h': False})
|
||||
result = cqgi.parse(script).build({"h": False})
|
||||
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.first_result.shape, '*False*')
|
||||
self.assertEqual(result.first_result.shape, "*False*")
|
||||
|
||||
def test_that_only_top_level_vars_are_detected(self):
|
||||
script = textwrap.dedent(
|
||||
|
@ -12,7 +12,6 @@ from tests import BaseTest
|
||||
|
||||
|
||||
class TestExporters(BaseTest):
|
||||
|
||||
def _exportBox(self, eType, stringsToFind):
|
||||
"""
|
||||
Exports a test object, and then looks for
|
||||
@ -21,31 +20,32 @@ class TestExporters(BaseTest):
|
||||
"""
|
||||
p = Workplane("XY").box(1, 2, 3)
|
||||
|
||||
if eType == exporters.ExportTypes.AMF:
|
||||
if eType == exporters.ExportTypes.AMF:
|
||||
s = io.BytesIO()
|
||||
else:
|
||||
s = io.StringIO()
|
||||
|
||||
exporters.exportShape(p, eType, s, 0.1)
|
||||
|
||||
result = '{}'.format(s.getvalue())
|
||||
result = "{}".format(s.getvalue())
|
||||
|
||||
for q in stringsToFind:
|
||||
self.assertTrue(result.find(q) > -1)
|
||||
return result
|
||||
|
||||
def testSTL(self):
|
||||
self._exportBox(exporters.ExportTypes.STL, ['facet normal'])
|
||||
self._exportBox(exporters.ExportTypes.STL, ["facet normal"])
|
||||
|
||||
def testSVG(self):
|
||||
self._exportBox(exporters.ExportTypes.SVG, ['<svg', '<g transform'])
|
||||
self._exportBox(exporters.ExportTypes.SVG, ["<svg", "<g transform"])
|
||||
|
||||
def testAMF(self):
|
||||
self._exportBox(exporters.ExportTypes.AMF, ['<amf units', '</object>'])
|
||||
self._exportBox(exporters.ExportTypes.AMF, ["<amf units", "</object>"])
|
||||
|
||||
def testSTEP(self):
|
||||
self._exportBox(exporters.ExportTypes.STEP, ['FILE_SCHEMA'])
|
||||
self._exportBox(exporters.ExportTypes.STEP, ["FILE_SCHEMA"])
|
||||
|
||||
def testTJS(self):
|
||||
self._exportBox(exporters.ExportTypes.TJS, [
|
||||
'vertices', 'formatVersion', 'faces'])
|
||||
self._exportBox(
|
||||
exporters.ExportTypes.TJS, ["vertices", "formatVersion", "faces"]
|
||||
)
|
||||
|
@ -36,12 +36,18 @@ class TestImporters(BaseTest):
|
||||
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
||||
|
||||
# Check the number of faces and vertices per face to make sure we have a box shape
|
||||
self.assertTrue(importedShape.faces("+X").size() ==
|
||||
1 and importedShape.faces("+X").vertices().size() == 4)
|
||||
self.assertTrue(importedShape.faces("+Y").size() ==
|
||||
1 and importedShape.faces("+Y").vertices().size() == 4)
|
||||
self.assertTrue(importedShape.faces("+Z").size() ==
|
||||
1 and importedShape.faces("+Z").vertices().size() == 4)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+X").size() == 1
|
||||
and importedShape.faces("+X").vertices().size() == 4
|
||||
)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+Y").size() == 1
|
||||
and importedShape.faces("+Y").vertices().size() == 4
|
||||
)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+Z").size() == 1
|
||||
and importedShape.faces("+Z").vertices().size() == 4
|
||||
)
|
||||
|
||||
def testSTEP(self):
|
||||
"""
|
||||
@ -55,7 +61,8 @@ class TestImporters(BaseTest):
|
||||
not segfault.
|
||||
"""
|
||||
tmpfile = OUTDIR + "/badSTEP.step"
|
||||
with open(tmpfile, 'w') as f: f.write("invalid STEP file")
|
||||
with open(tmpfile, "w") as f:
|
||||
f.write("invalid STEP file")
|
||||
with self.assertRaises(ValueError):
|
||||
importers.importShape(importers.ImportTypes.STEP, tmpfile)
|
||||
|
||||
@ -69,6 +76,8 @@ class TestImporters(BaseTest):
|
||||
objs = importers.importShape(importers.ImportTypes.STEP, filename)
|
||||
self.assertEqual(2, len(objs.all()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
unittest.main()
|
||||
|
@ -2,9 +2,10 @@ from tests import BaseTest
|
||||
|
||||
import cadquery
|
||||
|
||||
|
||||
class TestJupyter(BaseTest):
|
||||
def test_repr_html(self):
|
||||
cube = cadquery.Workplane('XY').box(1, 1, 1)
|
||||
cube = cadquery.Workplane("XY").box(1, 1, 1)
|
||||
shape = cube.val()
|
||||
self.assertIsInstance(shape, cadquery.occ_impl.shapes.Solid)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
__author__ = 'dcowden'
|
||||
__author__ = "dcowden"
|
||||
|
||||
"""
|
||||
Tests for CadQuery Selectors
|
||||
@ -20,22 +20,19 @@ from cadquery import selectors
|
||||
|
||||
|
||||
class TestCQSelectors(BaseTest):
|
||||
|
||||
def testWorkplaneCenter(self):
|
||||
"Test Moving workplane center"
|
||||
s = Workplane(Plane.XY())
|
||||
|
||||
# current point and world point should be equal
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
|
||||
# move origin and confirm center moves
|
||||
s.center(-2.0, -2.0)
|
||||
|
||||
# current point should be 0,0, but
|
||||
|
||||
self.assertTupleAlmostEquals(
|
||||
(-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
|
||||
def testVertices(self):
|
||||
t = makeUnitSquareWire() # square box
|
||||
@ -43,8 +40,7 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
self.assertEqual(4, c.vertices().size())
|
||||
self.assertEqual(4, c.edges().size())
|
||||
self.assertEqual(0, c.vertices().edges().size()
|
||||
) # no edges on any vertices
|
||||
self.assertEqual(0, c.vertices().edges().size()) # no edges on any vertices
|
||||
# but selecting all edges still yields all vertices
|
||||
self.assertEqual(4, c.edges().vertices().size())
|
||||
self.assertEqual(1, c.wires().size()) # just one wire
|
||||
@ -71,8 +67,7 @@ class TestCQSelectors(BaseTest):
|
||||
def testFirst(self):
|
||||
c = CQ(makeUnitCube())
|
||||
self.assertEqual(type(c.vertices().first().val()), Vertex)
|
||||
self.assertEqual(
|
||||
type(c.vertices().first().first().first().val()), Vertex)
|
||||
self.assertEqual(type(c.vertices().first().first().first().val()), Vertex)
|
||||
|
||||
def testCompounds(self):
|
||||
c = CQ(makeUnitSquareWire())
|
||||
@ -99,11 +94,11 @@ class TestCQSelectors(BaseTest):
|
||||
def testFaceTypesFilter(self):
|
||||
"Filters by face type"
|
||||
c = CQ(makeUnitCube())
|
||||
self.assertEqual(c.faces().size(), c.faces('%PLANE').size())
|
||||
self.assertEqual(c.faces().size(), c.faces('%plane').size())
|
||||
self.assertEqual(0, c.faces('%sphere').size())
|
||||
self.assertEqual(0, c.faces('%cone').size())
|
||||
self.assertEqual(0, c.faces('%SPHERE').size())
|
||||
self.assertEqual(c.faces().size(), c.faces("%PLANE").size())
|
||||
self.assertEqual(c.faces().size(), c.faces("%plane").size())
|
||||
self.assertEqual(0, c.faces("%sphere").size())
|
||||
self.assertEqual(0, c.faces("%cone").size())
|
||||
self.assertEqual(0, c.faces("%SPHERE").size())
|
||||
|
||||
def testPerpendicularDirFilter(self):
|
||||
c = CQ(makeUnitCube())
|
||||
@ -131,10 +126,12 @@ class TestCQSelectors(BaseTest):
|
||||
# faces parallel to Z axis
|
||||
self.assertEqual(2, c.faces("|Z").size())
|
||||
# TODO: provide short names for ParallelDirSelector
|
||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||
Vector((0, 0, 1)))).size()) # same thing as above
|
||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||
Vector((0, 0, -1)))).size()) # same thing as above
|
||||
self.assertEqual(
|
||||
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, 1)))).size()
|
||||
) # same thing as above
|
||||
self.assertEqual(
|
||||
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, -1)))).size()
|
||||
) # same thing as above
|
||||
|
||||
# just for fun, vertices on faces parallel to z
|
||||
self.assertEqual(8, c.faces("|Z").vertices().size())
|
||||
@ -178,97 +175,96 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertEqual(4, len(el))
|
||||
|
||||
def testNthDistance(self):
|
||||
c = Workplane('XY').pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||
c = Workplane("XY").pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||
|
||||
# 2nd face
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), 1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||
|
||||
# 2nd face with inversed selection vector
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(-1, 0, 0), 1)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(-1, 0, 0), 1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# 2nd last face
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(1, 0, 0), -2)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -2)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# Last face
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(1, 0, 0), -1)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||
|
||||
# check if the selected face if normal to the specified Vector
|
||||
self.assertAlmostEqual(
|
||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
|
||||
# repeat the test using string based selector
|
||||
|
||||
# 2nd face
|
||||
val = c.faces('>(1,0,0)[1]').val()
|
||||
val = c.faces(">(1,0,0)[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||
val = c.faces('>X[1]').val()
|
||||
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()
|
||||
val = c.faces(">(-1,0,0)[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
val = c.faces('<X[1]').val()
|
||||
val = c.faces("<X[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# 2nd last face
|
||||
val = c.faces('>X[-2]').val()
|
||||
val = c.faces(">X[-2]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# Last face
|
||||
val = c.faces('>X[-1]').val()
|
||||
val = c.faces(">X[-1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||
|
||||
# check if the selected face if normal to the specified Vector
|
||||
self.assertAlmostEqual(
|
||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
|
||||
# test selection of multiple faces with the same distance
|
||||
c = Workplane('XY')\
|
||||
.box(1, 4, 1, centered=(False, True, False)).faces('<Z')\
|
||||
.box(2, 2, 2, centered=(True, True, False)).faces('>Z')\
|
||||
c = (
|
||||
Workplane("XY")
|
||||
.box(1, 4, 1, centered=(False, True, False))
|
||||
.faces("<Z")
|
||||
.box(2, 2, 2, centered=(True, True, False))
|
||||
.faces(">Z")
|
||||
.box(1, 1, 1, centered=(True, True, False))
|
||||
)
|
||||
|
||||
# select 2nd from the bottom (NB python indexing is 0-based)
|
||||
vals = c.faces('>Z[1]').vals()
|
||||
vals = c.faces(">Z[1]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('>Z[1]').val()
|
||||
val = c.faces(">Z[1]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# do the same but by selecting 3rd from the top
|
||||
vals = c.faces('<Z[2]').vals()
|
||||
vals = c.faces("<Z[2]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('<Z[2]').val()
|
||||
val = c.faces("<Z[2]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# do the same but by selecting 2nd last from the bottom
|
||||
vals = c.faces('<Z[-2]').vals()
|
||||
vals = c.faces("<Z[-2]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('<Z[-2]').val()
|
||||
val = c.faces("<Z[-2]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# verify that <Z[-1] is equivalent to <Z
|
||||
val1 = c.faces('<Z[-1]').val()
|
||||
val2 = c.faces('<Z').val()
|
||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||
val2.Center().toTuple(),
|
||||
3)
|
||||
val1 = c.faces("<Z[-1]").val()
|
||||
val2 = c.faces("<Z").val()
|
||||
self.assertTupleAlmostEquals(
|
||||
val1.Center().toTuple(), val2.Center().toTuple(), 3
|
||||
)
|
||||
|
||||
# verify that >Z[-1] is equivalent to >Z
|
||||
val1 = c.faces('>Z[-1]').val()
|
||||
val2 = c.faces('>Z').val()
|
||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||
val2.Center().toTuple(),
|
||||
3)
|
||||
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())
|
||||
@ -302,7 +298,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)),
|
||||
((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)),
|
||||
((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)),
|
||||
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0))
|
||||
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0)),
|
||||
]
|
||||
|
||||
for d in test_data_vertices:
|
||||
@ -318,11 +314,13 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
||||
|
||||
# test multiple vertices selection
|
||||
vl = c.vertices(selectors.BoxSelector(
|
||||
(-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))).vals()
|
||||
vl = c.vertices(
|
||||
selectors.BoxSelector((-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))
|
||||
).vals()
|
||||
self.assertEqual(2, len(vl))
|
||||
vl = c.vertices(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))).vals()
|
||||
vl = c.vertices(
|
||||
selectors.BoxSelector((-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))
|
||||
).vals()
|
||||
self.assertEqual(4, len(vl))
|
||||
|
||||
# test edge selection
|
||||
@ -331,7 +329,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)),
|
||||
((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)),
|
||||
((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)),
|
||||
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0))
|
||||
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0)),
|
||||
]
|
||||
|
||||
for d in test_data_edges:
|
||||
@ -347,11 +345,9 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
||||
|
||||
# test multiple edge selection
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test face selection
|
||||
@ -360,7 +356,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)),
|
||||
((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)),
|
||||
((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)),
|
||||
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0))
|
||||
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0)),
|
||||
]
|
||||
|
||||
for d in test_data_faces:
|
||||
@ -376,22 +372,23 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
||||
|
||||
# test multiple face selection
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||
self.assertEqual(2, len(fl))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||
self.assertEqual(3, len(fl))
|
||||
|
||||
# test boundingbox option
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
|
||||
el = c.edges(
|
||||
selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)
|
||||
).vals()
|
||||
self.assertEqual(1, len(el))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
|
||||
fl = c.faces(
|
||||
selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)
|
||||
).vals()
|
||||
self.assertEqual(0, len(fl))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
|
||||
fl = c.faces(
|
||||
selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)
|
||||
).vals()
|
||||
self.assertEqual(1, len(fl))
|
||||
|
||||
def testAndSelector(self):
|
||||
@ -400,12 +397,13 @@ class TestCQSelectors(BaseTest):
|
||||
S = selectors.StringSyntaxSelector
|
||||
BS = selectors.BoxSelector
|
||||
|
||||
el = c.edges(selectors.AndSelector(
|
||||
S('|X'), BS((-2, -2, 0.1), (2, 2, 2)))).vals()
|
||||
el = c.edges(
|
||||
selectors.AndSelector(S("|X"), BS((-2, -2, 0.1), (2, 2, 2)))
|
||||
).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
|
||||
# test 'and' (intersection) operator
|
||||
el = c.edges(S('|X') & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||
el = c.edges(S("|X") & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
@ -455,27 +453,27 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
S = selectors.StringSyntaxSelector
|
||||
|
||||
fl = c.faces(selectors.InverseSelector(S('>Z'))).vals()
|
||||
fl = c.faces(selectors.InverseSelector(S(">Z"))).vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges(selectors.InverseSelector(S('>X'))).vals()
|
||||
el = c.faces(">Z").edges(selectors.InverseSelector(S(">X"))).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test invert operator
|
||||
fl = c.faces(-S('>Z')).vals()
|
||||
fl = c.faces(-S(">Z")).vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges(-S('>X')).vals()
|
||||
el = c.faces(">Z").edges(-S(">X")).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
fl = c.faces('not >Z').vals()
|
||||
fl = c.faces("not >Z").vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges('not >X').vals()
|
||||
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()
|
||||
v = c.vertices("(>X and >Y) or (<X and <Y)").vals()
|
||||
self.assertEqual(4, len(v))
|
||||
|
||||
def testFaceCount(self):
|
||||
@ -503,25 +501,27 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
gram = selectors._expression_grammar
|
||||
|
||||
expressions = ['+X ',
|
||||
'-Y',
|
||||
'|(1,0,0)',
|
||||
'#(1.,1.4114,-0.532)',
|
||||
'%Plane',
|
||||
'>XZ',
|
||||
'<Z[-2]',
|
||||
'>(1,4,55.)[20]',
|
||||
'|XY',
|
||||
'<YZ[0]',
|
||||
'front',
|
||||
'back',
|
||||
'left',
|
||||
'right',
|
||||
'top',
|
||||
'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 )']
|
||||
expressions = [
|
||||
"+X ",
|
||||
"-Y",
|
||||
"|(1,0,0)",
|
||||
"#(1.,1.4114,-0.532)",
|
||||
"%Plane",
|
||||
">XZ",
|
||||
"<Z[-2]",
|
||||
">(1,4,55.)[20]",
|
||||
"|XY",
|
||||
"<YZ[0]",
|
||||
"front",
|
||||
"back",
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"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)
|
||||
|
@ -16,65 +16,70 @@ zInvAxis_ = Vector(0, 0, -1)
|
||||
|
||||
|
||||
class TestWorkplanes(BaseTest):
|
||||
|
||||
def testYZPlaneOrigins(self):
|
||||
# xy plane-- with origin at x=0.25
|
||||
base = Vector(0.25, 0, 0)
|
||||
p = Plane(base, Vector(0, 1, 0), Vector(1, 0, 0))
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXYPlaneOrigins(self):
|
||||
base = Vector(0, 0, 0.25)
|
||||
p = Plane(base, Vector(1, 0, 0), Vector(0, 0, 1))
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXZPlaneOrigins(self):
|
||||
base = Vector(0, 0.25, 0)
|
||||
p = Plane(base, Vector(0, 0, 1), Vector(0, 1, 0))
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
def testPlaneBasics(self):
|
||||
p = Plane.XY()
|
||||
# local to world
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2)
|
||||
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2
|
||||
)
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2)
|
||||
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.YZ()
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.XZ()
|
||||
r = p.toWorldCoords((1, 1)).toTuple()
|
||||
@ -82,62 +87,68 @@ class TestWorkplanes(BaseTest):
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testOffsetPlanes(self):
|
||||
"Tests that a plane offset from the origin works ok too"
|
||||
p = Plane.XY(origin=(10.0, 10.0, 0))
|
||||
|
||||
self.assertTupleAlmostEquals(
|
||||
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((2.0, 2.0), p.toLocalCoords(
|
||||
Vector(12.0, 12.0, 0)).toTuple(), 2)
|
||||
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(2.0, 2.0), p.toLocalCoords(Vector(12.0, 12.0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
# TODO test these offsets in the other dimensions too
|
||||
p = Plane.YZ(origin=(0, 2, 2))
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((10, 10.0, 0.0), p.toLocalCoords(
|
||||
Vector(0.0, 12.0, 12.0)).toTuple(), 2)
|
||||
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10, 10.0, 0.0), p.toLocalCoords(Vector(0.0, 12.0, 12.0)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.XZ(origin=(2, 0, 2))
|
||||
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
||||
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
||||
self.assertTupleAlmostEquals((10.0, 10.0), p.toLocalCoords(
|
||||
Vector(12.0, 0.0, 12.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10.0, 10.0), p.toLocalCoords(Vector(12.0, 0.0, 12.0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXYPlaneBasics(self):
|
||||
p = Plane.named('XY')
|
||||
p = Plane.named("XY")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
|
||||
def testYZPlaneBasics(self):
|
||||
p = Plane.named('YZ')
|
||||
p = Plane.named("YZ")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
|
||||
def testZXPlaneBasics(self):
|
||||
p = Plane.named('ZX')
|
||||
p = Plane.named("ZX")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
|
||||
def testXZPlaneBasics(self):
|
||||
p = Plane.named('XZ')
|
||||
p = Plane.named("XZ")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
|
||||
def testYXPlaneBasics(self):
|
||||
p = Plane.named('YX')
|
||||
p = Plane.named("YX")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
|
||||
def testZYPlaneBasics(self):
|
||||
p = Plane.named('ZY')
|
||||
p = Plane.named("ZY")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
|
Reference in New Issue
Block a user