279
279
280
280
from OCP .LProp3d import LProp3d_CLProps
281
281
282
+ from OCP .BinTools import BinTools
283
+
282
284
from math import pi , sqrt , inf , radians , cos
283
285
284
286
import warnings
@@ -520,6 +522,26 @@ def importBrep(cls, f: Union[str, BytesIO]) -> "Shape":
520
522
521
523
return cls .cast (s )
522
524
525
+ def exportBin (self , f : Union [str , BytesIO ]) -> bool :
526
+ """
527
+ Export this shape to a binary BREP file.
528
+ """
529
+
530
+ rv = BinTools .Write_s (self .wrapped , f )
531
+
532
+ return True if rv is None else rv
533
+
534
+ @classmethod
535
+ def importBin (cls , f : Union [str , BytesIO ]) -> "Shape" :
536
+ """
537
+ Import shape from a binary BREP file.
538
+ """
539
+ s = TopoDS_Shape ()
540
+
541
+ BinTools .Read_s (s , f )
542
+
543
+ return cls .cast (s )
544
+
523
545
def geomType (self ) -> Geoms :
524
546
"""
525
547
Gets the underlying geometry type.
@@ -4528,6 +4550,7 @@ def _normalize(s: Shape) -> Shape:
4528
4550
"""
4529
4551
Apply some normalizations:
4530
4552
- Shell with only one Face -> Face.
4553
+ - Compound with only one element -> element.
4531
4554
"""
4532
4555
4533
4556
t = s .ShapeType ()
@@ -4724,17 +4747,26 @@ def shell(s: Sequence[Shape], tol: float = 1e-6) -> Shape:
4724
4747
4725
4748
4726
4749
@multimethod
4727
- def solid (* s : Shape , tol : float = 1e-6 ) -> Shape :
4750
+ def solid (s1 : Shape , * sn : Shape , tol : float = 1e-6 ) -> Shape :
4728
4751
"""
4729
- Build solid from faces.
4752
+ Build solid from faces or shells .
4730
4753
"""
4731
4754
4732
4755
builder = ShapeFix_Solid ()
4733
4756
4734
- faces = [f for el in s for f in _get (el , "Face" )]
4735
- rv = builder .SolidFromShell (shell (* faces , tol = tol ).wrapped )
4757
+ # get both Shells and Faces
4758
+ s = [s1 , * sn ]
4759
+ shells_faces = [f for el in s for f in _get (el , ("Shell" , "Face" ))]
4736
4760
4737
- return _compound_or_shape (rv )
4761
+ # if no shells are present, use faces to construct them
4762
+ shells = [el .wrapped for el in shells_faces if el .ShapeType () == "Shell" ]
4763
+ if not shells :
4764
+ faces = [el for el in shells_faces ]
4765
+ shells = [shell (* faces , tol = tol ).wrapped ]
4766
+
4767
+ rvs = [builder .SolidFromShell (sh ) for sh in shells ]
4768
+
4769
+ return _compound_or_shape (rvs )
4738
4770
4739
4771
4740
4772
@solid .register
@@ -4922,9 +4954,10 @@ def ellipse(r1: float, r2: float) -> Shape:
4922
4954
)
4923
4955
4924
4956
4925
- def plane (w : float , l : float ) -> Shape :
4957
+ @multimethod
4958
+ def plane (w : Real , l : Real ) -> Shape :
4926
4959
"""
4927
- Construct a planar face.
4960
+ Construct a finite planar face.
4928
4961
"""
4929
4962
4930
4963
pln_geom = gp_Pln (Vector (0 , 0 , 0 ).toPnt (), Vector (0 , 0 , 1 ).toDir ())
@@ -4934,6 +4967,24 @@ def plane(w: float, l: float) -> Shape:
4934
4967
)
4935
4968
4936
4969
4970
+ @plane .register
4971
+ def plane () -> Shape :
4972
+ """
4973
+ Construct an infinite planar face.
4974
+
4975
+ This is a crude approximation. Truly infinite faces in OCCT do not work as
4976
+ expected in all contexts.
4977
+ """
4978
+
4979
+ INF = 1e60
4980
+
4981
+ pln_geom = gp_Pln (Vector (0 , 0 , 0 ).toPnt (), Vector (0 , 0 , 1 ).toDir ())
4982
+
4983
+ return _compound_or_shape (
4984
+ BRepBuilderAPI_MakeFace (pln_geom , - INF , INF , - INF , INF ).Face ()
4985
+ )
4986
+
4987
+
4937
4988
def box (w : float , l : float , h : float ) -> Shape :
4938
4989
"""
4939
4990
Construct a solid box.
@@ -5012,9 +5063,10 @@ def cone(d: Real, h: Real) -> Shape:
5012
5063
return cone (d , 0 , h )
5013
5064
5014
5065
5066
+ @multimethod
5015
5067
def text (
5016
5068
txt : str ,
5017
- size : float ,
5069
+ size : Real ,
5018
5070
font : str = "Arial" ,
5019
5071
path : Optional [str ] = None ,
5020
5072
kind : Literal ["regular" , "bold" , "italic" ] = "regular" ,
@@ -5065,7 +5117,67 @@ def text(
5065
5117
font_i , NCollection_Utf8String (txt ), theHAlign = theHAlign , theVAlign = theVAlign
5066
5118
)
5067
5119
5068
- return clean (_compound_or_shape (rv ).faces ().fuse ())
5120
+ return clean (compound (_compound_or_shape (rv ).faces ()).fuse ())
5121
+
5122
+
5123
+ @text .register
5124
+ def text (
5125
+ txt : str ,
5126
+ size : Real ,
5127
+ spine : Shape ,
5128
+ planar : bool = False ,
5129
+ font : str = "Arial" ,
5130
+ path : Optional [str ] = None ,
5131
+ kind : Literal ["regular" , "bold" , "italic" ] = "regular" ,
5132
+ halign : Literal ["center" , "left" , "right" ] = "center" ,
5133
+ valign : Literal ["center" , "top" , "bottom" ] = "center" ,
5134
+ ) -> Shape :
5135
+ """
5136
+ Create a text on a spine.
5137
+ """
5138
+
5139
+ spine = _get_one_wire (spine )
5140
+ L = spine .Length ()
5141
+
5142
+ rv = []
5143
+ for el in text (txt , size , font , path , kind , halign , valign ):
5144
+ pos = el .BoundingBox ().center .x
5145
+
5146
+ # position
5147
+ rv .append (
5148
+ el .moved (- pos )
5149
+ .moved (rx = - 90 if planar else 0 , ry = - 90 )
5150
+ .moved (spine .locationAt (pos / L ))
5151
+ )
5152
+
5153
+ return _normalize (compound (rv ))
5154
+
5155
+
5156
+ @text .register
5157
+ def text (
5158
+ txt : str ,
5159
+ size : Real ,
5160
+ spine : Shape ,
5161
+ base : Shape ,
5162
+ font : str = "Arial" ,
5163
+ path : Optional [str ] = None ,
5164
+ kind : Literal ["regular" , "bold" , "italic" ] = "regular" ,
5165
+ halign : Literal ["center" , "left" , "right" ] = "center" ,
5166
+ valign : Literal ["center" , "top" , "bottom" ] = "center" ,
5167
+ ) -> Shape :
5168
+ """
5169
+ Create a text on a spine and a base surface.
5170
+ """
5171
+
5172
+ base = _get_one (base , "Face" )
5173
+
5174
+ tmp = text (txt , size , spine , False , font , path , kind , halign , valign )
5175
+
5176
+ rv = []
5177
+ for f in tmp .faces ():
5178
+ rv .append (f .project (base , f .normalAt ()))
5179
+
5180
+ return _normalize (compound (rv ))
5069
5181
5070
5182
5071
5183
#%% ops
@@ -5266,33 +5378,56 @@ def revolve(s: Shape, p: VectorLike, d: VectorLike, a: float = 360):
5266
5378
return _compound_or_shape (results )
5267
5379
5268
5380
5269
- def offset (s : Shape , t : float , cap = True , tol : float = 1e-6 ) -> Shape :
5381
+ def offset (
5382
+ s : Shape , t : float , cap = True , both : bool = False , tol : float = 1e-6
5383
+ ) -> Shape :
5270
5384
"""
5271
5385
Offset or thicken faces or shells.
5272
5386
"""
5273
5387
5274
- builder = BRepOffset_MakeOffset ()
5388
+ def _offset ( t ):
5275
5389
5276
- results = []
5390
+ results = []
5277
5391
5278
- for el in _get (s , ("Face" , "Shell" )):
5392
+ for el in _get (s , ("Face" , "Shell" )):
5279
5393
5280
- builder .Initialize (
5281
- el .wrapped ,
5282
- t ,
5283
- tol ,
5284
- BRepOffset_Mode .BRepOffset_Skin ,
5285
- False ,
5286
- False ,
5287
- GeomAbs_Intersection ,
5288
- cap ,
5289
- )
5394
+ builder = BRepOffset_MakeOffset ()
5290
5395
5291
- builder .MakeOffsetShape ()
5396
+ builder .Initialize (
5397
+ el .wrapped ,
5398
+ t ,
5399
+ tol ,
5400
+ BRepOffset_Mode .BRepOffset_Skin ,
5401
+ False ,
5402
+ False ,
5403
+ GeomAbs_Intersection ,
5404
+ cap ,
5405
+ )
5292
5406
5293
- results . append ( builder .Shape () )
5407
+ builder .MakeOffsetShape ( )
5294
5408
5295
- return _compound_or_shape (results )
5409
+ results .append (builder .Shape ())
5410
+
5411
+ return results
5412
+
5413
+ if both :
5414
+ results_pos = _offset (t )
5415
+ results_neg = _offset (- t )
5416
+
5417
+ results_both = [
5418
+ Shape (el1 ) + Shape (el2 ) for el1 , el2 in zip (results_pos , results_neg )
5419
+ ]
5420
+
5421
+ if len (results_both ) == 1 :
5422
+ rv = results_both [0 ]
5423
+ else :
5424
+ rv = Compound .makeCompound (results_both )
5425
+
5426
+ else :
5427
+ results = _offset (t )
5428
+ rv = _compound_or_shape (results )
5429
+
5430
+ return rv
5296
5431
5297
5432
5298
5433
@multimethod
0 commit comments