From 7983e6d0d52c0a73930f625e7669f12416960b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20S=C3=A1nchez=20de=20Le=C3=B3n=20Peque?= Date: Sun, 22 Dec 2019 20:16:00 +0100 Subject: [PATCH 1/3] Add Black formatting check to CI --- .travis.yml | 28 ++++++++++++++++------------ environment.yml | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 247371cc9..368c9f2db 100644 --- a/.travis.yml +++ b/.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; @@ -67,4 +71,4 @@ after_success: after_failure: - ls /cores/core.* -- lldb --core `ls /cores/core.*` --batch --one-line "bt" \ No newline at end of file +- lldb --core `ls /cores/core.*` --batch --one-line "bt" diff --git a/environment.yml b/environment.yml index 108bc9e8e..e9310ae15 100644 --- a/environment.yml +++ b/environment.yml @@ -9,6 +9,7 @@ dependencies: - pyparsing - sphinx - sphinx_rtd_theme + - black - codecov - pytest - pytest-cov From 96ca1d03d3fdebce00981cd2ee5c7c0ba3c0195a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20S=C3=A1nchez=20de=20Le=C3=B3n=20Peque?= Date: Fri, 27 Dec 2019 01:26:28 +0100 Subject: [PATCH 2/3] Add some documentation for code contributors --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index e78e52035..2cd7aeb1b 100644 --- a/README.md +++ b/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: From b1f6b8cfead1ed443ad3ebd252f596d4fa7933a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20S=C3=A1nchez=20de=20Le=C3=B3n=20Peque?= Date: Fri, 27 Dec 2019 01:31:38 +0100 Subject: [PATCH 3/3] Use uncompromised code formatting --- cadquery/__init__.py | 58 +- cadquery/cq.py | 395 ++-- cadquery/cq_directive.py | 48 +- cadquery/cqgi.py | 142 +- cadquery/occ_impl/exporters.py | 88 +- cadquery/occ_impl/geom.py | 264 +-- cadquery/occ_impl/importers.py | 4 +- cadquery/occ_impl/jupyter_tools.py | 130 +- cadquery/occ_impl/shapes.py | 817 +++++---- cadquery/selectors.py | 185 +- doc/conf.py | 146 +- examples/Ex001_Simple_Block.py | 6 +- .../Ex002_Block_With_Bored_Center_Hole.py | 17 +- ...03_Pillow_Block_With_Counterbored_Holes.py | 36 +- examples/Ex004_Extruded_Cylindrical_Plate.py | 17 +- examples/Ex005_Extruded_Lines_and_Arcs.py | 20 +- .../Ex006_Moving_the_Current_Working_Point.py | 4 +- examples/Ex007_Using_Point_Lists.py | 4 +- examples/Ex008_Polygon_Creation.py | 21 +- examples/Ex009_Polylines.py | 19 +- .../Ex010_Defining_an_Edge_with_a_Spline.py | 2 +- examples/Ex015_Rotated_Workplanes.py | 14 +- examples/Ex016_Using_Construction_Geometry.py | 13 +- examples/Ex018_Making_Lofts.py | 13 +- examples/Ex019_Counter_Sunk_Holes.py | 12 +- examples/Ex021_Splitting_an_Object.py | 3 +- examples/Ex022_Revolution.py | 10 +- examples/Ex023_Sweep.py | 8 +- .../Ex024_Sweep_With_Multiple_Sections.py | 81 +- examples/Ex100_Lego_Brick.py | 40 +- setup.py | 65 +- tests/__init__.py | 32 +- tests/test_cad_objects.py | 153 +- tests/test_cadquery.py | 1584 +++++++++++------ tests/test_cqgi.py | 35 +- tests/test_exporters.py | 20 +- tests/test_importers.py | 25 +- tests/test_jupyter.py | 3 +- tests/test_selectors.py | 210 +-- tests/test_workplanes.py | 79 +- 40 files changed, 2931 insertions(+), 1892 deletions(-) diff --git a/cadquery/__init__.py b/cadquery/__init__.py index 57e4148a5..652ec5e66 100644 --- a/cadquery/__init__.py +++ b/cadquery/__init__.py @@ -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" diff --git a/cadquery/cq.py b/cadquery/cq.py index 23bb30e47..61496f2f5 100644 --- a/cadquery/cq.py +++ b/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 @@ def _collectProperty(self, propName): # 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 @@ def toOCC(self): 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 @@ def workplane(self, offset=0.0, invert=False, centerOption='CenterOfMass', 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 @@ def _isCoPlanar(f0, f1): 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 @@ def _computeXdir(normal): 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 @@ def _computeXdir(normal): 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 @@ def _findType(self, types, searchStack=True, searchParents=True): 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 @@ def vertices(self, selector=None): :py:class:`StringSyntaxSelector` """ - return self._selectObjects('Vertices', selector) + return self._selectObjects("Vertices", selector) def faces(self, selector=None): """ @@ -586,7 +607,7 @@ def faces(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Faces', selector) + return self._selectObjects("Faces", selector) def edges(self, selector=None): """ @@ -617,7 +638,7 @@ def edges(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Edges', selector) + return self._selectObjects("Edges", selector) def wires(self, selector=None): """ @@ -640,7 +661,7 @@ def wires(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Wires', selector) + return self._selectObjects("Wires", selector) def solids(self, selector=None): """ @@ -666,7 +687,7 @@ def solids(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Solids', selector) + return self._selectObjects("Solids", selector) def shells(self, selector=None): """ @@ -686,7 +707,7 @@ def shells(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Shells', selector) + return self._selectObjects("Shells", selector) def compounds(self, selector=None): """ @@ -704,7 +725,7 @@ def compounds(self, selector=None): See more about selectors HERE """ - return self._selectObjects('Compounds', selector) + return self._selectObjects("Compounds", selector) def toSvg(self, opts=None): """ @@ -777,8 +798,9 @@ def rotate(self, axisStartPoint, axisEndPoint, angleDegrees): :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 @@ def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)): :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 @@ def __init__(self, inPlane, origin=(0, 0, 0), obj=None): 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 @@ def __init__(self, inPlane, origin=(0, 0, 0), obj=None): 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 @@ def transformed(self, rotate=(0, 0, 0), offset=(0, 0, 0)): """ # 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 @@ def _findFromPoint(self, useLocalCoords=False): 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 @@ def _makeslot(pnt): :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 @@ def spline(self, listOfXYTuple, tangents=None, periodic=False, 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 @@ def parametricCurve(self, func, N=400, start=0, stop=1): """ - 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 @@ def sagittaArc(self, endPoint, sag, forConstruction=False): 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 @@ def radiusArc(self, endPoint, radius, forConstruction=False): # 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 @@ def rotateAndCopy(self, matrix): # 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 @@ def mirrorY(self): # 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 @@ def mirrorX(self): # 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 @@ def rect(self, xLen, yLen, centered=True, forConstruction=False): 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 @@ def circle(self, radius, forConstruction=False): 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 @@ def polygon(self, nSides, diameter, forConstruction=False): :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 @@ def _makeCbore(center): 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 @@ def _makeCsk(center): # 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 @@ def _makeHole(center): 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 @@ def twistExtrude(self, distance, angleDegrees, combine=True, clean=True): # 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 @@ def extrude(self, distance, combine=True, clean=True, both=False, taper=None): 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 @@ def extrude(self, distance, combine=True, clean=True, both=False, taper=None): 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 @@ def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True 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. @@ -2360,22 +2413,27 @@ def sweep(self, path, multisection=False, sweepAlongWires=None, makeSolid=True, Possible values are {'transformed','round', 'right'} (default: 'right'). :return: a CQ object with the resulting solid selected. """ - + 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 @@ def union(self, toUnion=None, clean=True): 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) @@ -2492,7 +2551,7 @@ def cut(self, toCut, clean=True): if clean: newS = newS.clean() - + return self.newObject([newS]) def intersect(self, toIntersect, clean=True): @@ -2522,8 +2581,9 @@ def intersect(self, toIntersect, clean=True): newS = solidRef.intersect(solidToIntersect) - if clean: newS = newS.clean() - + if clean: + newS = newS.clean() + return self.newObject([newS]) def cutBlind(self, distanceToCut, clean=True, taper=None): @@ -2580,19 +2640,18 @@ def cutThruAll(self, clean=True, taper=0): 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() @@ -2635,9 +2694,8 @@ def _extrude(self, distance, both=False, taper=None): # 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 @@ def _extrude(self, distance, both=False, taper=None): 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 @@ def _revolve(self, angleDegrees, axisStart, axisEnd): 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 @@ def _revolve(self, angleDegrees, axisStart, axisEnd): # 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,19 +2777,28 @@ def _sweep(self, path, multisection=False, makeSolid=True, isFrenet=False, 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 thisObj = Solid.sweep_multi(sections, path.val(), makeSolid, isFrenet) toFuse.append(thisObj) - + self.ctx.pendingWires = [] 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 @@ def box(self, length, width, height, centered=(True, True, True), combine=True, 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 @@ def _makebox(pnt): # 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 @@ def _makesphere(pnt): 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 @@ def _makesphere(pnt): 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 @@ def _makewedge(pnt): (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 @@ def clean(self): 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 @@ def text(self, txt, fontsize, distance, cut=True, combine=False, clean=True, 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 @@ def _repr_html_(self): """ 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_() - diff --git a/cadquery/cq_directive.py b/cadquery/cq_directive.py index 11de3b5e3..bfbfc0de0 100644 --- a/cadquery/cq_directive.py +++ b/cadquery/cq_directive.py @@ -20,13 +20,22 @@ """ -template_content_indent = ' ' - - -def cq_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): +template_content_indent = " " + + +def cq_directive( + name, + arguments, + options, + content, + lineno, + content_offset, + block_text, + state, + state_machine, +): # only consider inline snippets - plot_code = '\n'.join(content) + 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) diff --git a/cadquery/cqgi.py b/cadquery/cqgi.py index e6611f460..4562fcefd 100644 --- a/cadquery/cqgi.py +++ b/cadquery/cqgi.py @@ -9,6 +9,7 @@ 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 @@ def build(self, build_parameters=None, build_options=None): 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 @@ def set_param_values(self, params): 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 @@ def __init__(self): 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 @@ def create(ast_node, var_name, var_type, default_value, valid_values=None, desc= 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 @@ def set_value(self, new_value): self.ast_node.n = f except ValueError: raise InvalidParameterError( - "Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric." - .format(str(new_value), self.name)) + "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 @@ def debug(self,obj,args={}): 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 @@ def has_results(self): return len(self.outputObjects) > 0 - class InvalidParameterError(Exception): """ Raised when an attempt is made to provide a new parameter value that cannot be assigned to the model """ + pass @@ -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 @@ def with_real_builtins(self): 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 @@ def add_entry(self, name, value): 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 @@ def handle_assignment(self, var_name, value_node): 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 @@ def visit_Assign(self, node): # 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: diff --git a/cadquery/occ_impl/exporters.py b/cadquery/occ_impl/exporters.py index f35282168..39898b1b1 100644 --- a/cadquery/occ_impl/exporters.py +++ b/cadquery/occ_impl/exporters.py @@ -4,6 +4,7 @@ import tempfile import os import sys + if sys.version_info.major == 2: import cStringIO as StringIO else: @@ -56,7 +57,7 @@ def exportShape(shape, exportType, fileLike, tolerance=0.1): The object should be already open and ready to write. The caller is responsible for closing the object """ - + from ..cq import CQ def tessellate(shape): @@ -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 @@ def __init__(self, tessellation): 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 @@ def addTriangleFace(self, i, j, k): 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 @@ def exportSVG(shape, fileName): """ -PATHTEMPLATE = "\t\t\t\n" +PATHTEMPLATE = '\t\t\t\n' diff --git a/cadquery/occ_impl/geom.py b/cadquery/occ_impl/geom.py index d959702aa..812f6dc2a 100644 --- a/cadquery/occ_impl/geom.py +++ b/cadquery/occ_impl/geom.py @@ -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 @@ def __init__(self, *args): 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): @@ -53,25 +63,25 @@ def __init__(self, *args): @property def x(self): return self.wrapped.X() - + @x.setter - def x(self,value): + def x(self, value): self.wrapped.SetX(value) @property def y(self): return self.wrapped.Y() - + @y.setter - def y(self,value): + def y(self, value): self.wrapped.SetY(value) @property def z(self): return self.wrapped.Z() - + @z.setter - def z(self,value): + def z(self, value): self.wrapped.SetZ(value) @property @@ -132,16 +142,13 @@ def getAngle(self, v): 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 @@ def projectToPlane(self, plane): 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 @@ def __abs__(self): 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 @@ def __init__(self, matrix=None): 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) @@ -277,11 +289,12 @@ def multiply(self, other): def transposed_list(self): """Needed by the cqparts gltf exporter """ - + 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)] def __getitem__(self, rc): @@ -298,7 +311,7 @@ def __getitem__(self, rc): 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 @@ def named(cls, stdName, origin=(0, 0, 0)): 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 @@ def __init__(self, origin, xDir, normal): :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 @@ def __ne__(self, other): @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 @@ def isWireInside(self, baseWire, testWire): 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 @@ def isWireInside(self, baseWire, testWire): # 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 @@ -580,7 +593,7 @@ def toLocalCoords(self, obj): """ from .shapes import Shape - + if isinstance(obj, Vector): return obj.transform(self.fG) elif isinstance(obj, Shape): @@ -588,7 +601,9 @@ def toLocalCoords(self, obj): 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 @@ def rotateShapes(self, listOfShapes, rotationMatrix): raise NotImplementedError - ''' + """ resultWires = [] for w in listOfShapes: mirrored = w.transformGeometry(rotationMatrix.wrapped) @@ -681,21 +696,19 @@ def rotateShapes(self, listOfShapes, rotationMatrix): 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 @@ -726,22 +739,21 @@ def _calcTransforms(self): # the double-inverting is strange, and I don't understand it. forward = Matrix() inverse = Matrix() - + forwardT = gp_Trsf() 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 @@ def __init__(self, bb): 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 @@ def findOutsideBox2D(bb1, bb2): 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 @@ def _fromTopoDS(cls, shape, tol=None, optimal=False): 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 diff --git a/cadquery/occ_impl/importers.py b/cadquery/occ_impl/importers.py index 892144c46..60a7c9377 100644 --- a/cadquery/occ_impl/importers.py +++ b/cadquery/occ_impl/importers.py @@ -34,14 +34,14 @@ def importStep(fileName): Accepts a file name and loads the STEP file into a cadquery shape :param fileName: The path and name of the STEP file to be imported """ - + # Now read and return the shape reader = STEPControl_Reader() readStatus = reader.ReadFile(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()): diff --git a/cadquery/occ_impl/jupyter_tools.py b/cadquery/occ_impl/jupyter_tools.py index 0c4f64b89..1714d4ab6 100644 --- a/cadquery/occ_impl/jupyter_tools.py +++ b/cadquery/occ_impl/jupyter_tools.py @@ -6,8 +6,7 @@ from .geom import BoundBox -BOILERPLATE = \ -''' +BOILERPLATE = """
@@ -35,69 +34,86 @@ //document.getElementById('{id}').runtime.fitAll() -''' +""" -#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 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 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), + ) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index cbba68d76..caa83f697 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -3,84 +3,104 @@ import OCC.Core.TopAbs as ta # Tolopolgy type enum import OCC.Core.GeomAbs as ga # Geometry type enum -from OCC.Core.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ, - gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d) +from OCC.Core.gp import ( + gp_Vec, + gp_Pnt, + gp_Ax1, + gp_Ax2, + gp_Ax3, + gp_Dir, + gp_Circ, + gp_Trsf, + gp_Pln, + gp_GTrsf, + gp_Pnt2d, + gp_Dir2d, +) # collection of pints (used for spline construction) from OCC.Core.TColgp import TColgp_HArray1OfPnt from OCC.Core.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface -from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex, - BRepBuilderAPI_MakeEdge, - BRepBuilderAPI_MakeFace, - BRepBuilderAPI_MakePolygon, - BRepBuilderAPI_MakeWire, - BRepBuilderAPI_Sewing, - BRepBuilderAPI_MakeSolid, - BRepBuilderAPI_Copy, - BRepBuilderAPI_GTransform, - BRepBuilderAPI_Transform, - BRepBuilderAPI_Transformed, - BRepBuilderAPI_RightCorner, - BRepBuilderAPI_RoundCorner) +from OCC.Core.BRepBuilderAPI import ( + BRepBuilderAPI_MakeVertex, + BRepBuilderAPI_MakeEdge, + BRepBuilderAPI_MakeFace, + BRepBuilderAPI_MakePolygon, + BRepBuilderAPI_MakeWire, + BRepBuilderAPI_Sewing, + BRepBuilderAPI_MakeSolid, + BRepBuilderAPI_Copy, + BRepBuilderAPI_GTransform, + BRepBuilderAPI_Transform, + BRepBuilderAPI_Transformed, + BRepBuilderAPI_RightCorner, + BRepBuilderAPI_RoundCorner, +) + # properties used to store mass calculation result from OCC.Core.GProp import GProp_GProps -from OCC.Core.BRepGProp import BRepGProp_Face, \ - brepgprop_LinearProperties, \ - brepgprop_SurfaceProperties, \ - brepgprop_VolumeProperties # used for mass calculation +from OCC.Core.BRepGProp import ( + BRepGProp_Face, + brepgprop_LinearProperties, + brepgprop_SurfaceProperties, + brepgprop_VolumeProperties, +) # used for mass calculation from OCC.Core.BRepLProp import BRepLProp_CLProps # local curve properties -from OCC.Core.BRepPrimAPI import (BRepPrimAPI_MakeBox, - BRepPrimAPI_MakeCone, - BRepPrimAPI_MakeCylinder, - BRepPrimAPI_MakeTorus, - BRepPrimAPI_MakeWedge, - BRepPrimAPI_MakePrism, - BRepPrimAPI_MakeRevol, - BRepPrimAPI_MakeSphere) +from OCC.Core.BRepPrimAPI import ( + BRepPrimAPI_MakeBox, + BRepPrimAPI_MakeCone, + BRepPrimAPI_MakeCylinder, + BRepPrimAPI_MakeTorus, + BRepPrimAPI_MakeWedge, + BRepPrimAPI_MakePrism, + BRepPrimAPI_MakeRevol, + BRepPrimAPI_MakeSphere, +) from OCC.Core.TopExp import TopExp_Explorer # Toplogy explorer -from OCC.Core.BRepTools import (breptools_UVBounds, - breptools_OuterWire) +from OCC.Core.BRepTools import breptools_UVBounds, breptools_OuterWire + # used for getting underlying geoetry -- is this equvalent to brep adaptor? from OCC.Core.BRep import BRep_Tool, BRep_Tool_Degenerated -from OCC.Core.TopoDS import (topods_Vertex, # downcasting functions - topods_Edge, - topods_Wire, - topods_Face, - topods_Shell, - topods_Compound, - topods_Solid) +from OCC.Core.TopoDS import ( + topods_Vertex, # downcasting functions + topods_Edge, + topods_Wire, + topods_Face, + topods_Shell, + topods_Compound, + topods_Solid, +) -from OCC.Core.TopoDS import (TopoDS_Compound, - TopoDS_Builder) +from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder from OCC.Core.GC import GC_MakeArcOfCircle # geometry construction from OCC.Core.GCE2d import GCE2d_MakeSegment -from OCC.Core.GeomAPI import (GeomAPI_Interpolate, - GeomAPI_ProjectPointOnSurf) +from OCC.Core.GeomAPI import GeomAPI_Interpolate, GeomAPI_ProjectPointOnSurf from OCC.Core.BRepFill import brepfill_Shell, brepfill_Face -from OCC.Core.BRepAlgoAPI import (BRepAlgoAPI_Common, - BRepAlgoAPI_Fuse, - BRepAlgoAPI_Cut) +from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common, BRepAlgoAPI_Fuse, BRepAlgoAPI_Cut from OCC.Core.Geom import Geom_ConicalSurface, Geom_CylindricalSurface from OCC.Core.Geom2d import Geom2d_Line from OCC.Core.BRepLib import breplib_BuildCurves3d -from OCC.Core.BRepOffsetAPI import (BRepOffsetAPI_ThruSections, - BRepOffsetAPI_MakePipeShell, - BRepOffsetAPI_MakeThickSolid) +from OCC.Core.BRepOffsetAPI import ( + BRepOffsetAPI_ThruSections, + BRepOffsetAPI_MakePipeShell, + BRepOffsetAPI_MakeThickSolid, +) -from OCC.Core.BRepFilletAPI import (BRepFilletAPI_MakeChamfer, - BRepFilletAPI_MakeFillet) +from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeChamfer, BRepFilletAPI_MakeFillet -from OCC.Core.TopTools import (TopTools_IndexedDataMapOfShapeListOfShape, - TopTools_ListOfShape) +from OCC.Core.TopTools import ( + TopTools_IndexedDataMapOfShapeListOfShape, + TopTools_ListOfShape, +) from OCC.Core.TopExp import topexp_MapShapesAndAncestors @@ -101,10 +121,7 @@ from OCC.Core.BRepCheck import BRepCheck_Analyzer -from OCC.Core.Addons import (text_to_brep, - Font_FA_Regular, - Font_FA_Italic, - Font_FA_Bold) +from OCC.Core.Addons import text_to_brep, Font_FA_Regular, Font_FA_Italic, Font_FA_Bold from OCC.Core.BRepFeat import BRepFeat_MakeDPrism @@ -115,75 +132,81 @@ import warnings TOLERANCE = 1e-6 -DEG2RAD = 2 * pi / 360. +DEG2RAD = 2 * pi / 360.0 HASH_CODE_MAX = 2147483647 # max 32bit signed int, required by OCC.Core.HashCode -shape_LUT = \ - {ta.TopAbs_VERTEX: 'Vertex', - ta.TopAbs_EDGE: 'Edge', - ta.TopAbs_WIRE: 'Wire', - ta.TopAbs_FACE: 'Face', - ta.TopAbs_SHELL: 'Shell', - ta.TopAbs_SOLID: 'Solid', - ta.TopAbs_COMPOUND: 'Compound'} - -shape_properties_LUT = \ - {ta.TopAbs_VERTEX: None, - ta.TopAbs_EDGE: brepgprop_LinearProperties, - ta.TopAbs_WIRE: brepgprop_LinearProperties, - ta.TopAbs_FACE: brepgprop_SurfaceProperties, - ta.TopAbs_SHELL: brepgprop_SurfaceProperties, - ta.TopAbs_SOLID: brepgprop_VolumeProperties, - ta.TopAbs_COMPOUND: brepgprop_VolumeProperties} +shape_LUT = { + ta.TopAbs_VERTEX: "Vertex", + ta.TopAbs_EDGE: "Edge", + ta.TopAbs_WIRE: "Wire", + ta.TopAbs_FACE: "Face", + ta.TopAbs_SHELL: "Shell", + ta.TopAbs_SOLID: "Solid", + ta.TopAbs_COMPOUND: "Compound", +} + +shape_properties_LUT = { + ta.TopAbs_VERTEX: None, + ta.TopAbs_EDGE: brepgprop_LinearProperties, + ta.TopAbs_WIRE: brepgprop_LinearProperties, + ta.TopAbs_FACE: brepgprop_SurfaceProperties, + ta.TopAbs_SHELL: brepgprop_SurfaceProperties, + ta.TopAbs_SOLID: brepgprop_VolumeProperties, + ta.TopAbs_COMPOUND: brepgprop_VolumeProperties, +} inverse_shape_LUT = {v: k for k, v in shape_LUT.items()} -downcast_LUT = \ - {ta.TopAbs_VERTEX: topods_Vertex, - ta.TopAbs_EDGE: topods_Edge, - ta.TopAbs_WIRE: topods_Wire, - ta.TopAbs_FACE: topods_Face, - ta.TopAbs_SHELL: topods_Shell, - ta.TopAbs_SOLID: topods_Solid, - ta.TopAbs_COMPOUND: topods_Compound} - -geom_LUT = \ - {ta.TopAbs_VERTEX: 'Vertex', - ta.TopAbs_EDGE: BRepAdaptor_Curve, - ta.TopAbs_WIRE: 'Wire', - ta.TopAbs_FACE: BRepAdaptor_Surface, - ta.TopAbs_SHELL: 'Shell', - ta.TopAbs_SOLID: 'Solid', - ta.TopAbs_COMPOUND: 'Compound'} - -geom_LUT_FACE = \ - {ga.GeomAbs_Plane : 'PLANE', - ga.GeomAbs_Cylinder : 'CYLINDER', - ga.GeomAbs_Cone : 'CONE', - ga.GeomAbs_Sphere : 'SPHERE', - ga.GeomAbs_Torus : 'TORUS', - ga.GeomAbs_BezierSurface : 'BEZIER', - ga.GeomAbs_BSplineSurface : 'BSPLINE', - ga.GeomAbs_SurfaceOfRevolution : 'REVOLUTION', - ga.GeomAbs_SurfaceOfExtrusion : 'EXTRUSION', - ga.GeomAbs_OffsetSurface : 'OFFSET', - ga.GeomAbs_OtherSurface : 'OTHER'} - -geom_LUT_EDGE = \ - {ga.GeomAbs_Line : 'LINE', - ga.GeomAbs_Circle : 'CIRCLE', - ga.GeomAbs_Ellipse : 'ELLIPSE', - ga.GeomAbs_Hyperbola : 'HYPERBOLA', - ga.GeomAbs_Parabola : 'PARABOLA', - ga.GeomAbs_BezierCurve : 'BEZIER', - ga.GeomAbs_BSplineCurve : 'BSPLINE', - ga.GeomAbs_OtherCurve : 'OTHER'} +downcast_LUT = { + ta.TopAbs_VERTEX: topods_Vertex, + ta.TopAbs_EDGE: topods_Edge, + ta.TopAbs_WIRE: topods_Wire, + ta.TopAbs_FACE: topods_Face, + ta.TopAbs_SHELL: topods_Shell, + ta.TopAbs_SOLID: topods_Solid, + ta.TopAbs_COMPOUND: topods_Compound, +} + +geom_LUT = { + ta.TopAbs_VERTEX: "Vertex", + ta.TopAbs_EDGE: BRepAdaptor_Curve, + ta.TopAbs_WIRE: "Wire", + ta.TopAbs_FACE: BRepAdaptor_Surface, + ta.TopAbs_SHELL: "Shell", + ta.TopAbs_SOLID: "Solid", + ta.TopAbs_COMPOUND: "Compound", +} + +geom_LUT_FACE = { + ga.GeomAbs_Plane: "PLANE", + ga.GeomAbs_Cylinder: "CYLINDER", + ga.GeomAbs_Cone: "CONE", + ga.GeomAbs_Sphere: "SPHERE", + ga.GeomAbs_Torus: "TORUS", + ga.GeomAbs_BezierSurface: "BEZIER", + ga.GeomAbs_BSplineSurface: "BSPLINE", + ga.GeomAbs_SurfaceOfRevolution: "REVOLUTION", + ga.GeomAbs_SurfaceOfExtrusion: "EXTRUSION", + ga.GeomAbs_OffsetSurface: "OFFSET", + ga.GeomAbs_OtherSurface: "OTHER", +} + +geom_LUT_EDGE = { + ga.GeomAbs_Line: "LINE", + ga.GeomAbs_Circle: "CIRCLE", + ga.GeomAbs_Ellipse: "ELLIPSE", + ga.GeomAbs_Hyperbola: "HYPERBOLA", + ga.GeomAbs_Parabola: "PARABOLA", + ga.GeomAbs_BezierCurve: "BEZIER", + ga.GeomAbs_BSplineCurve: "BSPLINE", + ga.GeomAbs_OtherCurve: "OTHER", +} def downcast(topods_obj): - ''' + """ Downcasts a TopoDS object to suitable specialized type - ''' + """ return downcast_LUT[topods_obj.ShapeType()](topods_obj) @@ -204,46 +227,46 @@ def __init__(self, obj): def clean(self): """Experimental clean using ShapeUpgrade""" - upgrader = ShapeUpgrade_UnifySameDomain( - self.wrapped, True, True, True) + upgrader = ShapeUpgrade_UnifySameDomain(self.wrapped, True, True, True) upgrader.Build() return self.cast(upgrader.Shape()) - + def fix(self): """Try to fix shape if not valid""" if not BRepCheck_Analyzer(self.wrapped).IsValid(): sf = ShapeFix_Shape(self.wrapped) sf.Perform() fixed = downcast(sf.Shape()) - + return self.cast(fixed) - + return self @classmethod def cast(cls, obj, forConstruction=False): "Returns the right type of wrapper, given a FreeCAD object" - + tr = None # define the shape lookup table for casting - constructor_LUT = {ta.TopAbs_VERTEX: Vertex, - ta.TopAbs_EDGE: Edge, - ta.TopAbs_WIRE: Wire, - ta.TopAbs_FACE: Face, - ta.TopAbs_SHELL: Shell, - ta.TopAbs_SOLID: Solid, - ta.TopAbs_COMPOUND: Compound} + constructor_LUT = { + ta.TopAbs_VERTEX: Vertex, + ta.TopAbs_EDGE: Edge, + ta.TopAbs_WIRE: Wire, + ta.TopAbs_FACE: Face, + ta.TopAbs_SHELL: Shell, + ta.TopAbs_SOLID: Solid, + ta.TopAbs_COMPOUND: Compound, + } t = obj.ShapeType() # NB downcast is nedded to handly TopoDS_Shape types tr = constructor_LUT[t](downcast(obj)) tr.forConstruction = forConstruction - return tr - + def exportStl(self, fileName, precision=1e-5): mesh = BRepMesh_IncrementalMesh(self.wrapped, precision, True) @@ -266,7 +289,7 @@ def exportBrep(self, fileName): """ return breptools_Write(self.wrapped, fileName) - + def geomType(self): """ Gets the underlying geometry type @@ -300,7 +323,7 @@ def geomType(self): return geom_LUT_EDGE[tr(self.wrapped).GetType()] else: return geom_LUT_FACE[tr(self.wrapped).GetType()] - + def hashCode(self): return self.wrapped.HashCode(HASH_CODE_MAX) @@ -332,8 +355,7 @@ def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)): basePointVector = Vector(basePointVector) T = gp_Trsf() - T.SetMirror(gp_Ax2(gp_Pnt(*basePointVector.toTuple()), - mirrorPlaneNormalVector)) + T.SetMirror(gp_Ax2(gp_Pnt(*basePointVector.toTuple()), mirrorPlaneNormalVector)) return self._apply_transform(T) @@ -341,15 +363,14 @@ def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)): def _center_of_mass(shape): Properties = GProp_GProps() - brepgprop_VolumeProperties(shape, - Properties) + brepgprop_VolumeProperties(shape, Properties) return Vector(Properties.CentreOfMass()) def Center(self): - ''' + """ Center of mass - ''' + """ return Shape.centerOfMass(self) @@ -364,14 +385,15 @@ def CombinedCenter(objects): :param objects: a list of objects with mass """ total_mass = sum(Shape.computeMass(o) for o in objects) - weighted_centers = [Shape.centerOfMass(o).multiply( - Shape.computeMass(o)) for o in objects] + weighted_centers = [ + Shape.centerOfMass(o).multiply(Shape.computeMass(o)) for o in objects + ] sum_wc = weighted_centers[0] for wc in weighted_centers[1:]: sum_wc = sum_wc.add(wc) - return Vector(sum_wc.multiply(1. / total_mass)) + return Vector(sum_wc.multiply(1.0 / total_mass)) @staticmethod def computeMass(obj): @@ -419,7 +441,7 @@ def CombinedCenterOfBoundBox(objects, tolerance=0.1): for wc in weighted_centers[1:]: sum_wc = sum_wc.add(wc) - return Vector(sum_wc.multiply(1. / total_mass)) + return Vector(sum_wc.multiply(1.0 / total_mass)) def Closed(self): return self.wrapped.Closed() @@ -442,30 +464,29 @@ def _entities(self, topo_type): def Vertices(self): - return [Vertex(i) for i in self._entities('Vertex')] + return [Vertex(i) for i in self._entities("Vertex")] def Edges(self): - return [Edge(i) for i in self._entities('Edge') if not BRep_Tool_Degenerated(i)] + return [Edge(i) for i in self._entities("Edge") if not BRep_Tool_Degenerated(i)] def Compounds(self): - return [Compound(i) for i in self._entities('Compound')] + return [Compound(i) for i in self._entities("Compound")] def Wires(self): - return [Wire(i) for i in self._entities('Wire')] + return [Wire(i) for i in self._entities("Wire")] def Faces(self): - return [Face(i) for i in self._entities('Face')] + return [Face(i) for i in self._entities("Face")] def Shells(self): - return [Shell(i) for i in self._entities('Shell')] + return [Shell(i) for i in self._entities("Shell")] def Solids(self): - return [Solid(i) for i in self._entities('Solid')] + return [Solid(i) for i in self._entities("Solid")] def Area(self): Properties = GProp_GProps() - brepgprop_SurfaceProperties(self.wrapped, - Properties) + brepgprop_SurfaceProperties(self.wrapped, Properties) return Properties.Mass() @@ -475,9 +496,7 @@ def Volume(self): def _apply_transform(self, T): - return Shape.cast(BRepBuilderAPI_Transform(self.wrapped, - T, - True).Shape()) + return Shape.cast(BRepBuilderAPI_Transform(self.wrapped, T, True).Shape()) def rotate(self, startVector, endVector, angleDegrees): """ @@ -494,9 +513,10 @@ def rotate(self, startVector, endVector, angleDegrees): endVector = Vector(endVector) T = gp_Trsf() - T.SetRotation(gp_Ax1(startVector.toPnt(), - (endVector - startVector).toDir()), - angleDegrees * DEG2RAD) + T.SetRotation( + gp_Ax1(startVector.toPnt(), (endVector - startVector).toDir()), + angleDegrees * DEG2RAD, + ) return self._apply_transform(T) @@ -513,8 +533,7 @@ def translate(self, vector): def scale(self, factor): T = gp_Trsf() - T.SetScale(gp_Pnt(), - factor) + T.SetScale(gp_Pnt(), factor) return self._apply_transform(T) @@ -529,8 +548,9 @@ def transformShape(self, tMatrix): with all objects keeping their type """ - r = Shape.cast(BRepBuilderAPI_Transform(self.wrapped, - tMatrix.wrapped.Trsf()).Shape()) + r = Shape.cast( + BRepBuilderAPI_Transform(self.wrapped, tMatrix.wrapped.Trsf()).Shape() + ) r.forConstruction = self.forConstruction return r @@ -548,9 +568,9 @@ def transformGeometry(self, tMatrix): If your transformation is only translation and rotation, it is safer to use transformShape, which doesnt change the underlying type of the geometry, but cannot handle skew transformations """ - r = Shape.cast(BRepBuilderAPI_GTransform(self.wrapped, - tMatrix.wrapped, - True).Shape()) + r = Shape.cast( + BRepBuilderAPI_GTransform(self.wrapped, tMatrix.wrapped, True).Shape() + ) r.forConstruction = self.forConstruction return r @@ -562,8 +582,7 @@ def cut(self, toCut): """ Remove a shape from another one """ - return Shape.cast(BRepAlgoAPI_Cut(self.wrapped, - toCut.wrapped).Shape()) + return Shape.cast(BRepAlgoAPI_Cut(self.wrapped, toCut.wrapped).Shape()) def fuse(self, toFuse): """ @@ -582,8 +601,7 @@ def intersect(self, toIntersect): """ Construct shape intersection """ - return Shape.cast(BRepAlgoAPI_Common(self.wrapped, - toIntersect.wrapped).Shape()) + return Shape.cast(BRepAlgoAPI_Common(self.wrapped, toIntersect.wrapped).Shape()) def _repr_html_(self): """ @@ -591,6 +609,7 @@ def _repr_html_(self): """ from .jupyter_tools import x3d_display + return x3d_display(self.wrapped, export_edges=True) @@ -611,9 +630,7 @@ def __init__(self, obj, forConstruction=False): def toTuple(self): geom_point = BRep_Tool.Pnt(self.wrapped) - return (geom_point.X(), - geom_point.Y(), - geom_point.Z()) + return (geom_point.X(), geom_point.Y(), geom_point.Z()) def Center(self): """ @@ -624,12 +641,10 @@ def Center(self): @classmethod def makeVertex(cls, x, y, z): - return cls(BRepBuilderAPI_MakeVertex(gp_Pnt(x, y, z) - ).Vertex()) + return cls(BRepBuilderAPI_MakeVertex(gp_Pnt(x, y, z)).Vertex()) class Mixin1D(object): - def Length(self): Properties = GProp_GProps() @@ -690,8 +705,8 @@ def tangentAt(self, locationParam=0.5): curve = self._geomAdaptor() umin, umax = curve.FirstParameter(), curve.LastParameter() - umid = (1-locationParam)*umin + locationParam*umax - + umid = (1 - locationParam) * umin + locationParam * umax + curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance()) curve_props.SetParameter(umid) @@ -704,35 +719,32 @@ def tangentAt(self, locationParam=0.5): def Center(self): Properties = GProp_GProps() - brepgprop_LinearProperties(self.wrapped, - Properties) + brepgprop_LinearProperties(self.wrapped, Properties) return Vector(Properties.CentreOfMass()) @classmethod - def makeCircle(cls, radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angle1=360.0, angle2=360): + def makeCircle( + cls, radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angle1=360.0, angle2=360 + ): """ """ pnt = Vector(pnt) dir = Vector(dir) - circle_gp = gp_Circ(gp_Ax2(pnt.toPnt(), - dir.toDir()), - radius) + circle_gp = gp_Circ(gp_Ax2(pnt.toPnt(), dir.toDir()), radius) if angle1 == angle2: # full circle case return cls(BRepBuilderAPI_MakeEdge(circle_gp).Edge()) else: # arc case - circle_geom = GC_MakeArcOfCircle(circle_gp, - angle1 * DEG2RAD, - angle2 * DEG2RAD, - True).Value() + circle_geom = GC_MakeArcOfCircle( + circle_gp, angle1 * DEG2RAD, angle2 * DEG2RAD, True + ).Value() return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) @classmethod - def makeSpline(cls, listOfVector, tangents=None, periodic=False, - tol = 1e-6): + def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6): """ Interpolate a spline through the provided points. :param cls: @@ -744,12 +756,12 @@ def makeSpline(cls, listOfVector, tangents=None, periodic=False, """ pnts = TColgp_HArray1OfPnt(1, len(listOfVector)) for ix, v in enumerate(listOfVector): - pnts.SetValue(ix+1, v.toPnt()) + pnts.SetValue(ix + 1, v.toPnt()) spline_builder = GeomAPI_Interpolate(pnts, periodic, tol) if tangents: - v1,v2 = tangents - spline_builder.Load(v1.wrapped,v2.wrapped) + v1, v2 = tangents + spline_builder.Load(v1.wrapped, v2.wrapped) spline_builder.Perform() spline_geom = spline_builder.Curve() @@ -766,9 +778,7 @@ def makeThreePointArc(cls, v1, v2, v3): :param v3: end vector :return: an edge object through the three points """ - circle_geom = GC_MakeArcOfCircle(v1.toPnt(), - v2.toPnt(), - v3.toPnt()).Value() + circle_geom = GC_MakeArcOfCircle(v1.toPnt(), v2.toPnt(), v3.toPnt()).Value() return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) @@ -780,8 +790,7 @@ def makeLine(cls, v1, v2): :param v2: Vector that represents the second point :return: A linear edge between the two provided points """ - return cls(BRepBuilderAPI_MakeEdge(v1.toPnt(), - v2.toPnt()).Edge()) + return cls(BRepBuilderAPI_MakeEdge(v1.toPnt(), v2.toPnt()).Edge()) class Wire(Shape, Mixin1D): @@ -819,17 +828,23 @@ def assembleEdges(cls, listOfEdges): :BRepBuilderAPI_NonManifoldWire = 3 """ wire_builder = BRepBuilderAPI_MakeWire() - + edges_list = TopTools_ListOfShape() for e in listOfEdges: - edges_list.Append(e.wrapped) + edges_list.Append(e.wrapped) wire_builder.Add(edges_list) - if wire_builder.Error()!=0: - w1 = 'BRepBuilderAPI_MakeWire::IsDone(): returns true if this algorithm contains a valid wire. IsDone returns false if: there are no edges in the wire, or the last edge which you tried to add was not connectable = '+ str(wire_builder.IsDone()) - w2 = 'BRepBuilderAPI_MakeWire::Error(): returns the construction status. BRepBuilderAPI_WireDone if the wire is built, or another value of the BRepBuilderAPI_WireError enumeration indicating why the construction failed = ' + str(wire_builder.Error()) + if wire_builder.Error() != 0: + w1 = ( + "BRepBuilderAPI_MakeWire::IsDone(): returns true if this algorithm contains a valid wire. IsDone returns false if: there are no edges in the wire, or the last edge which you tried to add was not connectable = " + + str(wire_builder.IsDone()) + ) + w2 = ( + "BRepBuilderAPI_MakeWire::Error(): returns the construction status. BRepBuilderAPI_WireDone if the wire is built, or another value of the BRepBuilderAPI_WireError enumeration indicating why the construction failed = " + + str(wire_builder.Error()) + ) warnings.warn(w1) warnings.warn(w2) - + return cls(wire_builder.Wire()) @classmethod @@ -860,8 +875,16 @@ def makePolygon(cls, listOfVertices, forConstruction=False): return w @classmethod - def makeHelix(cls, pitch, height, radius, center=Vector(0, 0, 0), - dir=Vector(0, 0, 1), angle=360.0, lefthand=False): + def makeHelix( + cls, + pitch, + height, + radius, + center=Vector(0, 0, 0), + dir=Vector(0, 0, 1), + angle=360.0, + lefthand=False, + ): """ Make a helix with a given pitch, height and radius By default a cylindrical surface is used to create the helix. If @@ -869,13 +892,14 @@ def makeHelix(cls, pitch, height, radius, center=Vector(0, 0, 0), """ # 1. build underlying cylindrical/conical surface - if angle == 360.: - geom_surf = Geom_CylindricalSurface(gp_Ax3(center.toPnt(), dir.toDir()), - radius) + if angle == 360.0: + geom_surf = Geom_CylindricalSurface( + gp_Ax3(center.toPnt(), dir.toDir()), radius + ) else: - geom_surf = Geom_ConicalSurface(gp_Ax3(center.toPnt(), dir.toDir()), - angle * DEG2RAD, - radius) + geom_surf = Geom_ConicalSurface( + gp_Ax3(center.toPnt(), dir.toDir()), angle * DEG2RAD, radius + ) # 2. construct an semgent in the u,v domain if lefthand: @@ -885,8 +909,8 @@ def makeHelix(cls, pitch, height, radius, center=Vector(0, 0, 0), # 3. put it together into a wire n_turns = height / pitch - u_start = geom_line.Value(0.) - u_stop = geom_line.Value(sqrt(n_turns * ((2 * pi)**2 + pitch**2))) + u_start = geom_line.Value(0.0) + u_stop = geom_line.Value(sqrt(n_turns * ((2 * pi) ** 2 + pitch ** 2))) geom_seg = GCE2d_MakeSegment(u_start, u_stop).Value() e = BRepBuilderAPI_MakeEdge(geom_seg, geom_surf).Edge() @@ -940,8 +964,7 @@ def normalAt(self, locationVector=None): v = 0.5 * (v0 + v1) else: # project point on surface - projector = GeomAPI_ProjectPointOnSurf(locationVector.toPnt(), - surface) + projector = GeomAPI_ProjectPointOnSurf(locationVector.toPnt(), surface) u, v = projector.LowerDistanceParameters() @@ -954,19 +977,18 @@ def normalAt(self, locationVector=None): def Center(self): Properties = GProp_GProps() - brepgprop_SurfaceProperties(self.wrapped, - Properties) + brepgprop_SurfaceProperties(self.wrapped, Properties) return Vector(Properties.CentreOfMass()) - + def outerWire(self): - + return self.cast(breptools_OuterWire(self.wrapped)) - + def innerWires(self): - + outer = self.outerWire() - + return [w for w in self.Wires() if not w.isSame(outer)] @classmethod @@ -976,11 +998,11 @@ def makePlane(cls, length, width, basePnt=(0, 0, 0), dir=(0, 0, 1)): pln_geom = gp_Pln(basePnt.toPnt(), dir.toDir()) - return cls(BRepBuilderAPI_MakeFace(pln_geom, - -width * 0.5, - width * 0.5, - -length * 0.5, - length * 0.5).Face()) + return cls( + BRepBuilderAPI_MakeFace( + pln_geom, -width * 0.5, width * 0.5, -length * 0.5, length * 0.5 + ).Face() + ) @classmethod def makeRuledSurface(cls, edgeOrWire1, edgeOrWire2, dist=None): @@ -991,26 +1013,24 @@ def makeRuledSurface(cls, edgeOrWire1, edgeOrWire2, dist=None): """ if isinstance(edgeOrWire1, Wire): - return cls.cast(brepfill_Shell(edgeOrWire1.wrapped, - edgeOrWire1.wrapped)) + return cls.cast(brepfill_Shell(edgeOrWire1.wrapped, edgeOrWire1.wrapped)) else: - return cls.cast(brepfill_Face(edgeOrWire1.wrapped, - edgeOrWire1.wrapped)) + return cls.cast(brepfill_Face(edgeOrWire1.wrapped, edgeOrWire1.wrapped)) @classmethod def makeFromWires(cls, outerWire, innerWires=[]): - ''' + """ Makes a planar face from one or more wires - ''' - - face_builder = BRepBuilderAPI_MakeFace(outerWire.wrapped,True) + """ + + face_builder = BRepBuilderAPI_MakeFace(outerWire.wrapped, True) for w in innerWires: face_builder.Add(w.wrapped) - + face_builder.Build() face = face_builder.Shape() - + return cls.cast(face).fix() @@ -1028,18 +1048,17 @@ def makeShell(cls, listOfFaces): shell_builder.Add(face.wrapped) shell_builder.Perform() - + return cls.cast(shell_builder.SewedShape()) class Mixin3D(object): - def tessellate(self, tolerance): tess = Tesselator(self.wrapped) tess.Compute(compute_edges=True, mesh_quality=tolerance) vertices = [] - indexes = [] + indexes = [] # add vertices for i_vert in range(tess.ObjGetVertexCount()): @@ -1080,10 +1099,9 @@ def chamfer(self, length, length2, edgeList): # make a edge --> faces mapping edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape() - topexp_MapShapesAndAncestors(self.wrapped, - ta.TopAbs_EDGE, - ta.TopAbs_FACE, - edge_face_map) + topexp_MapShapesAndAncestors( + self.wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, edge_face_map + ) # note: we prefer 'length' word to 'radius' as opposed to FreeCAD's API chamfer_builder = BRepFilletAPI_MakeChamfer(self.wrapped) @@ -1097,10 +1115,9 @@ def chamfer(self, length, length2, edgeList): for e in nativeEdges: face = edge_face_map.FindFromKey(e).First() - chamfer_builder.Add(d1, - d2, - e, - topods_Face(face)) # NB: edge_face_map return a generic TopoDS_Shape + chamfer_builder.Add( + d1, d2, e, topods_Face(face) + ) # NB: edge_face_map return a generic TopoDS_Shape return self.__class__(chamfer_builder.Shape()) def shell(self, faceList, thickness, tolerance=0.0001): @@ -1117,10 +1134,9 @@ def shell(self, faceList, thickness, tolerance=0.0001): for f in faceList: occ_faces_list.Append(f.wrapped) - shell_builder = BRepOffsetAPI_MakeThickSolid(self.wrapped, - occ_faces_list, - thickness, - tolerance) + shell_builder = BRepOffsetAPI_MakeThickSolid( + self.wrapped, occ_faces_list, thickness, tolerance + ) shell_builder.Build() @@ -1141,29 +1157,29 @@ def isInside(self, point, tolerance=1.0e-6): solid_classifier = BRepClass3d_SolidClassifier(self.wrapped) solid_classifier.Perform(gp_Pnt(*point), tolerance) - return (solid_classifier.State() == ta.TopAbs_IN or - solid_classifier.IsOnAFace()) + return solid_classifier.State() == ta.TopAbs_IN or solid_classifier.IsOnAFace() class Solid(Shape, Mixin3D): """ a single solid """ - + @classmethod def isSolid(cls, obj): """ Returns true if the object is a FreeCAD solid, false otherwise """ - if hasattr(obj, 'ShapeType'): - if obj.ShapeType == 'Solid' or \ - (obj.ShapeType == 'Compound' and len(obj.Solids) > 0): + if hasattr(obj, "ShapeType"): + if obj.ShapeType == "Solid" or ( + obj.ShapeType == "Compound" and len(obj.Solids) > 0 + ): return True return False - + @classmethod def makeSolid(cls, shell): - + return cls(BRepBuilderAPI_MakeSolid(shell.wrapped).Solid()) @classmethod @@ -1172,53 +1188,77 @@ def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1) makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height) By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)' """ - return cls(BRepPrimAPI_MakeBox(gp_Ax2(pnt.toPnt(), - dir.toDir()), - length, - width, - height).Shape()) + return cls( + BRepPrimAPI_MakeBox( + gp_Ax2(pnt.toPnt(), dir.toDir()), length, width, height + ).Shape() + ) @classmethod - def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360): + def makeCone( + cls, + radius1, + radius2, + height, + pnt=Vector(0, 0, 0), + dir=Vector(0, 0, 1), + angleDegrees=360, + ): """ Make a cone with given radii and height By default pnt=Vector(0,0,0), dir=Vector(0,0,1) and angle=360' """ - return cls(BRepPrimAPI_MakeCone(gp_Ax2(pnt.toPnt(), - dir.toDir()), - radius1, - radius2, - height, - angleDegrees * DEG2RAD).Shape()) + return cls( + BRepPrimAPI_MakeCone( + gp_Ax2(pnt.toPnt(), dir.toDir()), + radius1, + radius2, + height, + angleDegrees * DEG2RAD, + ).Shape() + ) @classmethod - def makeCylinder(cls, radius, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360): + def makeCylinder( + cls, radius, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360 + ): """ makeCylinder(radius,height,[pnt,dir,angle]) -- Make a cylinder with a given radius and height By default pnt=Vector(0,0,0),dir=Vector(0,0,1) and angle=360' """ - return cls(BRepPrimAPI_MakeCylinder(gp_Ax2(pnt.toPnt(), - dir.toDir()), - radius, - height, - angleDegrees * DEG2RAD).Shape()) + return cls( + BRepPrimAPI_MakeCylinder( + gp_Ax2(pnt.toPnt(), dir.toDir()), radius, height, angleDegrees * DEG2RAD + ).Shape() + ) @classmethod - def makeTorus(cls, radius1, radius2, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None): + def makeTorus( + cls, + radius1, + radius2, + pnt=None, + dir=None, + angleDegrees1=None, + angleDegrees2=None, + ): """ makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) -- Make a torus with agiven radii and angles By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0 ,angle1=360 and angle=360' """ - return cls(BRepPrimAPI_MakeTorus(gp_Ax2(pnt.toPnt(), - dir.toDir()), - radius1, - radius2, - angleDegrees1 * DEG2RAD, - angleDegrees2 * DEG2RAD).Shape()) + return cls( + BRepPrimAPI_MakeTorus( + gp_Ax2(pnt.toPnt(), dir.toDir()), + radius1, + radius2, + angleDegrees1 * DEG2RAD, + angleDegrees2 * DEG2RAD, + ).Shape() + ) @classmethod def makeLoft(cls, listOfWire, ruled=False): @@ -1240,35 +1280,52 @@ def makeLoft(cls, listOfWire, ruled=False): return cls(loft_builder.Shape()) @classmethod - def makeWedge(cls, dx, dy, dz, xmin, zmin, xmax, zmax, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)): + def makeWedge( + cls, + dx, + dy, + dz, + xmin, + zmin, + xmax, + zmax, + pnt=Vector(0, 0, 0), + dir=Vector(0, 0, 1), + ): """ Make a wedge located in pnt By default pnt=Vector(0,0,0) and dir=Vector(0,0,1) """ - return cls(BRepPrimAPI_MakeWedge( - gp_Ax2(pnt.toPnt(), - dir.toDir()), - dx, - dy, - dz, - xmin, - zmin, - xmax, - zmax).Solid()) + return cls( + BRepPrimAPI_MakeWedge( + gp_Ax2(pnt.toPnt(), dir.toDir()), dx, dy, dz, xmin, zmin, xmax, zmax + ).Solid() + ) @classmethod - def makeSphere(cls, radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees1=0, angleDegrees2=90, angleDegrees3=360): + def makeSphere( + cls, + radius, + pnt=Vector(0, 0, 0), + dir=Vector(0, 0, 1), + angleDegrees1=0, + angleDegrees2=90, + angleDegrees3=360, + ): """ Make a sphere with a given radius By default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360 """ - return cls(BRepPrimAPI_MakeSphere(gp_Ax2(pnt.toPnt(), - dir.toDir()), - radius, - angleDegrees1 * DEG2RAD, - angleDegrees2 * DEG2RAD, - angleDegrees3 * DEG2RAD).Shape()) + return cls( + BRepPrimAPI_MakeSphere( + gp_Ax2(pnt.toPnt(), dir.toDir()), + radius, + angleDegrees1 * DEG2RAD, + angleDegrees2 * DEG2RAD, + angleDegrees3 * DEG2RAD, + ).Shape() + ) @classmethod def _extrudeAuxSpine(cls, wire, spine, auxSpine): @@ -1283,7 +1340,9 @@ def _extrudeAuxSpine(cls, wire, spine, auxSpine): return extrude_builder.Shape() @classmethod - def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees): + def extrudeLinearWithRotation( + cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees + ): """ Creates a 'twisted prism' by extruding, while simultaneously rotating around the extrusion vector. @@ -1306,26 +1365,25 @@ def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, """ # make straight spine straight_spine_e = Edge.makeLine(vecCenter, vecCenter.add(vecNormal)) - straight_spine_w = Wire.combine([straight_spine_e, ]).wrapped + straight_spine_w = Wire.combine([straight_spine_e,]).wrapped # make an auxliliary spine - pitch = 360. / angleDegrees * vecNormal.Length + pitch = 360.0 / angleDegrees * vecNormal.Length radius = 1 - aux_spine_w = Wire.makeHelix(pitch, - vecNormal.Length, - radius, - center=vecCenter, - dir=vecNormal).wrapped + aux_spine_w = Wire.makeHelix( + pitch, vecNormal.Length, radius, center=vecCenter, dir=vecNormal + ).wrapped # extrude the outer wire - outer_solid = cls._extrudeAuxSpine(outerWire.wrapped, - straight_spine_w, - aux_spine_w) + outer_solid = cls._extrudeAuxSpine( + outerWire.wrapped, straight_spine_w, aux_spine_w + ) # extrude inner wires - inner_solids = [cls._extrudeAuxSpine(w.wrapped, - straight_spine_w. - aux_spine_w) for w in innerWires] + inner_solids = [ + cls._extrudeAuxSpine(w.wrapped, straight_spine_w.aux_spine_w) + for w in innerWires + ] # combine the inner solids into compund inner_comp = Compound._makeCompound(inner_solids) @@ -1358,21 +1416,19 @@ def extrudeLinear(cls, outerWire, innerWires, vecNormal, taper=0): reliable. """ - if taper==0: + if taper == 0: face = Face.makeFromWires(outerWire, innerWires) - prism_builder = BRepPrimAPI_MakePrism( - face.wrapped, vecNormal.wrapped, True) + prism_builder = BRepPrimAPI_MakePrism(face.wrapped, vecNormal.wrapped, True) else: face = Face.makeFromWires(outerWire) faceNormal = face.normalAt() - d = 1 if vecNormal.getAngle(faceNormal)<90 * DEG2RAD else -1 - prism_builder = LocOpe_DPrism(face.wrapped, - d * vecNormal.Length, - d * taper * DEG2RAD) + d = 1 if vecNormal.getAngle(faceNormal) < 90 * DEG2RAD else -1 + prism_builder = LocOpe_DPrism( + face.wrapped, d * vecNormal.Length, d * taper * DEG2RAD + ) return cls(prism_builder.Shape()) - @classmethod def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd): """ @@ -1404,20 +1460,28 @@ def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd): v1 = Vector(axisStart) v2 = Vector(axisEnd) v2 = v2 - v1 - revol_builder = BRepPrimAPI_MakeRevol(face.wrapped, - gp_Ax1(v1.toPnt(), v2.toDir()), - angleDegrees * DEG2RAD, - True) + revol_builder = BRepPrimAPI_MakeRevol( + face.wrapped, gp_Ax1(v1.toPnt(), v2.toDir()), angleDegrees * DEG2RAD, True + ) return cls(revol_builder.Shape()) - _transModeDict = {'transformed' : BRepBuilderAPI_Transformed, - 'round' : BRepBuilderAPI_RoundCorner, - 'right' : BRepBuilderAPI_RightCorner} + _transModeDict = { + "transformed": BRepBuilderAPI_Transformed, + "round": BRepBuilderAPI_RoundCorner, + "right": BRepBuilderAPI_RightCorner, + } @classmethod - def sweep(cls, outerWire, innerWires, path, makeSolid=True, isFrenet=False, - transitionMode='transformed'): + def sweep( + cls, + outerWire, + innerWires, + path, + makeSolid=True, + isFrenet=False, + transitionMode="transformed", + ): """ Attempt to sweep the list of wires into a prismatic solid along the provided path @@ -1431,11 +1495,11 @@ def sweep(cls, outerWire, innerWires, path, makeSolid=True, isFrenet=False, Possible values are {'transformed','round', 'right'} (default: 'right'). :return: a Solid object """ - if path.ShapeType() == 'Edge': - path = Wire.assembleEdges([path, ]) + if path.ShapeType() == "Edge": + path = Wire.assembleEdges([path,]) shapes = [] - for w in [outerWire]+innerWires: + for w in [outerWire] + innerWires: builder = BRepOffsetAPI_MakePipeShell(path.wrapped) builder.SetMode(isFrenet) builder.SetTransitionMode(cls._transModeDict[transitionMode]) @@ -1447,10 +1511,10 @@ def sweep(cls, outerWire, innerWires, path, makeSolid=True, isFrenet=False, shapes.append(cls(builder.Shape())) - rv,inner_shapes = shapes[0],shapes[1:] + rv, inner_shapes = shapes[0], shapes[1:] if inner_shapes: - inner_shapes = reduce(lambda a,b: a.fuse(b),inner_shapes) + inner_shapes = reduce(lambda a, b: a.fuse(b), inner_shapes) rv = rv.cut(inner_shapes) return rv @@ -1464,8 +1528,8 @@ def sweep_multi(cls, profiles, path, makeSolid=True, isFrenet=False): :param path: The wire to sweep the face resulting from the wires over :return: a Solid object """ - if path.ShapeType() == 'Edge': - path = Wire.assembleEdges([path, ]) + if path.ShapeType() == "Edge": + path = Wire.assembleEdges([path,]) builder = BRepOffsetAPI_MakePipeShell(path.wrapped) @@ -1480,8 +1544,7 @@ def sweep_multi(cls, profiles, path, makeSolid=True, isFrenet=False): return cls(builder.Shape()) - def dprism(self, basis, profiles, depth=None, taper=0, thruAll=True, - additive=True): + def dprism(self, basis, profiles, depth=None, taper=0, thruAll=True, additive=True): """ Make a prismatic feature (additive or subtractive) @@ -1496,13 +1559,10 @@ def dprism(self, basis, profiles, depth=None, taper=0, thruAll=True, shape = self.wrapped basis = basis.wrapped for p in sorted_profiles: - face = Face.makeFromWires(p[0],p[1:]) - feat = BRepFeat_MakeDPrism(shape, - face.wrapped, - basis, - taper*DEG2RAD, - additive, - False) + face = Face.makeFromWires(p[0], p[1:]) + feat = BRepFeat_MakeDPrism( + shape, face.wrapped, basis, taper * DEG2RAD, additive, False + ) if thruAll: feat.PerformThruAll() @@ -1513,21 +1573,22 @@ def dprism(self, basis, profiles, depth=None, taper=0, thruAll=True, return self.__class__(shape) + class Compound(Shape, Mixin3D): """ a collection of disconnected solids """ - + @staticmethod def _makeCompound(listOfShapes): - + comp = TopoDS_Compound() comp_builder = TopoDS_Builder() comp_builder.MakeCompound(comp) for s in listOfShapes: comp_builder.Add(comp, s) - + return comp @classmethod @@ -1539,39 +1600,51 @@ def makeCompound(cls, listOfShapes): return cls(cls._makeCompound((s.wrapped for s in listOfShapes))) @classmethod - def makeText(cls, text, size, height, font="Arial", kind='regular', - halign='center', valign='center',position=Plane.XY()): + def makeText( + cls, + text, + size, + height, + font="Arial", + kind="regular", + halign="center", + valign="center", + position=Plane.XY(), + ): """ Create a 3D text """ - font_kind = {'regular' : Font_FA_Regular, - 'bold' : Font_FA_Bold, - 'italic' : Font_FA_Italic}[kind] + font_kind = { + "regular": Font_FA_Regular, + "bold": Font_FA_Bold, + "italic": Font_FA_Italic, + }[kind] text_flat = Shape(text_to_brep(text, font, font_kind, size, False)) bb = text_flat.BoundingBox() - + t = Vector() - - if halign == 'center': - t.x = -bb.xlen/2 - elif halign == 'right': + + if halign == "center": + t.x = -bb.xlen / 2 + elif halign == "right": t.x = -bb.xlen - - if valign == 'center': - t.y = -bb.ylen/2 - elif valign == 'top': + + if valign == "center": + t.y = -bb.ylen / 2 + elif valign == "top": t.y = -bb.ylen - + text_flat = text_flat.translate(t) - - vecNormal = text_flat.Faces()[0].normalAt()*height + + vecNormal = text_flat.Faces()[0].normalAt() * height text_3d = BRepPrimAPI_MakePrism(text_flat.wrapped, vecNormal.wrapped) return cls(text_3d.Shape()).transformShape(position.rG) + def sortWiresByBuildOrder(wireList, result={}): """Tries to determine how wires should be combined into faces. @@ -1591,12 +1664,14 @@ def sortWiresByBuildOrder(wireList, result={}): # check if we have something to sort at all if len(wireList) < 2: - return [wireList, ] + return [ + wireList, + ] # make a Face, NB: this might return a compound of faces faces = Face.makeFromWires(wireList[0], wireList[1:]) - - rv = [] + + rv = [] for face in faces.Faces(): rv.append([face.outerWire(),] + face.innerWires()) diff --git a/cadquery/selectors.py b/cadquery/selectors.py index 8ead77374..f4eb361d1 100644 --- a/cadquery/selectors.py +++ b/cadquery/selectors.py @@ -21,9 +21,22 @@ 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 @@ def __init__(self, pnt): self.pnt = pnt def filter(self, objectList): - def dist(tShape): return tShape.Center().sub(Vector(*self.pnt)).Length # if tShape.ShapeType == 'Vertex': @@ -121,15 +133,18 @@ def filter(self, objectList): 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 @@ def filter(self, objectList): 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 @@ def __init__(self, vector, directionMax=True, tolerance=0.0001): 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 @@ def distance(tShape): 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 @@ def __init__(self, left, right): 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 @@ def __init__(self, parseResults): # define all token to object mappings self.axes = { - 'X': Vector(1, 0, 0), - 'Y': Vector(0, 1, 0), - 'Z': Vector(0, 0, 1), - 'XY': Vector(1, 1, 0), - 'YZ': Vector(0, 1, 1), - 'XZ': Vector(1, 0, 1) + "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 @@ def _chooseSelector(self, pr): """ 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 @@ def _getVector(self, pr): """ 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 not_callback(res): 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 @@ def __init__(self, selectorString): Feed the input string through the parser and construct an relevant complex selector object """ self.selectorString = selectorString - parse_result = _expression_grammar.parseString(selectorString, - parseAll=True) + parse_result = _expression_grammar.parseString(selectorString, parseAll=True) self.mySelector = parse_result.asList()[0] def filter(self, objectList): diff --git a/doc/conf.py b/doc/conf.py index fb76407e1..4e8517aa6 100644 --- a/doc/conf.py +++ b/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 @@ # 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 @@ ## "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 @@ 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 # " v 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 @@ # 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_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 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 @@ # (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' diff --git a/examples/Ex001_Simple_Block.py b/examples/Ex001_Simple_Block.py index f72445f45..8496d1038 100644 --- a/examples/Ex001_Simple_Block.py +++ b/examples/Ex001_Simple_Block.py @@ -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. diff --git a/examples/Ex002_Block_With_Bored_Center_Hole.py b/examples/Ex002_Block_With_Bored_Center_Hole.py index 6b00ffcdd..87ba3926c 100644 --- a/examples/Ex002_Block_With_Bored_Center_Hole.py +++ b/examples/Ex002_Block_With_Bored_Center_Hole.py @@ -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 @@ # 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) diff --git a/examples/Ex003_Pillow_Block_With_Counterbored_Holes.py b/examples/Ex003_Pillow_Block_With_Counterbored_Holes.py index 071dfd9a3..373478c5a 100644 --- a/examples/Ex003_Pillow_Block_With_Counterbored_Holes.py +++ b/examples/Ex003_Pillow_Block_With_Counterbored_Holes.py @@ -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 @@ # 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) diff --git a/examples/Ex004_Extruded_Cylindrical_Plate.py b/examples/Ex004_Extruded_Cylindrical_Plate.py index c9f308ff6..d287887ca 100644 --- a/examples/Ex004_Extruded_Cylindrical_Plate.py +++ b/examples/Ex004_Extruded_Cylindrical_Plate.py @@ -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 @@ # 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) diff --git a/examples/Ex005_Extruded_Lines_and_Arcs.py b/examples/Ex005_Extruded_Lines_and_Arcs.py index e213f31e9..c9fda2af4 100644 --- a/examples/Ex005_Extruded_Lines_and_Arcs.py +++ b/examples/Ex005_Extruded_Lines_and_Arcs.py @@ -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 @@ # 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) diff --git a/examples/Ex006_Moving_the_Current_Working_Point.py b/examples/Ex006_Moving_the_Current_Working_Point.py index 3c26121af..b1337538c 100644 --- a/examples/Ex006_Moving_the_Current_Working_Point.py +++ b/examples/Ex006_Moving_the_Current_Working_Point.py @@ -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. diff --git a/examples/Ex007_Using_Point_Lists.py b/examples/Ex007_Using_Point_Lists.py index d824c750f..ad577fcd3 100644 --- a/examples/Ex007_Using_Point_Lists.py +++ b/examples/Ex007_Using_Point_Lists.py @@ -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. diff --git a/examples/Ex008_Polygon_Creation.py b/examples/Ex008_Polygon_Creation.py index 2853c1e06..43f8eae9c 100644 --- a/examples/Ex008_Polygon_Creation.py +++ b/examples/Ex008_Polygon_Creation.py @@ -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 @@ # 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) diff --git a/examples/Ex009_Polylines.py b/examples/Ex009_Polylines.py index 8712bf99f..70f83e87d 100644 --- a/examples/Ex009_Polylines.py +++ b/examples/Ex009_Polylines.py @@ -6,13 +6,13 @@ # 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 @@ # 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) diff --git a/examples/Ex010_Defining_an_Edge_with_a_Spline.py b/examples/Ex010_Defining_an_Edge_with_a_Spline.py index 8b4c67cb4..57b063288 100644 --- a/examples/Ex010_Defining_an_Edge_with_a_Spline.py +++ b/examples/Ex010_Defining_an_Edge_with_a_Spline.py @@ -13,7 +13,7 @@ (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 diff --git a/examples/Ex015_Rotated_Workplanes.py b/examples/Ex015_Rotated_Workplanes.py index 13a9a8e68..602b4cb02 100644 --- a/examples/Ex015_Rotated_Workplanes.py +++ b/examples/Ex015_Rotated_Workplanes.py @@ -13,10 +13,16 @@ # 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) diff --git a/examples/Ex016_Using_Construction_Geometry.py b/examples/Ex016_Using_Construction_Geometry.py index aa92a0c4f..37e653f89 100644 --- a/examples/Ex016_Using_Construction_Geometry.py +++ b/examples/Ex016_Using_Construction_Geometry.py @@ -12,10 +12,15 @@ # 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) diff --git a/examples/Ex018_Making_Lofts.py b/examples/Ex018_Making_Lofts.py index f5ae39b83..2106d99fe 100644 --- a/examples/Ex018_Making_Lofts.py +++ b/examples/Ex018_Making_Lofts.py @@ -11,10 +11,15 @@ # 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) diff --git a/examples/Ex019_Counter_Sunk_Holes.py b/examples/Ex019_Counter_Sunk_Holes.py index 5ca39c625..e6a8d84c8 100644 --- a/examples/Ex019_Counter_Sunk_Holes.py +++ b/examples/Ex019_Counter_Sunk_Holes.py @@ -11,9 +11,15 @@ # 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) diff --git a/examples/Ex021_Splitting_an_Object.py b/examples/Ex021_Splitting_an_Object.py index 5f878bf69..bdd217b4b 100644 --- a/examples/Ex021_Splitting_an_Object.py +++ b/examples/Ex021_Splitting_an_Object.py @@ -9,8 +9,7 @@ # 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. diff --git a/examples/Ex022_Revolution.py b/examples/Ex022_Revolution.py index c5f31070d..eb4debb84 100644 --- a/examples/Ex022_Revolution.py +++ b/examples/Ex022_Revolution.py @@ -9,13 +9,13 @@ # 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) diff --git a/examples/Ex023_Sweep.py b/examples/Ex023_Sweep.py index c2ba5017a..c5b016074 100644 --- a/examples/Ex023_Sweep.py +++ b/examples/Ex023_Sweep.py @@ -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) @@ -37,4 +33,4 @@ show_object(frenetShell.translate((5, 0, 0))) show_object(defaultRect.translate((10, 0, 0))) show_object(plineSweep.translate((15, 0, 0))) -show_object(arcSweep.translate((20, 0, 0))) \ No newline at end of file +show_object(arcSweep.translate((20, 0, 0))) diff --git a/examples/Ex024_Sweep_With_Multiple_Sections.py b/examples/Ex024_Sweep_With_Multiple_Sections.py index 0c76f93db..5c4cd0058 100644 --- a/examples/Ex024_Sweep_With_Multiple_Sections.py +++ b/examples/Ex024_Sweep_With_Multiple_Sections.py @@ -4,37 +4,80 @@ 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(recttocircleSweep.translate((0, 10, 0))) show_object(specialSweep.translate((0, 15, 0))) show_object(arcSweep.translate((0, -5, 0))) - - diff --git a/examples/Ex100_Lego_Brick.py b/examples/Ex100_Lego_Brick.py index e0981255f..ca2fae617 100644 --- a/examples/Ex100_Lego_Brick.py +++ b/examples/Ex100_Lego_Brick.py @@ -4,8 +4,8 @@ ##### # 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 @@ 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 = 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(" 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 diff --git a/setup.py b/setup.py index 3faff965d..48f712e4b 100644 --- a/setup.py +++ b/setup.py @@ -15,43 +15,48 @@ 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", + ], ) diff --git a/tests/__init__.py b/tests/__init__.py index 2c0ca15d2..b36542e9e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,21 +6,23 @@ 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", ] diff --git a/tests/test_cad_objects.py b/tests/test_cad_objects.py index 53c432fe2..c556678a6 100644 --- a/tests/test_cad_objects.py +++ b/tests/test_cad_objects.py @@ -3,9 +3,11 @@ 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,36 +15,34 @@ 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) - + 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 @@ def testEdgeWrapperCenter(self): 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 @@ def _cyl(pnt): 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 @@ def testVectorOperators(self): 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 @@ def testVectorProject(self): # 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 @@ def matrix_vals(m): 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 @@ def matrix_vals(m): 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 @@ def matrix_vals(m): 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 @@ def testTranslate(self): 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() diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 01562c6e9..46dfc4121 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -3,7 +3,7 @@ """ # system modules -import math,os.path,time,tempfile +import math, os.path, time, tempfile from random import choice from random import random from random import randrange @@ -11,7 +11,14 @@ # my modules from cadquery import * from cadquery import exporters -from tests import BaseTest, writeStringToFile, makeUnitCube, readFileAsString, makeUnitSquareWire, makeCube +from tests import ( + BaseTest, + writeStringToFile, + makeUnitCube, + readFileAsString, + makeUnitSquareWire, + makeCube, +) # where unit test output will be saved OUTDIR = tempfile.gettempdir() @@ -45,7 +52,6 @@ class TestCadQuery(BaseTest): - def tearDown(self): """ Update summary with data from this test. @@ -61,12 +67,15 @@ def tearDown(self): existingSummary = readFileAsString(SUMMARY_FILE) svgText = readFileAsString(svgFile) svgText = svgText.replace( - '', "") + '', "" + ) # now write data into the file # the content we are replacing it with also includes the marker, so it can be replaced again - existingSummary = existingSummary.replace("", TEST_RESULT_TEMPLATE % ( - dict(svg=svgText, name=self._testMethodName))) + existingSummary = existingSummary.replace( + "", + TEST_RESULT_TEMPLATE % (dict(svg=svgText, name=self._testMethodName)), + ) writeStringToFile(existingSummary, SUMMARY_FILE) @@ -82,25 +91,27 @@ def testToOCC(self): """ Tests to make sure that a CadQuery object is converted correctly to a OCC object. """ - r = Workplane('XY').rect(5, 5).extrude(5) + r = Workplane("XY").rect(5, 5).extrude(5) r = r.toOCC() import OCC.Core as OCC + self.assertEqual(type(r), OCC.TopoDS.TopoDS_Compound) def testToSVG(self): """ Tests to make sure that a CadQuery object is converted correctly to SVG """ - r = Workplane('XY').rect(5, 5).extrude(5) + r = Workplane("XY").rect(5, 5).extrude(5) r_str = r.toSvg() # Make sure that a couple of sections from the SVG output make sense self.assertTrue(r_str.index('path d="M') > 0) - self.assertTrue(r_str.index( - 'line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3"') > 0) + self.assertTrue( + r_str.index('line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3"') > 0 + ) def testCubePlugin(self): """ @@ -126,8 +137,13 @@ def _singleCube(pnt): Workplane.makeCubes = makeCubes # call it - result = Workplane("XY").box(6.0, 8.0, 0.5).faces( - ">Z").rect(4.0, 4.0, forConstruction=True).vertices() + result = ( + Workplane("XY") + .box(6.0, 8.0, 0.5) + .faces(">Z") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + ) result = result.makeCubes(1.0) result = result.combineSolids() self.saveModel(result) @@ -143,7 +159,6 @@ def testCylinderPlugin(self): """ def cylinders(self, radius, height): - def _cyl(pnt): # inner function to build a cylinder return Solid.makeCylinder(radius, height, pnt) @@ -151,11 +166,16 @@ def _cyl(pnt): # combine all the cylinders into a single compound r = self.eachpoint(_cyl, True).combineSolids() return r + Workplane.cyl = cylinders # now test. here we want weird workplane to see if the objects are transformed right - s = Workplane(Plane(Vector((0, 0, 0)), Vector((1, -1, 0)), Vector((1, 1, 0)))).rect(2.0, 3.0, forConstruction=True).vertices() \ + s = ( + Workplane(Plane(Vector((0, 0, 0)), Vector((1, -1, 0)), Vector((1, 1, 0)))) + .rect(2.0, 3.0, forConstruction=True) + .vertices() .cyl(0.25, 0.5) + ) self.assertEqual(4, s.solids().size()) self.saveModel(s) @@ -168,22 +188,35 @@ def testPolygonPlugin(self): """ def rPoly(self, nSides, diameter): - 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) return self.eachpoint(_makePolygon, True) Workplane.rPoly = rPoly - s = Workplane("XY").box(4.0, 4.0, 0.25).faces(">Z").workplane().rect(2.0, 2.0, forConstruction=True).vertices()\ - .rPoly(5, 0.5).cutThruAll() + s = ( + Workplane("XY") + .box(4.0, 4.0, 0.25) + .faces(">Z") + .workplane() + .rect(2.0, 2.0, forConstruction=True) + .vertices() + .rPoly(5, 0.5) + .cutThruAll() + ) # 6 base sides, 4 pentagons, 5 sides each = 26 self.assertEqual(26, s.faces().size()) @@ -195,8 +228,7 @@ def testPointList(self): """ c = CQ(makeUnitCube()) - s = c.faces(">Z").workplane().pushPoints( - [(-0.3, 0.3), (0.3, 0.3), (0, 0)]) + s = c.faces(">Z").workplane().pushPoints([(-0.3, 0.3), (0.3, 0.3), (0, 0)]) self.assertEqual(3, s.size()) # TODO: is the ability to iterate over points with circle really worth it? # maybe we should just require using all() and a loop for this. the semantics and @@ -209,7 +241,7 @@ def testPointList(self): def callback_fn(pnt): self.assertEqual((0.0, 0.0), (pnt.x, pnt.y)) - r = Workplane('XY') + r = Workplane("XY") r.objects = [] r.eachpoint(callback_fn) @@ -237,10 +269,8 @@ def testRotate(self): """Test solid rotation at the CQ object level.""" box = Workplane("XY").box(1, 1, 5) box.rotate((0, 0, 0), (1, 0, 0), 90) - startPoint = box.faces("Z").circle(1.5)\ - .workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True) + s = ( + 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) + ) self.saveModel(s) - #self.assertEqual(1,s.solids().size() ) - #self.assertEqual(8,s.faces().size() ) + # self.assertEqual(1,s.solids().size() ) + # self.assertEqual(8,s.faces().size() ) def testRevolveCylinder(self): """ @@ -319,56 +355,81 @@ def testRevolveCylinder(self): angle_degrees = 360.0 # Test revolve without any options for making a cylinder - result = Workplane("XY").rect( - rectangle_width, rectangle_length, False).revolve() + result = ( + Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve() + ) self.assertEqual(3, result.faces().size()) self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) # Test revolve when only setting the angle to revolve through - result = Workplane("XY").rect( - rectangle_width, rectangle_length, False).revolve(angle_degrees) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length, False) + .revolve(angle_degrees) + ) self.assertEqual(3, result.faces().size()) self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect( - rectangle_width, rectangle_length, False).revolve(270.0) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length, False) + .revolve(270.0) + ) self.assertEqual(5, result.faces().size()) self.assertEqual(6, result.vertices().size()) self.assertEqual(9, result.edges().size()) # Test when passing revolve the angle and the axis of revolution's start point - result = Workplane("XY").rect( - rectangle_width, rectangle_length).revolve(angle_degrees, (-5, -5)) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(angle_degrees, (-5, -5)) + ) self.assertEqual(3, result.faces().size()) self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect( - rectangle_width, rectangle_length).revolve(270.0, (-5, -5)) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(270.0, (-5, -5)) + ) self.assertEqual(5, result.faces().size()) self.assertEqual(6, result.vertices().size()) self.assertEqual(9, result.edges().size()) # Test when passing revolve the angle and both the start and ends of the axis of revolution - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve( - angle_degrees, (-5, -5), (-5, 5)) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(angle_degrees, (-5, -5), (-5, 5)) + ) self.assertEqual(3, result.faces().size()) self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect( - rectangle_width, rectangle_length).revolve(270.0, (-5, -5), (-5, 5)) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(270.0, (-5, -5), (-5, 5)) + ) self.assertEqual(5, result.faces().size()) self.assertEqual(6, result.vertices().size()) self.assertEqual(9, result.edges().size()) # Testing all of the above without combine - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve( - angle_degrees, (-5, -5), (-5, 5), False) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(angle_degrees, (-5, -5), (-5, 5), False) + ) self.assertEqual(3, result.faces().size()) self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve( - 270.0, (-5, -5), (-5, 5), False) + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length) + .revolve(270.0, (-5, -5), (-5, 5), False) + ) self.assertEqual(5, result.faces().size()) self.assertEqual(6, result.vertices().size()) self.assertEqual(9, result.edges().size()) @@ -384,8 +445,11 @@ def testRevolveDonut(self): rectangle_length = 10.0 angle_degrees = 360.0 - result = Workplane("XY").rect(rectangle_width, rectangle_length, True)\ + result = ( + Workplane("XY") + .rect(rectangle_width, rectangle_length, True) .revolve(angle_degrees, (20, 0), (20, 10)) + ) self.assertEqual(4, result.faces().size()) self.assertEqual(4, result.vertices().size()) self.assertEqual(6, result.edges().size()) @@ -404,18 +468,13 @@ def testSpline(self): """ Tests construction of splines """ - pts = [ - (0, 0), - (0, 1), - (1, 2), - (2, 4) - ] + pts = [(0, 0), (0, 1), (1, 2), (2, 4)] # Spline path - just a smoke test path = Workplane("XZ").spline(pts).val() # Closed spline - path_closed = Workplane("XZ").spline(pts,periodic=True).val() + path_closed = Workplane("XZ").spline(pts, periodic=True).val() self.assertTrue(path_closed.IsClosed()) # attempt to build a valid face @@ -429,24 +488,19 @@ def testSpline(self): self.assertFalse(f.isValid()) # Spline with explicit tangents - path_const = Workplane("XZ").spline(pts,tangents=((0,1),(1,0))).val() + path_const = Workplane("XZ").spline(pts, tangents=((0, 1), (1, 0))).val() self.assertFalse(path.tangentAt(0) == path_const.tangentAt(0)) self.assertFalse(path.tangentAt(1) == path_const.tangentAt(1)) - + # test include current - path1 = Workplane("XZ").spline(pts[1:],includeCurrent=True).val() - self.assertAlmostEqual(path.Length(),path1.Length()) + path1 = Workplane("XZ").spline(pts[1:], includeCurrent=True).val() + self.assertAlmostEqual(path.Length(), path1.Length()) def testSweep(self): """ Tests the operation of sweeping a wire(s) along a path """ - pts = [ - (0, 0), - (0, 1), - (1, 2), - (2, 4) - ] + pts = [(0, 0), (0, 1), (1, 2), (2, 4)] # Spline path path = Workplane("XZ").spline(pts) @@ -467,8 +521,7 @@ def testSweep(self): self.assertEqual(3, result.edges().size()) # Test with makeSolid False and isFrenet True - result = Workplane("XY").circle(1.0).sweep( - path, makeSolid=False, isFrenet=True) + result = Workplane("XY").circle(1.0).sweep(path, makeSolid=False, isFrenet=True) self.assertEqual(1, result.faces().size()) self.assertEqual(3, result.edges().size()) @@ -481,7 +534,7 @@ def testSweep(self): path = Workplane("XZ").polyline(pts) # Test defaults - result = Workplane("XY").circle(0.1).sweep(path,transition='transformed') + result = Workplane("XY").circle(0.1).sweep(path, transition="transformed") self.assertEqual(5, result.faces().size()) self.assertEqual(7, result.edges().size()) @@ -489,24 +542,40 @@ def testSweep(self): path = Workplane("XZ").polyline(pts) # Test defaults - result = Workplane("XY").circle(0.2).circle(0.1).sweep(path,transition='transformed') + result = ( + Workplane("XY") + .circle(0.2) + .circle(0.1) + .sweep(path, transition="transformed") + ) self.assertEqual(8, result.faces().size()) self.assertEqual(14, result.edges().size()) # Polyline path and different transition settings - for t in ('transformed','right','round'): + for t in ("transformed", "right", "round"): path = Workplane("XZ").polyline(pts) - result = Workplane("XY").circle(0.2).rect(0.2,0.1).rect(0.1,0.2)\ - .sweep(path,transition=t) + result = ( + Workplane("XY") + .circle(0.2) + .rect(0.2, 0.1) + .rect(0.1, 0.2) + .sweep(path, transition=t) + ) self.assertTrue(result.solids().val().isValid()) # Polyline path and multiple inner profiles path = Workplane("XZ").polyline(pts) # Test defaults - result = Workplane("XY").circle(0.2).rect(0.2,0.1).rect(0.1,0.2)\ - .circle(0.1).sweep(path) + result = ( + Workplane("XY") + .circle(0.2) + .rect(0.2, 0.1) + .rect(0.1, 0.2) + .circle(0.1) + .sweep(path) + ) self.assertTrue(result.solids().val().isValid()) # Arc path @@ -526,36 +595,79 @@ def testMultisectionSweep(self): path = 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 = 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 = ( + 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 = 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 = 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) + recttocircleSweep = ( + 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 = ( + 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 = Workplane("YZ").circle(1.0).workplane(offset=10.0).rect(2.0, 2.0). \ - sweep(path, multisection=True) + specialSweep = ( + 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 = Workplane("XZ").moveTo(-5, 4).lineTo(0, 4). \ - threePointArc((4, 0), (0, -4)).lineTo(-5, -4) + path = ( + 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 = 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 = ( + 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) + ) # Test and saveModel self.assertEqual(1, defaultSweep.solids().size()) @@ -569,7 +681,7 @@ def testTwistExtrude(self): """ Tests extrusion while twisting through an angle. """ - profile = Workplane('XY').rect(10, 10) + profile = Workplane("XY").rect(10, 10) r = profile.twistExtrude(10, 45, False) self.assertEqual(6, r.faces().size()) @@ -578,7 +690,7 @@ def testTwistExtrudeCombine(self): """ Tests extrusion while twisting through an angle, combining with other solids. """ - profile = Workplane('XY').rect(10, 10) + profile = Workplane("XY").rect(10, 10) r = profile.twistExtrude(10, 45) self.assertEqual(6, r.faces().size()) @@ -586,9 +698,16 @@ def testTwistExtrudeCombine(self): def testRectArray(self): NUMX = 3 NUMY = 3 - s = Workplane("XY").box(40, 40, 5, centered=(True, True, True)).faces( - ">Z").workplane().rarray(8.0, 8.0, NUMX, NUMY, True).circle(2.0).extrude(2.0) - #s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().circle(2.0).extrude(2.0) + s = ( + Workplane("XY") + .box(40, 40, 5, centered=(True, True, True)) + .faces(">Z") + .workplane() + .rarray(8.0, 8.0, NUMX, NUMY, True) + .circle(2.0) + .extrude(2.0) + ) + # s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().circle(2.0).extrude(2.0) self.saveModel(s) # 6 faces for the box, 2 faces for each cylinder self.assertEqual(6 + NUMX * NUMY * 2, s.faces().size()) @@ -623,8 +742,14 @@ def testPolarArray(self): self.assertAlmostEqual(radius, s.objects[0].y) def testNestedCircle(self): - s = Workplane("XY").box(40, 40, 5).pushPoints( - [(10, 0), (0, 10)]).circle(4).circle(2).extrude(4) + s = ( + Workplane("XY") + .box(40, 40, 5) + .pushPoints([(10, 0), (0, 10)]) + .circle(4) + .circle(2) + .extrude(4) + ) self.saveModel(s) self.assertEqual(14, s.faces().size()) @@ -651,8 +776,13 @@ def testLegoBrick(self): # build the brick s = Workplane("XY").box(total_length, total_width, H) # make the base s = s.faces("Z").workplane().rarray(P, P, lbumps, wbumps, True).circle( - bumpDiam / 2.0).extrude(1.8) # make the bumps on the top + s = ( + s.faces(">Z") + .workplane() + .rarray(P, P, lbumps, wbumps, True) + .circle(bumpDiam / 2.0) + .extrude(1.8) + ) # make the bumps on the top # add posts on the bottom. posts are different diameter depending on geometry # solid studs for 1 bump, tubes for multiple, none for 1x1 @@ -660,32 +790,40 @@ def testLegoBrick(self): tmp = s.faces(" 1 and wbumps > 1: - tmp = tmp.rarray(P, P, lbumps - 1, wbumps - 1, center=True).circle( - postDiam / 2.0).circle(bumpDiam / 2.0).extrude(H - t) + tmp = ( + tmp.rarray(P, P, lbumps - 1, wbumps - 1, center=True) + .circle(postDiam / 2.0) + .circle(bumpDiam / 2.0) + .extrude(H - t) + ) elif lbumps > 1: - tmp = tmp.rarray(P, P, lbumps - 1, 1, - center=True).circle(t).extrude(H - t) + tmp = tmp.rarray(P, P, lbumps - 1, 1, center=True).circle(t).extrude(H - t) elif wbumps > 1: - tmp = tmp.rarray(P, P, 1, wbumps - 1, - center=True).circle(t).extrude(H - t) + tmp = tmp.rarray(P, P, 1, wbumps - 1, center=True).circle(t).extrude(H - t) self.saveModel(s) def testAngledHoles(self): - s = Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane().transformed(offset=Vector(0, -1.5, 1.0), rotate=Vector(60, 0, 0))\ - .rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25) + s = ( + Workplane("front") + .box(4.0, 4.0, 0.25) + .faces(">Z") + .workplane() + .transformed(offset=Vector(0, -1.5, 1.0), rotate=Vector(60, 0, 0)) + .rect(1.5, 1.5, forConstruction=True) + .vertices() + .hole(0.25) + ) self.saveModel(s) self.assertEqual(10, s.faces().size()) def testTranslateSolid(self): c = CQ(makeUnitCube()) - self.assertAlmostEqual(0.0, c.faces( - "Z').workplane().circle(0.125).extrude( - 0.5, True) # make a boss, not updating the original + r = ( + c.faces(">Z").workplane().circle(0.125).extrude(0.5, True) + ) # make a boss, not updating the original self.assertEqual(8, r.faces().size()) # just the boss faces self.assertEqual(6, c.faces().size()) # original is not modified @@ -737,9 +876,14 @@ def testSimpleWorkplane(self): A simple square part with a hole in it """ s = Workplane(Plane.XY()) - r = s.rect(2.0, 2.0).extrude(0.5)\ - .faces(">Z").workplane()\ - .circle(0.25).cutBlind(-1.0) + r = ( + s.rect(2.0, 2.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .circle(0.25) + .cutBlind(-1.0) + ) self.saveModel(r) self.assertEqual(7, r.faces().size()) @@ -749,13 +893,12 @@ def testMultiFaceWorkplane(self): Test Creation of workplane from multiple co-planar face selection. """ - s = Workplane('XY').box(1, 1, 1).faces( - '>Z').rect(1, 0.5).cutBlind(-0.2) + s = Workplane("XY").box(1, 1, 1).faces(">Z").rect(1, 0.5).cutBlind(-0.2) - w = s.faces('>Z').workplane() + w = s.faces(">Z").workplane() o = w.objects[0] # origin of the workplane - self.assertAlmostEqual(o.x, 0., 3) - self.assertAlmostEqual(o.y, 0., 3) + self.assertAlmostEqual(o.x, 0.0, 3) + self.assertAlmostEqual(o.y, 0.0, 3) self.assertAlmostEqual(o.z, 0.5, 3) def testTriangularPrism(self): @@ -779,8 +922,13 @@ def testConstructionWire(self): also tests using a workplane plane other than XY """ s = Workplane(Plane.YZ()) - r = s.rect(2.0, 2.0).rect( - 1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5) + r = ( + s.rect(2.0, 2.0) + .rect(1.3, 1.3, forConstruction=True) + .vertices() + .circle(0.125) + .extrude(0.5) + ) self.saveModel(r) # 10 faces-- 6 plus 4 holes, the vertices of the second rect. self.assertEqual(10, r.faces().size()) @@ -798,8 +946,13 @@ def testTwoWorkplanes(self): # r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices() # for c in r.all(): # c.circle(0.125).extrude(0.5,True) - r = s.rect(2.0, 2.0).rect( - 1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5) + r = ( + s.rect(2.0, 2.0) + .rect(1.3, 1.3, forConstruction=True) + .vertices() + .circle(0.125) + .extrude(0.5) + ) # side hole, blind deep 1.9 t = r.faces(">Y").workplane().circle(0.125).cutBlind(-1.9) @@ -829,43 +982,45 @@ def testIntersect(self): resS = currentS.intersect(toIntersect.val()) self.assertEqual(6, resS.faces().size()) - self.assertAlmostEqual(resS.val().Volume(),0.5) + self.assertAlmostEqual(resS.val().Volume(), 0.5) resS = currentS.intersect(toIntersect) self.assertEqual(6, resS.faces().size()) - self.assertAlmostEqual(resS.val().Volume(),0.5) + self.assertAlmostEqual(resS.val().Volume(), 0.5) def testBoundingBox(self): """ Tests the boudingbox center of a model """ - result0 = (Workplane("XY") - .moveTo(10, 0) - .lineTo(5, 0) - .threePointArc((3.9393, 0.4393), (3.5, 1.5)) - .threePointArc((3.0607, 2.5607), (2, 3)) - .lineTo(1.5, 3) - .threePointArc((0.4393, 3.4393), (0, 4.5)) - .lineTo(0, 13.5) - .threePointArc((0.4393, 14.5607), (1.5, 15)) - .lineTo(28, 15) - .lineTo(28, 13.5) - .lineTo(24, 13.5) - .lineTo(24, 11.5) - .lineTo(27, 11.5) - .lineTo(27, 10) - .lineTo(22, 10) - .lineTo(22, 13.2) - .lineTo(14.5, 13.2) - .lineTo(14.5, 10) - .lineTo(12.5, 10) - .lineTo(12.5, 13.2) - .lineTo(5.5, 13.2) - .lineTo(5.5, 2) - .threePointArc((5.793, 1.293), (6.5, 1)) - .lineTo(10, 1) - .close()) + result0 = ( + Workplane("XY") + .moveTo(10, 0) + .lineTo(5, 0) + .threePointArc((3.9393, 0.4393), (3.5, 1.5)) + .threePointArc((3.0607, 2.5607), (2, 3)) + .lineTo(1.5, 3) + .threePointArc((0.4393, 3.4393), (0, 4.5)) + .lineTo(0, 13.5) + .threePointArc((0.4393, 14.5607), (1.5, 15)) + .lineTo(28, 15) + .lineTo(28, 13.5) + .lineTo(24, 13.5) + .lineTo(24, 11.5) + .lineTo(27, 11.5) + .lineTo(27, 10) + .lineTo(22, 10) + .lineTo(22, 13.2) + .lineTo(14.5, 13.2) + .lineTo(14.5, 10) + .lineTo(12.5, 10) + .lineTo(12.5, 13.2) + .lineTo(5.5, 13.2) + .lineTo(5.5, 2) + .threePointArc((5.793, 1.293), (6.5, 1)) + .lineTo(10, 1) + .close() + ) result = result0.extrude(100) bb_center = result.val().BoundingBox().center self.saveModel(result) @@ -888,9 +1043,13 @@ def testCutThroughAll(self): """ # base block s = Workplane(Plane.XY()) - r = s.rect(2.0, 2.0).rect( - 1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5) - + r = ( + s.rect(2.0, 2.0) + .rect(1.3, 1.3, forConstruction=True) + .vertices() + .circle(0.125) + .extrude(0.5) + ) # thru all without explicit face selection t = r.circle(0.5).cutThruAll() @@ -907,31 +1066,47 @@ def testCutToFaceOffsetNOTIMPLEMENTEDYET(self): """ # base block s = Workplane(Plane.XY()) - r = s.rect(2.0, 2.0).rect( - 1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5) + r = ( + s.rect(2.0, 2.0) + .rect(1.3, 1.3, forConstruction=True) + .vertices() + .circle(0.125) + .extrude(0.5) + ) # side hole, up to 0.1 from the last face try: - t = r.faces(">Y").workplane().circle( - 0.125).cutToOffsetFromFace(r.faces().mminDist(Dir.Y), 0.1) + t = ( + r.faces(">Y") + .workplane() + .circle(0.125) + .cutToOffsetFromFace(r.faces().mminDist(Dir.Y), 0.1) + ) # should end up being a blind hole self.assertEqual(10, t.faces().size()) - t.first().val().exportStep('c:/temp/testCutToFace.STEP') + t.first().val().exportStep("c:/temp/testCutToFace.STEP") except: pass # Not Implemented Yet def testWorkplaneOnExistingSolid(self): "Tests extruding on an existing solid" - c = CQ(makeUnitCube()).faces(">Z").workplane().circle( - 0.25).circle(0.125).extrude(0.25) + c = ( + CQ(makeUnitCube()) + .faces(">Z") + .workplane() + .circle(0.25) + .circle(0.125) + .extrude(0.25) + ) self.saveModel(c) self.assertEqual(10, c.faces().size()) def testWorkplaneCenterMove(self): # this workplane is centered at x=0.5,y=0.5, the center of the upper face - s = Workplane("XY").box(1, 1, 1).faces(">Z").workplane( - ).center(-0.5, -0.5) # move the center to the corner + s = ( + Workplane("XY").box(1, 1, 1).faces(">Z").workplane().center(-0.5, -0.5) + ) # move the center to the corner t = s.circle(0.25).extrude(0.2) # make a boss self.assertEqual(9, t.faces().size()) @@ -946,7 +1121,7 @@ def testBasicLines(self): # most users dont understand what a wire is, they are just drawing r = s.lineTo(1.0, 0).lineTo(0, 1.0).close().wire().extrude(0.25) - r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesStep1.STEP')) + r.val().exportStep(os.path.join(OUTDIR, "testBasicLinesStep1.STEP")) # no faces on the original workplane self.assertEqual(0, s.faces().size()) @@ -956,12 +1131,12 @@ def testBasicLines(self): # now add a circle through a side face r1 = r.faces("+XY").workplane().circle(0.08).cutThruAll() self.assertEqual(6, r1.faces().size()) - r1.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesXY.STEP')) + r1.val().exportStep(os.path.join(OUTDIR, "testBasicLinesXY.STEP")) # now add a circle through a top r2 = r1.faces("+Z").workplane().circle(0.08).cutThruAll() self.assertEqual(9, r2.faces().size()) - r2.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesZ.STEP')) + r2.val().exportStep(os.path.join(OUTDIR, "testBasicLinesZ.STEP")) self.saveModel(r2) @@ -970,17 +1145,19 @@ def test2DDrawing(self): Draw things like 2D lines and arcs, should be expanded later to include all 2D constructs """ s = Workplane(Plane.XY()) - r = s.lineTo(1.0, 0.0) \ - .lineTo(1.0, 1.0) \ - .threePointArc((1.0, 1.5), (0.0, 1.0)) \ - .lineTo(0.0, 0.0) \ - .moveTo(1.0, 0.0) \ - .lineTo(2.0, 0.0) \ - .lineTo(2.0, 2.0) \ - .threePointArc((2.0, 2.5), (0.0, 2.0)) \ - .lineTo(-2.0, 2.0) \ - .lineTo(-2.0, 0.0) \ - .close() + r = ( + s.lineTo(1.0, 0.0) + .lineTo(1.0, 1.0) + .threePointArc((1.0, 1.5), (0.0, 1.0)) + .lineTo(0.0, 0.0) + .moveTo(1.0, 0.0) + .lineTo(2.0, 0.0) + .lineTo(2.0, 2.0) + .threePointArc((2.0, 2.5), (0.0, 2.0)) + .lineTo(-2.0, 2.0) + .lineTo(-2.0, 0.0) + .close() + ) self.assertEqual(1, r.wires().size()) @@ -1004,12 +1181,19 @@ def test2DDrawing(self): self.assertEqual(1, r.wire().size()) self.assertEqual(4, r.edges().size()) - self.assertEqual((1.0, 1.0), - (r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0))) - .first().val().X, - r.vertices( - selectors.NearestToPointSelector((0.0, 0.0, 0.0))) - .first().val().Y)) + self.assertEqual( + (1.0, 1.0), + ( + r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0))) + .first() + .val() + .X, + r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0))) + .first() + .val() + .Y, + ), + ) # Test the sagittaArc and radiusArc functions a1 = Workplane(Plane.YZ()).threePointArc((5, 1), (10, 0)) @@ -1017,13 +1201,13 @@ def test2DDrawing(self): a3 = Workplane(Plane.YZ()).threePointArc((6, 2), (12, 0)) a4 = Workplane(Plane.YZ()).radiusArc((12, 0), -10) - assert(a1.edges().first().val().geomType() == "CIRCLE") - assert(a2.edges().first().val().geomType() == "CIRCLE") - assert(a3.edges().first().val().geomType() == "CIRCLE") - assert(a4.edges().first().val().geomType() == "CIRCLE") + assert a1.edges().first().val().geomType() == "CIRCLE" + assert a2.edges().first().val().geomType() == "CIRCLE" + assert a3.edges().first().val().geomType() == "CIRCLE" + assert a4.edges().first().val().geomType() == "CIRCLE" - assert(a1.edges().first().val().Length() == a2.edges().first().val().Length()) - assert(a3.edges().first().val().Length() == a4.edges().first().val().Length()) + assert a1.edges().first().val().Length() == a2.edges().first().val().Length() + assert a3.edges().first().val().Length() == a4.edges().first().val().Length() def testPolarLines(self): """ @@ -1032,11 +1216,13 @@ def testPolarLines(self): # Test the PolarLine* functions s = Workplane(Plane.XY()) - r = s.polarLine(10, 45) \ - .polarLineTo(10, -45) \ - .polarLine(10, -180) \ - .polarLine(-10, -90) \ + r = ( + s.polarLine(10, 45) + .polarLineTo(10, -45) + .polarLine(10, -180) + .polarLine(-10, -90) .close() + ) # a single wire, 5 edges self.assertEqual(1, r.wires().size()) @@ -1046,12 +1232,12 @@ def testLargestDimension(self): """ Tests the largestDimension function when no solids are on the stack and when there are """ - r = Workplane('XY').box(1, 1, 1) + r = Workplane("XY").box(1, 1, 1) dim = r.largestDimension() self.assertAlmostEqual(8.7, dim, 1) - r = Workplane('XY') + r = Workplane("XY") dim = r.largestDimension() self.assertEqual(-1, dim) @@ -1067,12 +1253,19 @@ def testOccBottle(self): s = Workplane(Plane.XY()) # draw half the profile of the bottle - p = s.center(-L / 2.0, 0).vLine(w / 2.0).threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0)).vLine(-w / 2.0).mirrorX()\ + p = ( + s.center(-L / 2.0, 0) + .vLine(w / 2.0) + .threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0)) + .vLine(-w / 2.0) + .mirrorX() .extrude(30.0, True) + ) # make the neck p.faces(">Z").workplane().circle(3.0).extrude( - 2.0, True) # .edges().fillet(0.05) + 2.0, True + ) # .edges().fillet(0.05) # make a shell p.faces(">Z").shell(0.3) @@ -1090,7 +1283,7 @@ def testSplineShape(self): (1.5, 1.0), (1.0, 1.25), (0.5, 1.0), - (0, 1.0) + (0, 1.0), ] r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts).close() r = r.extrude(0.5) @@ -1100,8 +1293,13 @@ def testSimpleMirror(self): """ Tests a simple mirroring operation """ - s = Workplane("XY").lineTo(2, 2).threePointArc((3, 1), (2, 0)) \ - .mirrorX().extrude(0.25) + s = ( + Workplane("XY") + .lineTo(2, 2) + .threePointArc((3, 1), (2, 0)) + .mirrorX() + .extrude(0.25) + ) self.assertEqual(6, s.faces().size()) self.saveModel(s) @@ -1123,7 +1321,7 @@ def testUnorderedMirror(self): (r / 2, s / 2), (r / 2 - t, s / 2), (r / 2 - t, r / 2 - 1.5 * t), - (t / 2, 0) + (t / 2, 0), ] r = Workplane("XY").polyline(points).mirrorX() @@ -1131,13 +1329,12 @@ def testUnorderedMirror(self): self.assertEqual(1, r.wires().size()) self.assertEqual(18, r.edges().size()) - # try the same with includeCurrent=True - r = Workplane("XY").polyline(points[1:],includeCurrent=True).mirrorX() + # try the same with includeCurrent=True + r = Workplane("XY").polyline(points[1:], includeCurrent=True).mirrorX() self.assertEqual(1, r.wires().size()) self.assertEqual(18, r.edges().size()) - def testChainedMirror(self): """ Tests whether or not calling mirrorX().mirrorY() works correctly @@ -1145,23 +1342,22 @@ def testChainedMirror(self): r = 20 s = 7 t = 1.5 - + points = [ - (0, 0), - (0, t/2), - (r/2-1.5*t, r/2-t), - (s/2, r/2-t), - (s/2, r/2), - (r/2, r/2), - (r/2, s/2), - (r/2-t, s/2), - (r/2-t, r/2-1.5*t), - (t/2, 0) + (0, 0), + (0, t / 2), + (r / 2 - 1.5 * t, r / 2 - t), + (s / 2, r / 2 - t), + (s / 2, r / 2), + (r / 2, r / 2), + (r / 2, s / 2), + (r / 2 - t, s / 2), + (r / 2 - t, r / 2 - 1.5 * t), + (t / 2, 0), ] - - r = Workplane("XY").polyline(points).mirrorX().mirrorY() \ - .extrude(1).faces('>Z') - + + r = Workplane("XY").polyline(points).mirrorX().mirrorY().extrude(1).faces(">Z") + self.assertEquals(1, r.wires().size()) self.assertEquals(32, r.edges().size()) @@ -1191,7 +1387,7 @@ def testIbeam(self): (t / 2.0, (t - H / 2.0)), (W / 2.0, (t - H / 2.0)), (W / 2.0, H / -2.0), - (0, H / -2.0) + (0, H / -2.0), ] r = s.polyline(pts).mirrorY() # these other forms also work res = r.extrude(L) @@ -1210,8 +1406,15 @@ def testFillet(self): """ Tests filleting edges on a solid """ - c = CQ(makeUnitCube()).faces(">Z").workplane().circle( - 0.25).extrude(0.25, True).edges("|Z").fillet(0.2) + c = ( + CQ(makeUnitCube()) + .faces(">Z") + .workplane() + .circle(0.25) + .extrude(0.25, True) + .edges("|Z") + .fillet(0.2) + ) self.saveModel(c) self.assertEqual(12, c.faces().size()) @@ -1241,8 +1444,7 @@ def testChamferCylinder(self): """ Test chamfer API with a cylinder shape """ - cylinder = Workplane("XY").circle( - 1).extrude(1).faces(">Z").chamfer(0.1) + cylinder = Workplane("XY").circle(1).extrude(1).faces(">Z").chamfer(0.1) self.saveModel(cylinder) self.assertEqual(4, cylinder.faces().size()) @@ -1251,21 +1453,15 @@ def testCounterBores(self): Tests making a set of counterbored holes in a face """ c = CQ(makeCube(3.0)) - pnts = [ - (-1.0, -1.0), (0.0, 0.0), (1.0, 1.0) - ] - c = c.faces(">Z").workplane().pushPoints( - pnts).cboreHole(0.1, 0.25, 0.25, 0.75) + pnts = [(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)] + c = c.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25, 0.75) self.assertEqual(18, c.faces().size()) self.saveModel(c) # Tests the case where the depth of the cboreHole is not specified c2 = CQ(makeCube(3.0)) - pnts = [ - (-1.0, -1.0), (0.0, 0.0), (1.0, 1.0) - ] - c2 = c2.faces(">Z").workplane().pushPoints( - pnts).cboreHole(0.1, 0.25, 0.25) + pnts = [(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)] + c2 = c2.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25) self.assertEqual(15, c2.faces().size()) def testCounterSinks(self): @@ -1273,8 +1469,15 @@ def testCounterSinks(self): Tests countersinks """ s = Workplane(Plane.XY()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) def testSplitKeepingHalf(self): @@ -1283,8 +1486,7 @@ def testSplitKeepingHalf(self): """ # drill a hole in the side - c = CQ(makeUnitCube()).faces( - ">Z").workplane().circle(0.25).cutThruAll() + c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() self.assertEqual(7, c.faces().size()) @@ -1299,13 +1501,11 @@ def testSplitKeepingBoth(self): """ # drill a hole in the side - c = CQ(makeUnitCube()).faces( - ">Z").workplane().circle(0.25).cutThruAll() + c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() self.assertEqual(7, c.faces().size()) # now cut it in half sideways - result = c.faces( - ">Y").workplane(-0.5).split(keepTop=True, keepBottom=True) + result = c.faces(">Y").workplane(-0.5).split(keepTop=True, keepBottom=True) # stack will have both halves, original will be unchanged # two solids are on the stack, eac @@ -1318,13 +1518,11 @@ def testSplitKeepingBottom(self): Tests splitting a solid improperly """ # Drill a hole in the side - c = CQ(makeUnitCube()).faces( - ">Z").workplane().circle(0.25).cutThruAll() + c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() self.assertEqual(7, c.faces().size()) # Now cut it in half sideways - result = c.faces( - ">Y").workplane(-0.5).split(keepTop=False, keepBottom=True) + result = c.faces(">Y").workplane(-0.5).split(keepTop=False, keepBottom=True) # stack will have both halves, original will be unchanged # one solid is on the stack @@ -1370,22 +1568,37 @@ def testBoxPointList(self): """ Tests creating an array of boxes """ - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box( - 0.25, 0.25, 0.25, combine=True) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .box(0.25, 0.25, 0.25, combine=True) + ) # 1 object, 4 solids because the object is a compound self.assertEqual(4, s.solids().size()) self.assertEqual(1, s.size()) self.saveModel(s) - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box( - 0.25, 0.25, 0.25, combine=False) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .box(0.25, 0.25, 0.25, combine=False) + ) # 4 objects, 4 solids, because each is a separate solid self.assertEqual(4, s.size()) self.assertEqual(4, s.solids().size()) def testBoxCombine(self): - s = Workplane("XY").box(4, 4, 0.5).faces(">Z").workplane().rect( - 3, 3, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True) + s = ( + Workplane("XY") + .box(4, 4, 0.5) + .faces(">Z") + .workplane() + .rect(3, 3, forConstruction=True) + .vertices() + .box(0.25, 0.25, 0.25, combine=True) + ) self.saveModel(s) self.assertEqual(1, s.solids().size()) # we should have one big solid @@ -1394,27 +1607,36 @@ def testBoxCombine(self): def testSphereDefaults(self): s = Workplane("XY").sphere(10) - self.saveModel(s) # Until FreeCAD fixes their sphere operation + self.saveModel(s) # Until FreeCAD fixes their sphere operation self.assertEqual(1, s.solids().size()) self.assertEqual(1, s.faces().size()) def testSphereCustom(self): - s = Workplane("XY").sphere(10, angle1=0, angle2=90, - angle3=360, centered=(False, False, False)) + s = Workplane("XY").sphere( + 10, angle1=0, angle2=90, angle3=360, centered=(False, False, False) + ) self.saveModel(s) self.assertEqual(1, s.solids().size()) self.assertEqual(2, s.faces().size()) def testSpherePointList(self): - s = Workplane("XY").rect( - 4.0, 4.0, forConstruction=True).vertices().sphere(0.25, combine=False) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .sphere(0.25, combine=False) + ) # self.saveModel(s) # Until FreeCAD fixes their sphere operation self.assertEqual(4, s.solids().size()) self.assertEqual(4, s.faces().size()) def testSphereCombine(self): - s = Workplane("XY").rect( - 4.0, 4.0, forConstruction=True).vertices().sphere(2.25, combine=True) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .sphere(2.25, combine=True) + ) # self.saveModel(s) # Until FreeCAD fixes their sphere operation self.assertEqual(1, s.solids().size()) self.assertEqual(4, s.faces().size()) @@ -1427,62 +1649,111 @@ def testWedgeDefaults(self): self.assertEqual(5, s.vertices().size()) def testWedgeCentering(self): - s = Workplane("XY").wedge(10, 10, 10, 5, 5, 5, 5, centered=(False, False, False)) + s = Workplane("XY").wedge( + 10, 10, 10, 5, 5, 5, 5, centered=(False, False, False) + ) # self.saveModel(s) self.assertEqual(1, s.solids().size()) self.assertEqual(5, s.faces().size()) self.assertEqual(5, s.vertices().size()) def testWedgePointList(self): - s = Workplane("XY").rect( - 4.0, 4.0, forConstruction=True).vertices().wedge(10, 10, 10, 5, 5, 5, 5, combine=False) - #self.saveModel(s) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .wedge(10, 10, 10, 5, 5, 5, 5, combine=False) + ) + # self.saveModel(s) self.assertEqual(4, s.solids().size()) self.assertEqual(20, s.faces().size()) self.assertEqual(20, s.vertices().size()) def testWedgeCombined(self): - s = Workplane("XY").rect( - 4.0, 4.0, forConstruction=True).vertices().wedge(10, 10, 10, 5, 5, 5, 5, combine=True) + s = ( + Workplane("XY") + .rect(4.0, 4.0, forConstruction=True) + .vertices() + .wedge(10, 10, 10, 5, 5, 5, 5, combine=True) + ) # self.saveModel(s) self.assertEqual(1, s.solids().size()) self.assertEqual(12, s.faces().size()) self.assertEqual(16, s.vertices().size()) def testQuickStartXY(self): - s = Workplane(Plane.XY()).box(2, 4, 0.5).faces(">Z").workplane().rect(1.5, 3.5, forConstruction=True)\ - .vertices().cskHole(0.125, 0.25, 82, depth=None) + s = ( + Workplane(Plane.XY()) + .box(2, 4, 0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.assertEqual(1, s.solids().size()) self.assertEqual(14, s.faces().size()) self.saveModel(s) def testQuickStartYZ(self): - s = Workplane(Plane.YZ()).box(2, 4, 0.5).faces(">X").workplane().rect(1.5, 3.5, forConstruction=True)\ - .vertices().cskHole(0.125, 0.25, 82, depth=None) + s = ( + Workplane(Plane.YZ()) + .box(2, 4, 0.5) + .faces(">X") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.assertEqual(1, s.solids().size()) self.assertEqual(14, s.faces().size()) self.saveModel(s) def testQuickStartXZ(self): - s = Workplane(Plane.XZ()).box(2, 4, 0.5).faces(">Y").workplane().rect(1.5, 3.5, forConstruction=True)\ - .vertices().cskHole(0.125, 0.25, 82, depth=None) + s = ( + Workplane(Plane.XZ()) + .box(2, 4, 0.5) + .faces(">Y") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.assertEqual(1, s.solids().size()) self.assertEqual(14, s.faces().size()) self.saveModel(s) def testDoubleTwistedLoft(self): - s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed( - rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft() - s2 = Workplane("XY").polygon(8, 20.0).workplane( - offset=-4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft() + s = ( + Workplane("XY") + .polygon(8, 20.0) + .workplane(offset=4.0) + .transformed(rotate=Vector(0, 0, 15.0)) + .polygon(8, 20) + .loft() + ) + s2 = ( + Workplane("XY") + .polygon(8, 20.0) + .workplane(offset=-4.0) + .transformed(rotate=Vector(0, 0, 15.0)) + .polygon(8, 20) + .loft() + ) # self.assertEquals(10,s.faces().size()) # self.assertEquals(1,s.solids().size()) s3 = s.combineSolids(s2) self.saveModel(s3) def testTwistedLoft(self): - s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed( - rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft() + s = ( + Workplane("XY") + .polygon(8, 20.0) + .workplane(offset=4.0) + .transformed(rotate=Vector(0, 0, 15.0)) + .polygon(8, 20) + .loft() + ) self.assertEqual(10, s.faces().size()) self.assertEqual(1, s.solids().size()) self.saveModel(s) @@ -1507,13 +1778,12 @@ def testUnions(self): toUnion = s.rect(1.0, 1.0).extrude(1.0) resS = currentS.union(toUnion) - - self.assertEqual(11,resS.faces().size()) + + self.assertEqual(11, resS.faces().size()) def testCombine(self): s = Workplane(Plane.XY()) - objects1 = s.rect(2.0, 2.0).extrude(0.5).faces( - '>Z').rect(1.0, 1.0).extrude(0.5) + objects1 = s.rect(2.0, 2.0).extrude(0.5).faces(">Z").rect(1.0, 1.0).extrude(0.5) objects1.combine() @@ -1544,33 +1814,64 @@ def testClean(self): # make a cube with a splitter edge on one of the faces # autosimplify should remove the splitter - s = Workplane("XY").moveTo(0, 0).line(5, 0).line(5, 0).line(0, 10).\ - line(-10, 0).close().extrude(10) + s = ( + Workplane("XY") + .moveTo(0, 0) + .line(5, 0) + .line(5, 0) + .line(0, 10) + .line(-10, 0) + .close() + .extrude(10) + ) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by union operation - s = Workplane("XY").box(10, 10, 10).union( - Workplane("XY").box(20, 10, 10)) + s = Workplane("XY").box(10, 10, 10).union(Workplane("XY").box(20, 10, 10)) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by extrude+combine operation - s = Workplane("XY").box(10, 10, 10).faces(">Y").\ - workplane().rect(5, 10, 5).extrude(20) + s = ( + Workplane("XY") + .box(10, 10, 10) + .faces(">Y") + .workplane() + .rect(5, 10, 5) + .extrude(20) + ) self.assertEqual(10, s.faces().size()) # test removal of splitter caused by double hole operation - s = Workplane("XY").box(10, 10, 10).faces(">Z").workplane().\ - hole(3, 5).faces(">Z").workplane().hole(3, 10) + s = ( + Workplane("XY") + .box(10, 10, 10) + .faces(">Z") + .workplane() + .hole(3, 5) + .faces(">Z") + .workplane() + .hole(3, 10) + ) self.assertEqual(7, s.faces().size()) # test removal of splitter caused by cutThruAll - s = Workplane("XY").box(10, 10, 10).faces(">Y").workplane().\ - rect(10, 5).cutBlind(-5).faces(">Z").workplane().\ - center(0, 2.5).rect(5, 5).cutThruAll() + s = ( + Workplane("XY") + .box(10, 10, 10) + .faces(">Y") + .workplane() + .rect(10, 5) + .cutBlind(-5) + .faces(">Z") + .workplane() + .center(0, 2.5) + .rect(5, 5) + .cutThruAll() + ) self.assertEqual(18, s.faces().size()) @@ -1584,16 +1885,33 @@ def testNoClean(self): Test the case when clean is disabled. """ # test disabling autoSimplify - s = Workplane("XY").moveTo(0, 0).line(5, 0).line(5, 0).line(0, 10).\ - line(-10, 0).close().extrude(10, clean=False) + s = ( + Workplane("XY") + .moveTo(0, 0) + .line(5, 0) + .line(5, 0) + .line(0, 10) + .line(-10, 0) + .close() + .extrude(10, clean=False) + ) self.assertEqual(7, s.faces().size()) - s = Workplane("XY").box(10, 10, 10).\ - union(Workplane("XY").box(20, 10, 10), clean=False) + s = ( + Workplane("XY") + .box(10, 10, 10) + .union(Workplane("XY").box(20, 10, 10), clean=False) + ) self.assertEqual(14, s.faces().size()) - s = Workplane("XY").box(10, 10, 10).faces(">Y").\ - workplane().rect(5, 10, 5).extrude(20, clean=False) + s = ( + Workplane("XY") + .box(10, 10, 10) + .faces(">Y") + .workplane() + .rect(5, 10, 5) + .extrude(20, clean=False) + ) self.assertEqual(12, s.faces().size()) @@ -1601,8 +1919,17 @@ def testExplicitClean(self): """ Test running of `clean()` method explicitly. """ - s = Workplane("XY").moveTo(0, 0).line(5, 0).line(5, 0).line(0, 10).\ - line(-10, 0).close().extrude(10, clean=False).clean() + s = ( + Workplane("XY") + .moveTo(0, 0) + .line(5, 0) + .line(5, 0) + .line(0, 10) + .line(-10, 0) + .close() + .extrude(10, clean=False) + .clean() + ) self.assertEqual(6, s.faces().size()) def testPlanes(self): @@ -1611,62 +1938,132 @@ def testPlanes(self): """ # ZX plane s = Workplane(Plane.ZX()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # YX plane s = Workplane(Plane.YX()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # YX plane s = Workplane(Plane.YX()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # ZY plane s = Workplane(Plane.ZY()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # front plane s = Workplane(Plane.front()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # back plane s = Workplane(Plane.back()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # left plane s = Workplane(Plane.left()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # right plane s = Workplane(Plane.right()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # top plane s = Workplane(Plane.top()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) # bottom plane s = Workplane(Plane.bottom()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) + result = ( + s.rect(2.0, 4.0) + .extrude(0.5) + .faces(">Z") + .workplane() + .rect(1.5, 3.5, forConstruction=True) + .vertices() + .cskHole(0.125, 0.25, 82, depth=None) + ) self.saveModel(result) def testIsInside(self): @@ -1715,8 +2112,14 @@ def build(): h = 10.0 t = 1.0 s1 = Workplane("XY").circle(bd).workplane(offset=h).circle(td).loft() - s2 = Workplane("XY").workplane(offset=t).circle( - bd - (2.0 * t)).workplane(offset=(h - t)).circle(td - (2.0 * t)).loft() + s2 = ( + Workplane("XY") + .workplane(offset=t) + .circle(bd - (2.0 * t)) + .workplane(offset=(h - t)) + .circle(td - (2.0 * t)) + .loft() + ) s3 = s1.cut(s2) self.saveModel(s3) @@ -1756,57 +2159,90 @@ def testEnclosure(self): p_lipHeight = 1.0 # outer shell - oshell = Workplane("XY").rect(p_outerWidth, p_outerLength).extrude( - p_outerHeight + p_lipHeight) + oshell = ( + Workplane("XY") + .rect(p_outerWidth, p_outerLength) + .extrude(p_outerHeight + p_lipHeight) + ) # weird geometry happens if we make the fillets in the wrong order if p_sideRadius > p_topAndBottomRadius: - oshell = oshell.edges("|Z").fillet(p_sideRadius)\ - .edges("#Z").fillet(p_topAndBottomRadius) + oshell = ( + oshell.edges("|Z") + .fillet(p_sideRadius) + .edges("#Z") + .fillet(p_topAndBottomRadius) + ) else: - oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)\ - .edges("|Z").fillet(p_sideRadius) + oshell = ( + oshell.edges("#Z") + .fillet(p_topAndBottomRadius) + .edges("|Z") + .fillet(p_sideRadius) + ) # inner shell - ishell = oshell.faces("Z").workplane(-p_thickness)\ - .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)\ - .vertices()\ - .circle(p_screwpostOD / 2.0)\ - .circle(p_screwpostID / 2.0)\ + POSTWIDTH = p_outerWidth - 2.0 * p_screwpostInset + POSTLENGTH = p_outerLength - 2.0 * p_screwpostInset + + box = ( + box.faces(">Z") + .workplane(-p_thickness) + .rect(POSTWIDTH, POSTLENGTH, forConstruction=True) + .vertices() + .circle(p_screwpostOD / 2.0) + .circle(p_screwpostID / 2.0) .extrude((-1.0) * (p_outerHeight + p_lipHeight - p_thickness), True) + ) # split lid into top and bottom parts - (lid, bottom) = box.faces(">Z").workplane(-p_thickness - - p_lipHeight).split(keepTop=True, keepBottom=True).all() # splits into two solids + (lid, bottom) = ( + box.faces(">Z") + .workplane(-p_thickness - p_lipHeight) + .split(keepTop=True, keepBottom=True) + .all() + ) # splits into two solids # translate the lid, and subtract the bottom from it to produce the lid inset lowerLid = lid.translate((0, 0, -p_lipHeight)) cutlip = lowerLid.cut(bottom).translate( - (p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight)) + (p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight) + ) # compute centers for counterbore/countersink or counterbore - topOfLidCenters = cutlip.faces(">Z").workplane().rect( - POSTWIDTH, POSTLENGTH, forConstruction=True).vertices() + topOfLidCenters = ( + cutlip.faces(">Z") + .workplane() + .rect(POSTWIDTH, POSTLENGTH, forConstruction=True) + .vertices() + ) # add holes of the desired type if p_boreDiameter > 0 and p_boreDepth > 0: topOfLid = topOfLidCenters.cboreHole( - p_screwpostID, p_boreDiameter, p_boreDepth, (2.0) * p_thickness) + p_screwpostID, p_boreDiameter, p_boreDepth, (2.0) * p_thickness + ) elif p_countersinkDiameter > 0 and p_countersinkAngle > 0: topOfLid = topOfLidCenters.cskHole( - p_screwpostID, p_countersinkDiameter, p_countersinkAngle, (2.0) * p_thickness) + p_screwpostID, + p_countersinkDiameter, + p_countersinkAngle, + (2.0) * p_thickness, + ) else: topOfLid = topOfLidCenters.hole(p_screwpostID, (2.0) * p_thickness) @@ -1823,9 +2259,9 @@ def testExtrude(self): """ Test extrude """ - r = 1. - h = 1. - decimal_places = 9. + r = 1.0 + h = 1.0 + decimal_places = 9.0 # extrude in one direction s = Workplane("XY").circle(r).extrude(h, both=False) @@ -1836,9 +2272,7 @@ def testExtrude(self): # calculate the distance between the top and the bottom face delta = top_face.val().Center().sub(bottom_face.val().Center()) - self.assertTupleAlmostEquals(delta.toTuple(), - (0., 0., h), - decimal_places) + self.assertTupleAlmostEquals(delta.toTuple(), (0.0, 0.0, h), decimal_places) # extrude symmetrically s = Workplane("XY").circle(r).extrude(h, both=True) @@ -1849,14 +2283,14 @@ def testExtrude(self): # calculate the distance between the top and the bottom face delta = top_face.val().Center().sub(bottom_face.val().Center()) - self.assertTupleAlmostEquals(delta.toTuple(), - (0., 0., 2. * h), - decimal_places) + self.assertTupleAlmostEquals( + delta.toTuple(), (0.0, 0.0, 2.0 * h), decimal_places + ) def testTaperedExtrudeCutBlind(self): - h = 1. - r = 1. + h = 1.0 + r = 1.0 t = 5 # extrude with a positive taper @@ -1882,10 +2316,17 @@ def testTaperedExtrudeCutBlind(self): self.assertTrue(delta > 0) # cut a tapered hole - s = Workplane("XY").rect(2*r,2*r).extrude(2*h).faces('>Z').workplane()\ - .rect(r,r).cutBlind(-h, taper=t) - - middle_face = s.faces('>Z[-2]') + s = ( + Workplane("XY") + .rect(2 * r, 2 * r) + .extrude(2 * h) + .faces(">Z") + .workplane() + .rect(r, r) + .cutBlind(-h, taper=t) + ) + + middle_face = s.faces(">Z[-2]") self.assertTrue(middle_face.val().Area() < 1) @@ -1896,7 +2337,13 @@ def testClose(self): # Close when endPoint and startPoint coincide. # Create a double half-circle - b = Workplane(Plane.XY()).sagittaArc((10, 0), 2).sagittaArc((0, 0), 2).close().extrude(2) + b = ( + Workplane(Plane.XY()) + .sagittaArc((10, 0), 2) + .sagittaArc((0, 0), 2) + .close() + .extrude(2) + ) # The b shape shall have twice the volume of the a shape. self.assertAlmostEqual(a.val().Volume() * 2.0, b.val().Volume()) @@ -1906,46 +2353,80 @@ def testClose(self): length = 10.0 width = 5.0 - obj1 = Workplane('XY', origin=(0, 0, -thickness / 2)) \ - .moveTo(length / 2, 0).threePointArc((0, width / 2), (-length / 2, 0)) \ - .threePointArc((0, -width / 2), (length / 2, 0)) \ - .close().extrude(thickness) + obj1 = ( + Workplane("XY", origin=(0, 0, -thickness / 2)) + .moveTo(length / 2, 0) + .threePointArc((0, width / 2), (-length / 2, 0)) + .threePointArc((0, -width / 2), (length / 2, 0)) + .close() + .extrude(thickness) + ) - os_x = 8.0 # Offset in X + os_x = 8.0 # Offset in X os_y = -19.5 # Offset in Y - obj2 = Workplane('YZ', origin=(os_x, os_y, -thickness / 2)) \ - .moveTo(os_x + length / 2, os_y).sagittaArc((os_x -length / 2, os_y), width / 2) \ - .sagittaArc((os_x + length / 2, os_y), width / 2) \ - .close().extrude(thickness) + obj2 = ( + Workplane("YZ", origin=(os_x, os_y, -thickness / 2)) + .moveTo(os_x + length / 2, os_y) + .sagittaArc((os_x - length / 2, os_y), width / 2) + .sagittaArc((os_x + length / 2, os_y), width / 2) + .close() + .extrude(thickness) + ) # The obj1 shape shall have the same volume as the obj2 shape. self.assertAlmostEqual(obj1.val().Volume(), obj2.val().Volume()) def testText(self): - box = Workplane("XY" ).box(4, 4, 0.5) - - obj1 = box.faces('>Z').workplane()\ - .text('CQ 2.0',0.5,-.05,cut=True,halign='left',valign='bottom', font='Sans') - - #combined object should have smaller volume - self.assertGreater(box.val().Volume(),obj1.val().Volume()) - - obj2 = box.faces('>Z').workplane()\ - .text('CQ 2.0',0.5,.05,cut=False,combine=True, font='Sans') - - #combined object should have bigger volume - self.assertLess(box.val().Volume(),obj2.val().Volume()) - - #verify that the number of top faces is correct (NB: this is font specific) - self.assertEqual(len(obj2.faces('>Z').vals()),5) - - obj3 = box.faces('>Z').workplane()\ - .text('CQ 2.0',0.5,.05,cut=False,combine=False,halign='right',valign='top', font='Sans') - - #verify that the number of solids is correct - self.assertEqual(len(obj3.solids().vals()),5) + box = Workplane("XY").box(4, 4, 0.5) + + obj1 = ( + box.faces(">Z") + .workplane() + .text( + "CQ 2.0", + 0.5, + -0.05, + cut=True, + halign="left", + valign="bottom", + font="Sans", + ) + ) + + # combined object should have smaller volume + self.assertGreater(box.val().Volume(), obj1.val().Volume()) + + obj2 = ( + box.faces(">Z") + .workplane() + .text("CQ 2.0", 0.5, 0.05, cut=False, combine=True, font="Sans") + ) + + # combined object should have bigger volume + self.assertLess(box.val().Volume(), obj2.val().Volume()) + + # verify that the number of top faces is correct (NB: this is font specific) + self.assertEqual(len(obj2.faces(">Z").vals()), 5) + + obj3 = ( + box.faces(">Z") + .workplane() + .text( + "CQ 2.0", + 0.5, + 0.05, + cut=False, + combine=False, + halign="right", + valign="top", + font="Sans", + ) + ) + + # verify that the number of solids is correct + self.assertEqual(len(obj3.solids().vals()), 5) def testParametricCurve(self): @@ -1954,83 +2435,87 @@ def testParametricCurve(self): k = 4 r = 1 - func = lambda t: ( r*(k+1)*cos(t) - r* cos((k+1)*t), - r*(k+1)*sin(t) - r* sin((k+1)*t)) + func = lambda t: ( + r * (k + 1) * cos(t) - r * cos((k + 1) * t), + r * (k + 1) * sin(t) - r * sin((k + 1) * t), + ) - res_open = Workplane('XY').parametricCurve(func).extrude(3) + res_open = Workplane("XY").parametricCurve(func).extrude(3) - #open profile generates an invalid solid + # open profile generates an invalid solid self.assertFalse(res_open.solids().val().isValid()) - res_closed = Workplane('XY').parametricCurve(func,start=0,stop=2*pi)\ - .extrude(3) + res_closed = ( + Workplane("XY").parametricCurve(func, start=0, stop=2 * pi).extrude(3) + ) - #closed profile will generate a valid solid with 3 faces + # closed profile will generate a valid solid with 3 faces self.assertTrue(res_closed.solids().val().isValid()) - self.assertEqual(len(res_closed.faces().vals()),3) - + self.assertEqual(len(res_closed.faces().vals()), 3) + def testMakeShellSolid(self): - c0 = math.sqrt(2)/4 - vertices = [[c0, -c0, c0], [c0, c0, -c0], [-c0, c0, c0], [-c0, -c0, -c0]] + c0 = math.sqrt(2) / 4 + vertices = [[c0, -c0, c0], [c0, c0, -c0], [-c0, c0, c0], [-c0, -c0, -c0]] faces_ixs = [[0, 1, 2, 0], [1, 0, 3, 1], [2, 3, 0, 2], [3, 2, 1, 3]] - + faces = [] for ixs in faces_ixs: lines = [] - for v1,v2 in zip(ixs,ixs[1:]): - lines.append(Edge.makeLine(Vector(*vertices[v1]), - Vector(*vertices[v2]))) + for v1, v2 in zip(ixs, ixs[1:]): + lines.append( + Edge.makeLine(Vector(*vertices[v1]), Vector(*vertices[v2])) + ) wire = Wire.combine(lines) faces.append(Face.makeFromWires(wire)) - + shell = Shell.makeShell(faces) solid = Solid.makeSolid(shell) - + self.assertTrue(shell.isValid()) self.assertTrue(solid.isValid()) - - self.assertEqual(len(solid.Vertices()),4) - self.assertEqual(len(solid.Faces()),4) + + self.assertEqual(len(solid.Vertices()), 4) + self.assertEqual(len(solid.Faces()), 4) def testIsInsideSolid(self): # test solid - model = Workplane('XY').box(10,10,10) - solid = model.val() # get first object on stack + model = Workplane("XY").box(10, 10, 10) + solid = model.val() # get first object on stack - self.assertTrue(solid.isInside((0,0,0))) - self.assertFalse(solid.isInside((10,10,10))) - self.assertTrue(solid.isInside((Vector(3,3,3)))) - self.assertFalse(solid.isInside((Vector(30.0,30.0,30.0)))) + self.assertTrue(solid.isInside((0, 0, 0))) + self.assertFalse(solid.isInside((10, 10, 10))) + self.assertTrue(solid.isInside((Vector(3, 3, 3)))) + self.assertFalse(solid.isInside((Vector(30.0, 30.0, 30.0)))) - self.assertTrue(solid.isInside((0,0,4.99), tolerance=0.1)) - self.assertTrue(solid.isInside((0,0,5))) # check point on surface - self.assertTrue(solid.isInside((0,0,5.01), tolerance=0.1)) - self.assertFalse(solid.isInside((0,0,5.1), tolerance=0.1)) + self.assertTrue(solid.isInside((0, 0, 4.99), tolerance=0.1)) + self.assertTrue(solid.isInside((0, 0, 5))) # check point on surface + self.assertTrue(solid.isInside((0, 0, 5.01), tolerance=0.1)) + self.assertFalse(solid.isInside((0, 0, 5.1), tolerance=0.1)) # test compound solid - model = Workplane('XY').box(10,10,10) - model = model.moveTo(50,50).box(10,10,10) + model = Workplane("XY").box(10, 10, 10) + model = model.moveTo(50, 50).box(10, 10, 10) solid = model.val() - self.assertTrue(solid.isInside((0,0,0))) - self.assertTrue(solid.isInside((50,50,0))) - self.assertFalse(solid.isInside((50,56,0))) + self.assertTrue(solid.isInside((0, 0, 0))) + self.assertTrue(solid.isInside((50, 50, 0))) + self.assertFalse(solid.isInside((50, 56, 0))) # make sure raises on non solid - model = Workplane('XY').rect(10,10) + model = Workplane("XY").rect(10, 10) solid = model.val() with self.assertRaises(AttributeError): - solid.isInside((0,0,0)) + solid.isInside((0, 0, 0)) # test solid with an internal void - void = Workplane('XY').box(10,10,10) - model = Workplane('XY').box(100,100,100).cut(void) + void = Workplane("XY").box(10, 10, 10) + model = Workplane("XY").box(100, 100, 100).cut(void) solid = model.val() - self.assertFalse(solid.isInside((0,0,0))) - self.assertTrue(solid.isInside((40,40,40))) - self.assertFalse(solid.isInside((55,55,55))) + self.assertFalse(solid.isInside((0, 0, 0))) + self.assertTrue(solid.isInside((40, 40, 40))) + self.assertFalse(solid.isInside((55, 55, 55))) def testWorkplaneCenterOptions(self): """ @@ -2038,141 +2523,222 @@ def testWorkplaneCenterOptions(self): """ decimal_places = 9 - pts = [(0,0),(90,0),(90,30),(30,30),(30,60),(0.0,60)] + pts = [(0, 0), (90, 0), (90, 30), (30, 30), (30, 60), (0.0, 60)] r = Workplane("XY").polyline(pts).close().extrude(10.0) - origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin') \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin") + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (0.0, 0.0, 10.0), decimal_places) - origin = r.faces(">Z").workplane(centerOption='CenterOfMass') \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z").workplane(centerOption="CenterOfMass").plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (37.5, 22.5, 10.0), decimal_places) - origin = r.faces(">Z").workplane(centerOption='CenterOfBoundBox') \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z") + .workplane(centerOption="CenterOfBoundBox") + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (45.0, 30.0, 10.0), decimal_places) - origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin',origin=(30,10,20)) \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin", origin=(30, 10, 20)) + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (30.0, 10.0, 10.0), decimal_places) - origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin',origin=Vector(30,10,20)) \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin", origin=Vector(30, 10, 20)) + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (30.0, 10.0, 10.0), decimal_places) with self.assertRaises(ValueError): - origin = r.faces(">Z").workplane(centerOption='undefined') + origin = r.faces(">Z").workplane(centerOption="undefined") # test case where plane origin is shifted with center call - r = r.faces(">Z").workplane(centerOption='ProjectedOrigin').center(30,0) \ - .hole(90) - - origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin') \ - .plane.origin.toTuple() + r = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin") + .center(30, 0) + .hole(90) + ) + + origin = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin") + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (30.0, 0.0, 10.0), decimal_places) - origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin', origin=(0,0,0)) \ - .plane.origin.toTuple() + origin = ( + r.faces(">Z") + .workplane(centerOption="ProjectedOrigin", origin=(0, 0, 0)) + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (0.0, 0.0, 10.0), decimal_places) # make sure projection works in all directions r = Workplane("YZ").polyline(pts).close().extrude(10.0) - origin = r.faces(">X").workplane(centerOption='ProjectedOrigin') \ - .plane.origin.toTuple() + origin = ( + r.faces(">X") + .workplane(centerOption="ProjectedOrigin") + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (10.0, 0.0, 0.0), decimal_places) - origin = r.faces(">X").workplane(centerOption='CenterOfMass') \ - .plane.origin.toTuple() + origin = ( + r.faces(">X").workplane(centerOption="CenterOfMass").plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (10.0, 37.5, 22.5), decimal_places) - origin = r.faces(">X").workplane(centerOption='CenterOfBoundBox') \ - .plane.origin.toTuple() + origin = ( + r.faces(">X") + .workplane(centerOption="CenterOfBoundBox") + .plane.origin.toTuple() + ) self.assertTupleAlmostEquals(origin, (10.0, 45.0, 30.0), decimal_places) r = Workplane("XZ").polyline(pts).close().extrude(10.0) - origin = r.faces("Z").workplane().slot2D(4,1,0).cutThruAll() + box = Workplane("XY").box(5, 5, 1) + result = box.faces(">Z").workplane().slot2D(4, 1, 0).cutThruAll() self.assertAlmostEqual(result.val().Volume(), 21.214601837, decimal_places) - result = box.faces(">Z").workplane().slot2D(4,1,0).cutBlind(-0.5) + result = box.faces(">Z").workplane().slot2D(4, 1, 0).cutBlind(-0.5) self.assertAlmostEqual(result.val().Volume(), 23.107300918, decimal_places) # Test to see if slot is rotated correctly - result = Workplane("XY").slot2D(4,1,45).extrude(1) + result = Workplane("XY").slot2D(4, 1, 45).extrude(1) point = result.faces(">Z").edges(">X").first().val().startPoint().toTuple() - self.assertTupleAlmostEquals(point, (0.707106781, 1.414213562, 1.0), decimal_places) + self.assertTupleAlmostEquals( + point, (0.707106781, 1.414213562, 1.0), decimal_places + ) def test_assembleEdges(self): # Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides # Passes an open wire to assembleEdges so that IsDone is true but Error returns 2 to test the warning functionality. - edge_points = [[-7.,-7.,0.], [-3.,-10.,3.], [7.,-7.,0.], [7.,7.,0.], [-7.,7.,0.]] - edge_wire = Workplane('XY').polyline([(-7.,-7.), (7.,-7.), (7.,7.), (-7.,7.)]) - edge_wire = edge_wire.add(Workplane('YZ').workplane().transformed(offset=Vector(0, 0, -7), rotate=Vector(0, 45, 0)).spline([(-7.,0.), (3,-3), (7.,0.)])) + edge_points = [ + [-7.0, -7.0, 0.0], + [-3.0, -10.0, 3.0], + [7.0, -7.0, 0.0], + [7.0, 7.0, 0.0], + [-7.0, 7.0, 0.0], + ] + edge_wire = Workplane("XY").polyline( + [(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)] + ) + edge_wire = edge_wire.add( + Workplane("YZ") + .workplane() + .transformed(offset=Vector(0, 0, -7), rotate=Vector(0, 45, 0)) + .spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)]) + ) edge_wire = [o.vals()[0] for o in edge_wire.all()] edge_wire = Wire.assembleEdges(edge_wire) - + # Embossed star, need to change optional parameters to obtain nice looking result. - r1=3. - r2=10. - fn=6 - edge_points = [[r1*math.cos(i * math.pi/fn), r1*math.sin(i * math.pi/fn)] if i%2==0 else [r2*math.cos(i * math.pi/fn), r2*math.sin(i * math.pi/fn)] for i in range(2*fn+1)] - edge_wire = Workplane('XY').polyline(edge_points) + r1 = 3.0 + r2 = 10.0 + fn = 6 + edge_points = [ + [r1 * math.cos(i * math.pi / fn), r1 * math.sin(i * math.pi / fn)] + if i % 2 == 0 + else [r2 * math.cos(i * math.pi / fn), r2 * math.sin(i * math.pi / fn)] + for i in range(2 * fn + 1) + ] + edge_wire = Workplane("XY").polyline(edge_points) edge_wire = [o.vals()[0] for o in edge_wire.all()] edge_wire = Wire.assembleEdges(edge_wire) - + # Points on hexagonal pattern coordinates, use of pushpoints. - r1 = 1. + r1 = 1.0 fn = 6 - edge_points = [[r1*math.cos(i * 2*math.pi/fn), r1*math.sin(i * 2*math.pi/fn)] for i in range(fn+1)] - surface_points = [[0.25,0,0.75], [-0.25,0,0.75], [0,0.25,0.75], [0,-0.25,0.75], [0,0,2]] - edge_wire = Workplane('XY').polyline(edge_points) + edge_points = [ + [r1 * math.cos(i * 2 * math.pi / fn), r1 * math.sin(i * 2 * math.pi / fn)] + for i in range(fn + 1) + ] + surface_points = [ + [0.25, 0, 0.75], + [-0.25, 0, 0.75], + [0, 0.25, 0.75], + [0, -0.25, 0.75], + [0, 0, 2], + ] + edge_wire = Workplane("XY").polyline(edge_points) edge_wire = [o.vals()[0] for o in edge_wire.all()] edge_wire = Wire.assembleEdges(edge_wire) - + # Gyroïd, all edges are splines on different workplanes. - edge_points = [[[3.54, 3.54], [1.77, 0.0], [3.54, -3.54]], [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]], [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]], [[-3.54, -3.54], [-1.77, 0.0], [-3.54, 3.54]], [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]], [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]]] - plane_list = ['XZ', 'XY', 'YZ', 'XZ', 'YZ', 'XY'] - offset_list = [-3.54, 3.54, 3.54, 3.54, -3.54, -3.54] - edge_wire = Workplane(plane_list[0]).workplane(offset=-offset_list[0]).spline(edge_points[0]) - for i in range(len(edge_points)-1): - edge_wire = edge_wire.add(Workplane(plane_list[i+1]).workplane(offset=-offset_list[i+1]).spline(edge_points[i+1])) + edge_points = [ + [[3.54, 3.54], [1.77, 0.0], [3.54, -3.54]], + [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]], + [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]], + [[-3.54, -3.54], [-1.77, 0.0], [-3.54, 3.54]], + [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]], + [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]], + ] + plane_list = ["XZ", "XY", "YZ", "XZ", "YZ", "XY"] + offset_list = [-3.54, 3.54, 3.54, 3.54, -3.54, -3.54] + edge_wire = ( + Workplane(plane_list[0]) + .workplane(offset=-offset_list[0]) + .spline(edge_points[0]) + ) + for i in range(len(edge_points) - 1): + edge_wire = edge_wire.add( + Workplane(plane_list[i + 1]) + .workplane(offset=-offset_list[i + 1]) + .spline(edge_points[i + 1]) + ) edge_wire = [o.vals()[0] for o in edge_wire.all()] edge_wire = Wire.assembleEdges(edge_wire) diff --git a/tests/test_cqgi.py b/tests/test_cqgi.py index a7218ae68..43d38d773 100644 --- a/tests/test_cqgi.py +++ b/tests/test_cqgi.py @@ -42,8 +42,9 @@ def test_parser(self): 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 @@ def test_build_with_debug(self): 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 @@ def test_build_with_empty_params(self): 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 @@ def test_describe_parameters(self): """ ) 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 @@ def test_describe_parameter_invalid_doesnt_fail_script(self): """ ) 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 @@ def test_that_invalid_syntax_in_script_fails_immediately(self): 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 @@ def test_that_assinging_number_to_string_works(self): 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 @@ def test_that_assigning_string_to_number_fails(self): 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 @@ def test_that_assigning_unknown_var_fails(self): """ ) - 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 @@ def test_setting_boolean_variable(self): """ ) - 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( diff --git a/tests/test_exporters.py b/tests/test_exporters.py index db631beaa..a91eab98c 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -12,7 +12,6 @@ class TestExporters(BaseTest): - def _exportBox(self, eType, stringsToFind): """ Exports a test object, and then looks for @@ -21,31 +20,32 @@ def _exportBox(self, eType, stringsToFind): """ 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, ['']) + self._exportBox(exporters.ExportTypes.AMF, [""]) 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"] + ) diff --git a/tests/test_importers.py b/tests/test_importers.py index 16be51dc5..018fcd9fd 100644 --- a/tests/test_importers.py +++ b/tests/test_importers.py @@ -36,12 +36,18 @@ def importBox(self, importType, fileName): 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 @@ def testInvalidSTEP(self): 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 @@ def testImportMultipartSTEP(self): objs = importers.importShape(importers.ImportTypes.STEP, filename) self.assertEqual(2, len(objs.all())) -if __name__ == '__main__': + +if __name__ == "__main__": import unittest + unittest.main() diff --git a/tests/test_jupyter.py b/tests/test_jupyter.py index 7f4b92c25..ad7db4db0 100644 --- a/tests/test_jupyter.py +++ b/tests/test_jupyter.py @@ -2,9 +2,10 @@ 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) diff --git a/tests/test_selectors.py b/tests/test_selectors.py index d557375e8..049a5a4b6 100644 --- a/tests/test_selectors.py +++ b/tests/test_selectors.py @@ -1,4 +1,4 @@ -__author__ = 'dcowden' +__author__ = "dcowden" """ Tests for CadQuery Selectors @@ -20,22 +20,19 @@ 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 @@ def testVertices(self): 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 @@ def testAll(self): 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 @@ def testSolid(self): 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 @@ def testParallelPlaneFaceFilter(self): # 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 @@ def testMinDistance(self): 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[-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')\ + c = ( + Workplane("XY") + .box(1, 4, 1, centered=(False, True, False)) + .faces("Z") .box(1, 1, 1, centered=(True, True, False)) + ) # select 2nd from the bottom (NB python indexing is 0-based) - vals = c.faces('>Z[1]').vals() + 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[-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 @@ def testBox(self): ((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 @@ def testBox(self): 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 @@ def testBox(self): ((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 @@ def testBox(self): 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 @@ def testBox(self): ((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 @@ def testBox(self): 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 @@ def testAndSelector(self): 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 @@ def testInverseSelector(self): 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) or (XZ', - '(1,4,55.)[20]', - '|XY', - '(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 Y )'] + expressions = [ + "+X ", + "-Y", + "|(1,0,0)", + "#(1.,1.4114,-0.532)", + "%Plane", + ">XZ", + "(1,4,55.)[20]", + "|XY", + "(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 Y )", + ] for e in expressions: gram.parseString(e, parseAll=True) diff --git a/tests/test_workplanes.py b/tests/test_workplanes.py index 414a16f19..9548e7eda 100644 --- a/tests/test_workplanes.py +++ b/tests/test_workplanes.py @@ -16,65 +16,70 @@ 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 @@ def testPlaneBasics(self): # 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)