3333 List ,
3434 cast ,
3535 Dict ,
36+ Iterator ,
3637)
3738from typing_extensions import Literal
38- from inspect import Parameter , Signature , isbuiltin
39+ from inspect import Parameter , Signature
3940
4041
4142from .occ_impl .geom import Vector , Plane , Location
5152)
5253
5354from .occ_impl .exporters .svg import getSVG , exportSVG
55+ from .occ_impl .exporters import export
5456
55- from .utils import deprecate , deprecate_kwarg_name
57+ from .utils import deprecate , deprecate_kwarg_name , get_arity
5658
5759from .selectors import (
5860 Selector ,
@@ -270,7 +272,7 @@ def split(self: T, keepTop: bool = False, keepBottom: bool = False) -> T:
270272 ...
271273
272274 @overload
273- def split (self : T , splitter : Union [T , Shape ]) -> T :
275+ def split (self : T , splitter : Union ["Workplane" , Shape ]) -> T :
274276 ...
275277
276278 def split (self : T , * args , ** kwargs ) -> T :
@@ -383,9 +385,7 @@ def combineSolids(
383385 raise ValueError ("Cannot Combine: at least one solid required!" )
384386
385387 # get context solid and we don't want to find our own objects
386- ctxSolid = self ._findType (
387- (Solid , Compound ), searchStack = False , searchParents = True
388- )
388+ ctxSolid = self ._findType ((Solid ,), searchStack = False , searchParents = True )
389389 if ctxSolid is None :
390390 ctxSolid = toCombine .pop (0 )
391391
@@ -748,8 +748,20 @@ def end(self, n: int = 1) -> "Workplane":
748748 def _findType (self , types , searchStack = True , searchParents = True ):
749749
750750 if searchStack :
751- rv = [s for s in self .objects if isinstance (s , types )]
752- if rv and types == (Solid , Compound ):
751+ rv = []
752+ for obj in self .objects :
753+ if isinstance (obj , types ):
754+ rv .append (obj )
755+ # unpack compounds in a special way when looking for Solids
756+ elif isinstance (obj , Compound ) and types == (Solid ,):
757+ for T in types :
758+ # _entities(...) needed due to weird behavior with shelled object unpacking
759+ rv .extend (T (el ) for el in obj ._entities (T .__name__ ))
760+ # otherwise unpack compounds normally
761+ elif isinstance (obj , Compound ):
762+ rv .extend (el for el in obj if isinstance (el , type ))
763+
764+ if rv and types == (Solid ,):
753765 return Compound .makeCompound (rv )
754766 elif rv :
755767 return rv [0 ]
@@ -781,7 +793,7 @@ def findSolid(
781793 results with an object already on the stack.
782794 """
783795
784- found = self ._findType ((Solid , Compound ), searchStack , searchParents )
796+ found = self ._findType ((Solid ,), searchStack , searchParents )
785797
786798 if found is None :
787799 message = "on the stack or " if searchStack else ""
@@ -802,7 +814,7 @@ def findFace(self, searchStack: bool = True, searchParents: bool = True) -> Face
802814 :returns: A face or None if no face is found.
803815 """
804816
805- found = self ._findType (Face , searchStack , searchParents )
817+ found = self ._findType (( Face ,) , searchStack , searchParents )
806818
807819 if found is None :
808820 message = "on the stack or " if searchStack else ""
@@ -1460,8 +1472,8 @@ def rarray(
14601472 If you want to position the array at another point, create another workplane
14611473 that is shifted to the position you would like to use as a reference
14621474
1463- :param xSpacing: spacing between points in the x direction ( must be > 0)
1464- :param ySpacing: spacing between points in the y direction ( must be > 0)
1475+ :param xSpacing: spacing between points in the x direction ( must be >= 0)
1476+ :param ySpacing: spacing between points in the y direction ( must be >= 0)
14651477 :param xCount: number of points ( > 0 )
14661478 :param yCount: number of points ( > 0 )
14671479 :param center: If True, the array will be centered around the workplane center.
@@ -1470,8 +1482,8 @@ def rarray(
14701482 centering along each axis.
14711483 """
14721484
1473- if xSpacing <= 0 or ySpacing <= 0 or xCount < 1 or yCount < 1 :
1474- raise ValueError ("Spacing and count must be > 0 " )
1485+ if ( xSpacing <= 0 and ySpacing <= 0 ) or xCount < 1 or yCount < 1 :
1486+ raise ValueError ("Spacing and count must be > 0 in at least one direction " )
14751487
14761488 if isinstance (center , bool ):
14771489 center = (center , center )
@@ -3310,9 +3322,7 @@ def _fuseWithBase(self: T, obj: Shape) -> T:
33103322 :return: a new object that represents the result of combining the base object with obj,
33113323 or obj if one could not be found
33123324 """
3313- baseSolid = self ._findType (
3314- (Solid , Compound ), searchStack = True , searchParents = True
3315- )
3325+ baseSolid = self ._findType ((Solid ,), searchStack = True , searchParents = True )
33163326 r = obj
33173327 if baseSolid is not None :
33183328 r = baseSolid .fuse (obj )
@@ -3328,7 +3338,7 @@ def _cutFromBase(self: T, obj: Shape) -> T:
33283338 :return: a new object that represents the result of combining the base object with obj,
33293339 or obj if one could not be found
33303340 """
3331- baseSolid = self ._findType ((Solid , Compound ), True , True )
3341+ baseSolid = self ._findType ((Solid ,), True , True )
33323342
33333343 r = obj
33343344 if baseSolid is not None :
@@ -3399,9 +3409,7 @@ def union(
33993409
34003410 # now combine with existing solid, if there is one
34013411 # look for parents to cut from
3402- solidRef = self ._findType (
3403- (Solid , Compound ), searchStack = True , searchParents = True
3404- )
3412+ solidRef = self ._findType ((Solid ,), searchStack = True , searchParents = True )
34053413 if solidRef is not None :
34063414 r = solidRef .fuse (* newS , glue = glue , tol = tol )
34073415 elif len (newS ) > 1 :
@@ -3414,7 +3422,8 @@ def union(
34143422
34153423 return self .newObject ([r ])
34163424
3417- def __or__ (self : T , toUnion : Union ["Workplane" , Solid , Compound ]) -> T :
3425+ @deprecate ()
3426+ def __or__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
34183427 """
34193428 Syntactic sugar for union.
34203429
@@ -3426,15 +3435,15 @@ def __or__(self: T, toUnion: Union["Workplane", Solid, Compound]) -> T:
34263435 Sphere = Workplane("XY").sphere(1)
34273436 result = Box | Sphere
34283437 """
3429- return self .union (toUnion )
3438+ return self .union (other )
34303439
3431- def __add__ (self : T , toUnion : Union ["Workplane" , Solid , Compound ]) -> T :
3440+ def __add__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
34323441 """
34333442 Syntactic sugar for union.
34343443
34353444 Notice that :code:`r = a + b` is equivalent to :code:`r = a.union(b)` and :code:`r = a | b`.
34363445 """
3437- return self .union (toUnion )
3446+ return self .union (other )
34383447
34393448 def cut (
34403449 self : T ,
@@ -3472,7 +3481,7 @@ def cut(
34723481
34733482 return self .newObject ([newS ])
34743483
3475- def __sub__ (self : T , toUnion : Union ["Workplane" , Solid , Compound ]) -> T :
3484+ def __sub__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
34763485 """
34773486 Syntactic sugar for cut.
34783487
@@ -3484,7 +3493,7 @@ def __sub__(self: T, toUnion: Union["Workplane", Solid, Compound]) -> T:
34843493 Sphere = Workplane("XY").sphere(1)
34853494 result = Box - Sphere
34863495 """
3487- return self .cut (toUnion )
3496+ return self .cut (other )
34883497
34893498 def intersect (
34903499 self : T ,
@@ -3522,7 +3531,8 @@ def intersect(
35223531
35233532 return self .newObject ([newS ])
35243533
3525- def __and__ (self : T , toUnion : Union ["Workplane" , Solid , Compound ]) -> T :
3534+ @deprecate ()
3535+ def __and__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
35263536 """
35273537 Syntactic sugar for intersect.
35283538
@@ -3534,7 +3544,38 @@ def __and__(self: T, toUnion: Union["Workplane", Solid, Compound]) -> T:
35343544 Sphere = Workplane("XY").sphere(1)
35353545 result = Box & Sphere
35363546 """
3537- return self .intersect (toUnion )
3547+
3548+ return self .intersect (other )
3549+
3550+ def __mul__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
3551+ """
3552+ Syntactic sugar for intersect.
3553+
3554+ Notice that :code:`r = a * b` is equivalent to :code:`r = a.intersect(b)`.
3555+
3556+ Example::
3557+
3558+ Box = Workplane("XY").box(1, 1, 1, centered=(False, False, False))
3559+ Sphere = Workplane("XY").sphere(1)
3560+ result = Box * Sphere
3561+ """
3562+
3563+ return self .intersect (other )
3564+
3565+ def __truediv__ (self : T , other : Union ["Workplane" , Solid , Compound ]) -> T :
3566+ """
3567+ Syntactic sugar for intersect.
3568+
3569+ Notice that :code:`r = a / b` is equivalent to :code:`r = a.split(b)`.
3570+
3571+ Example::
3572+
3573+ Box = Workplane("XY").box(1, 1, 1, centered=(False, False, False))
3574+ Sphere = Workplane("XY").sphere(1)
3575+ result = Box / Sphere
3576+ """
3577+
3578+ return self .split (other )
35383579
35393580 def cutBlind (
35403581 self : T ,
@@ -3680,6 +3721,10 @@ def _getFaces(self) -> List[Face]:
36803721 for el in self .objects :
36813722 if isinstance (el , Sketch ):
36823723 rv .extend (el )
3724+ elif isinstance (el , Face ):
3725+ rv .append (el )
3726+ elif isinstance (el , Compound ):
3727+ rv .extend (subel for subel in el if isinstance (subel , Face ))
36833728
36843729 if not rv :
36853730 rv .extend (wiresToFaces (self .ctx .popPendingWires ()))
@@ -4441,6 +4486,19 @@ def __getitem__(self: T, item: Union[int, Sequence[int], slice]) -> T:
44414486
44424487 return rv
44434488
4489+ def __iter__ (self : T ) -> Iterator [Shape ]:
4490+ """
4491+ Special method for iterating over Shapes in objects
4492+ """
4493+
4494+ for el in self .objects :
4495+ if isinstance (el , Compound ):
4496+ yield from el
4497+ elif isinstance (el , Shape ):
4498+ yield el
4499+ elif isinstance (el , Sketch ):
4500+ yield from el
4501+
44444502 def filter (self : T , f : Callable [[CQObject ], bool ]) -> T :
44454503 """
44464504 Filter items using a boolean predicate.
@@ -4488,11 +4546,7 @@ def invoke(
44884546 :return: Workplane object.
44894547 """
44904548
4491- if isbuiltin (f ):
4492- arity = 0 # assume 0 arity for builtins; they cannot be introspected
4493- else :
4494- arity = f .__code__ .co_argcount # NB: this is not understood by mypy
4495-
4549+ arity = get_arity (f )
44964550 rv = self
44974551
44984552 if arity == 0 :
@@ -4506,6 +4560,29 @@ def invoke(
45064560
45074561 return rv
45084562
4563+ def export (
4564+ self : T ,
4565+ fname : str ,
4566+ tolerance : float = 0.1 ,
4567+ angularTolerance : float = 0.1 ,
4568+ opt : Optional [Dict [str , Any ]] = None ,
4569+ ) -> T :
4570+ """
4571+ Export Workplane to file.
4572+
4573+ :param path: Filename.
4574+ :param tolerance: the deflection tolerance, in model units. Default 0.1.
4575+ :param angularTolerance: the angular tolerance, in radians. Default 0.1.
4576+ :param opt: additional options passed to the specific exporter. Default None.
4577+ :return: Self.
4578+ """
4579+
4580+ export (
4581+ self , fname , tolerance = tolerance , angularTolerance = angularTolerance , opt = opt
4582+ )
4583+
4584+ return self
4585+
45094586
45104587# alias for backward compatibility
45114588CQ = Workplane
0 commit comments