Skip to content

Commit 0885b19

Browse files
Merge branch 'master' into 7.5
2 parents a1d0075 + d350c1b commit 0885b19

File tree

8 files changed

+592
-190
lines changed

8 files changed

+592
-190
lines changed

cadquery/cq.py

Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def split(self, keepTop: bool = False, keepBottom: bool = False) -> "Workplane":
245245
246246
# drill a hole in the side
247247
c = Workplane().box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()
248-
248+
249249
# now cut it in half sideways
250250
c = c.faces(">Y").workplane(-0.5).split(keepTop=True)
251251
"""
@@ -327,7 +327,7 @@ def all(self) -> List["Workplane"]:
327327

328328
def size(self) -> int:
329329
"""
330-
Return the number of objects currently on the stack
330+
Return the number of objects currently on the stack
331331
"""
332332
return len(self.objects)
333333

@@ -444,7 +444,7 @@ def workplane(
444444
* The centerOption paramter sets how the center is defined.
445445
Options are 'CenterOfMass', 'CenterOfBoundBox', or 'ProjectedOrigin'.
446446
'CenterOfMass' and 'CenterOfBoundBox' are in relation to the selected
447-
face(s) or vertex (vertices). 'ProjectedOrigin' uses by default the current origin
447+
face(s) or vertex (vertices). 'ProjectedOrigin' uses by default the current origin
448448
or the optional origin parameter (if specified) and projects it onto the plane
449449
defined by the selected face(s).
450450
* The Z direction will be normal to the plane of the face,computed
@@ -714,17 +714,17 @@ def _selectObjects(
714714
tag: Optional[str] = None,
715715
) -> "Workplane":
716716
"""
717-
Filters objects of the selected type with the specified selector,and returns results
717+
Filters objects of the selected type with the specified selector,and returns results
718718
719-
:param objType: the type of object we are searching for
720-
:type objType: string: (Vertex|Edge|Wire|Solid|Shell|Compound|CompSolid)
721-
:param tag: if set, search the tagged CQ object instead of self
722-
:type tag: string
723-
:return: a CQ object with the selected objects on the stack.
719+
:param objType: the type of object we are searching for
720+
:type objType: string: (Vertex|Edge|Wire|Solid|Shell|Compound|CompSolid)
721+
:param tag: if set, search the tagged CQ object instead of self
722+
:type tag: string
723+
:return: a CQ object with the selected objects on the stack.
724724
725-
**Implementation Note**: This is the base implementation of the vertices,edges,faces,
726-
solids,shells, and other similar selector methods. It is a useful extension point for
727-
plugin developers to make other selector methods.
725+
**Implementation Note**: This is the base implementation of the vertices,edges,faces,
726+
solids,shells, and other similar selector methods. It is a useful extension point for
727+
plugin developers to make other selector methods.
728728
"""
729729
cq_obj = self._getTagged(tag) if tag else self
730730
# A single list of all faces from all objects on the stack
@@ -1571,7 +1571,7 @@ def polarLine(
15711571
:param float distance: distance of the end of the line from the current point
15721572
:param float angle: angle of the vector to the end of the line with the x-axis
15731573
:return: the Workplane object with the current point at the end of the new line
1574-
"""
1574+
"""
15751575
x = math.cos(math.radians(angle)) * distance
15761576
y = math.sin(math.radians(angle)) * distance
15771577

@@ -1672,6 +1672,9 @@ def spline(
16721672
listOfXYTuple: Iterable[VectorLike],
16731673
tangents: Optional[Sequence[VectorLike]] = None,
16741674
periodic: bool = False,
1675+
parameters: Optional[Sequence[float]] = None,
1676+
scale: bool = True,
1677+
tol: Optional[float] = None,
16751678
forConstruction: bool = False,
16761679
includeCurrent: bool = False,
16771680
makeWire: bool = False,
@@ -1681,8 +1684,41 @@ def spline(
16811684
16821685
:param listOfXYTuple: points to interpolate through
16831686
:type listOfXYTuple: list of 2-tuple
1684-
:param tangents: tuple of Vectors specifying start and finish tangent
1687+
:param tangents: vectors specifying the direction of the tangent to the
1688+
curve at each of the specified interpolation points.
1689+
1690+
If only 2 tangents are given, they will be used as the initial and
1691+
final tangent.
1692+
1693+
If some tangents are not specified (i.e., are None), no tangent
1694+
constraint will be applied to the corresponding interpolation point.
1695+
1696+
The spline will be C2 continuous at the interpolation points where
1697+
no tangent constraint is specified, and C1 continuous at the points
1698+
where a tangent constraint is specified.
16851699
:param periodic: creation of periodic curves
1700+
:param parameters: the value of the parameter at each interpolation point.
1701+
(The intepolated curve is represented as a vector-valued function of a
1702+
scalar parameter.)
1703+
1704+
If periodic == True, then len(parameters) must be
1705+
len(intepolation points) + 1, otherwise len(parameters) must be equal to
1706+
len(interpolation points).
1707+
:param scale: whether to scale the specified tangent vectors before
1708+
interpolating.
1709+
1710+
Each tangent is scaled, so it's length is equal to the derivative of
1711+
the Lagrange interpolated curve.
1712+
1713+
I.e., set this to True, if you want to use only the direction of
1714+
the tangent vectors specified by ``tangents``, but not their magnitude.
1715+
:param tol: tolerance of the algorithm (consult OCC documentation)
1716+
1717+
Used to check that the specified points are not too close to each
1718+
other, and that tangent vectors are not too short. (In either case
1719+
interpolation may fail.)
1720+
1721+
Set to None to use the default tolerance.
16861722
:param includeCurrent: use current point as a starting point of the curve
16871723
:param makeWire: convert the resulting spline edge to a wire
16881724
:return: a Workplane object with the current point at the end of the spline
@@ -1707,9 +1743,6 @@ def spline(
17071743
17081744
*WARNING* It is fairly easy to create a list of points
17091745
that cannot be correctly interpreted as a spline.
1710-
1711-
Future Enhancements:
1712-
* provide access to control points
17131746
"""
17141747

17151748
vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple]
@@ -1721,15 +1754,23 @@ def spline(
17211754
allPoints = vecs
17221755

17231756
if tangents:
1724-
t1, t2 = Vector(tangents[0]), Vector(tangents[1])
1725-
tangents_g: Optional[Tuple[Vector, Vector]] = (
1726-
self.plane.toWorldCoords(t1) - self.plane.origin,
1727-
self.plane.toWorldCoords(t2) - self.plane.origin,
1728-
)
1757+
tangents_g: Optional[Sequence[Vector]] = [
1758+
self.plane.toWorldCoords(t) - self.plane.origin
1759+
if t is not None
1760+
else None
1761+
for t in tangents
1762+
]
17291763
else:
17301764
tangents_g = None
17311765

1732-
e = Edge.makeSpline(allPoints, tangents=tangents_g, periodic=periodic)
1766+
e = Edge.makeSpline(
1767+
allPoints,
1768+
tangents=tangents_g,
1769+
periodic=periodic,
1770+
parameters=parameters,
1771+
scale=scale,
1772+
**({"tol": tol} if tol else {}),
1773+
)
17331774

17341775
if makeWire:
17351776
rv_w = Wire.assembleEdges([e])
@@ -2735,7 +2776,7 @@ def extrude(
27352776
27362777
extrude always *adds* material to a part.
27372778
2738-
The returned object is always a CQ object, and depends on wither combine is True, and
2779+
The returned object is always a CQ object, and depends on whether combine is True, and
27392780
whether a context solid is already defined:
27402781
27412782
* if combine is False, the new value is pushed onto the stack.
@@ -2781,7 +2822,7 @@ def revolve(
27812822
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
27822823
:return: a CQ object with the resulting solid selected.
27832824
2784-
The returned object is always a CQ object, and depends on wither combine is True, and
2825+
The returned object is always a CQ object, and depends on whether combine is True, and
27852826
whether a context solid is already defined:
27862827
27872828
* if combine is False, the new value is pushed onto the stack.
@@ -2937,7 +2978,7 @@ def union(
29372978
"""
29382979
Unions all of the items on the stack of toUnion with the current solid.
29392980
If there is no current solid, the items in toUnion are unioned together.
2940-
2981+
29412982
:param toUnion:
29422983
:type toUnion: a solid object, or a CQ object having a solid,
29432984
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape (default True)
@@ -2975,12 +3016,31 @@ def union(
29753016

29763017
return self.newObject([r])
29773018

3019+
def __or__(self, toUnion: Union["Workplane", Solid, Compound]) -> "Workplane":
3020+
"""
3021+
Syntactic sugar for union.
3022+
Notice that `r = a | b` is equivalent to `r = a.union(b)` and `r = a + b`.
3023+
3024+
Example::
3025+
Box = Workplane("XY").box(1, 1, 1, centered=(False, False, False))
3026+
Sphere = Workplane("XY").sphere(1)
3027+
result = Box | Sphere
3028+
"""
3029+
return self.union(toUnion)
3030+
3031+
def __add__(self, toUnion: Union["Workplane", Solid, Compound]) -> "Workplane":
3032+
"""
3033+
Syntactic sugar for union.
3034+
Notice that `r = a + b` is equivalent to `r = a.union(b)` and `r = a | b`.
3035+
"""
3036+
return self.union(toUnion)
3037+
29783038
def cut(
29793039
self, toCut: Union["Workplane", Solid, Compound], clean: bool = True
29803040
) -> "Workplane":
29813041
"""
29823042
Cuts the provided solid from the current solid, IE, perform a solid subtraction
2983-
3043+
29843044
:param toCut: object to cut
29853045
:type toCut: a solid object, or a CQ object having a solid,
29863046
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
@@ -3010,12 +3070,25 @@ def cut(
30103070

30113071
return self.newObject([newS])
30123072

3073+
def __sub__(self, toUnion: Union["Workplane", Solid, Compound]) -> "Workplane":
3074+
"""
3075+
Syntactic sugar for cut.
3076+
Notice that `r = a - b` is equivalent to `r = a.cut(b)`.
3077+
3078+
Example::
3079+
3080+
Box = Workplane("XY").box(1, 1, 1, centered=(False, False, False))
3081+
Sphere = Workplane("XY").sphere(1)
3082+
result = Box - Sphere
3083+
"""
3084+
return self.cut(toUnion)
3085+
30133086
def intersect(
30143087
self, toIntersect: Union["Workplane", Solid, Compound], clean: bool = True
30153088
) -> "Workplane":
30163089
"""
30173090
Intersects the provided solid from the current solid.
3018-
3091+
30193092
:param toIntersect: object to intersect
30203093
:type toIntersect: a solid object, or a CQ object having a solid,
30213094
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
@@ -3045,6 +3118,19 @@ def intersect(
30453118

30463119
return self.newObject([newS])
30473120

3121+
def __and__(self, toUnion: Union["Workplane", Solid, Compound]) -> "Workplane":
3122+
"""
3123+
Syntactic sugar for intersect.
3124+
Notice that `r = a & b` is equivalent to `r = a.intersect(b)`.
3125+
3126+
Example::
3127+
3128+
Box = Workplane("XY").box(1, 1, 1, centered=(False, False, False))
3129+
Sphere = Workplane("XY").sphere(1)
3130+
result = Box & Sphere
3131+
"""
3132+
return self.intersect(toUnion)
3133+
30483134
def cutBlind(
30493135
self, distanceToCut: float, clean: bool = True, taper: Optional[float] = None
30503136
) -> "Workplane":
@@ -3140,10 +3226,7 @@ def _extrude(
31403226
:return: OCCT solid(s), suitable for boolean operations.
31413227
31423228
This method is a utility method, primarily for plugin and internal use.
3143-
It is the basis for cutBlind,extrude,cutThruAll, and all similar methods.
3144-
3145-
Future Enhancements:
3146-
extrude along a profile (sweep)
3229+
It is the basis for cutBlind, extrude, cutThruAll, and all similar methods.
31473230
"""
31483231

31493232
# group wires together into faces based on which ones are inside the others
@@ -3633,7 +3716,7 @@ def text(
36333716
36343717
extrude always *adds* material to a part.
36353718
3636-
The returned object is always a CQ object, and depends on wither combine is True, and
3719+
The returned object is always a CQ object, and depends on whether combine is True, and
36373720
whether a context solid is already defined:
36383721
36393722
* if combine is False, the new value is pushed onto the stack.
@@ -3666,7 +3749,7 @@ def text(
36663749
def section(self, height: float = 0.0) -> "Workplane":
36673750
"""
36683751
Slices current solid at the given height.
3669-
3752+
36703753
:param float height: height to slice at (default: 0)
36713754
:return: a CQ object with the resulting face(s).
36723755
"""
@@ -3687,7 +3770,7 @@ def section(self, height: float = 0.0) -> "Workplane":
36873770
def toPending(self) -> "Workplane":
36883771
"""
36893772
Adds wires/edges to pendingWires/pendingEdges.
3690-
3773+
36913774
:return: same CQ object with updated context.
36923775
"""
36933776

@@ -3697,22 +3780,31 @@ def toPending(self) -> "Workplane":
36973780
return self
36983781

36993782
def offset2D(
3700-
self, d: float, kind: Literal["arc", "intersection", "tangent"] = "arc"
3783+
self,
3784+
d: float,
3785+
kind: Literal["arc", "intersection", "tangent"] = "arc",
3786+
forConstruction: bool = False,
37013787
) -> "Workplane":
37023788
"""
37033789
Creates a 2D offset wire.
3704-
3705-
:param float d: thickness. Negative thickness denotes offset to inside.
3790+
3791+
:param d: thickness. Negative thickness denotes offset to inside.
37063792
:param kind: offset kind. Use "arc" for rounded and "intersection" for sharp edges (default: "arc")
3707-
3793+
:param forConstruction: Should the result be added to pending wires?
3794+
37083795
:return: CQ object with resulting wire(s).
37093796
"""
37103797

37113798
ws = self._consolidateWires()
37123799
rv = list(chain.from_iterable(w.offset2D(d, kind) for w in ws))
37133800

37143801
self.ctx.pendingEdges = []
3715-
self.ctx.pendingWires = rv
3802+
if forConstruction:
3803+
for wire in rv:
3804+
wire.forConstruction = True
3805+
self.ctx.pendingWires = []
3806+
else:
3807+
self.ctx.pendingWires = rv
37163808

37173809
return self.newObject(rv)
37183810

cadquery/occ_impl/importers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,14 @@ def importDXF(filename, tol=1e-6, exclude=[]):
223223
:param exclude: a list of layer names not to import (default: [])
224224
"""
225225

226+
# normalize layer names to conform the DXF spec
227+
exclude_lwr = [ex.lower() for ex in exclude]
228+
226229
dxf = ezdxf.readfile(filename)
227230
faces = []
228231

229232
for name, layer in dxf.modelspace().groupby(dxfattrib="layer").items():
230-
res = _dxf_convert(layer, tol) if name not in exclude else None
233+
res = _dxf_convert(layer, tol) if name.lower() not in exclude_lwr else None
231234
if res:
232235
wire_sets = sortWiresByBuildOrder(res)
233236
for wire_set in wire_sets:

0 commit comments

Comments
 (0)