fromtypingimport(Optional,Tuple,Union,Iterable,List,Sequence,Iterator,Dict,Any,overload,TypeVar,castastcast,Literal,Protocol,)fromtyping_extensionsimportSelffromioimportBytesIOfromvtkmodules.vtkCommonDataModelimportvtkPolyDatafromvtkmodules.vtkFiltersCoreimportvtkTriangleFilter,vtkPolyDataNormalsfrom.geomimportVector,VectorLike,BoundBox,Plane,Location,Matrixfrom.shape_protocolsimportgeom_LUT_FACE,geom_LUT_EDGE,Shapes,Geomsfrom..selectorsimport(Selector,StringSyntaxSelector,)from..utilsimportmultimethod# change default OCCT logging levelfromOCP.MessageimportMessage,Message_GravityforprinterinMessage.DefaultMessenger_s().Printers():printer.SetTraceLevel(Message_Gravity.Message_Fail)importOCP.TopAbsasta# Topology type enumimportOCP.GeomAbsasga# Geometry type enumfromOCP.PrecisionimportPrecisionfromOCP.gpimport(gp_Vec,gp_Pnt,gp_Ax1,gp_Ax2,gp_Ax3,gp_Dir,gp_Circ,gp_Trsf,gp_Pln,gp_Pnt2d,gp_Dir2d,gp_Elips,)# Array of points (used for B-spline construction):fromOCP.TColgpimport(TColgp_HArray1OfPnt,TColgp_HArray2OfPnt,TColgp_Array1OfPnt,TColgp_HArray1OfPnt2d,)# Array of vectors (used for B-spline interpolation):fromOCP.TColgpimportTColgp_Array1OfVec# Array of booleans (used for B-spline interpolation):fromOCP.TColStdimportTColStd_HArray1OfBoolean# Array of floats (used for B-spline interpolation):fromOCP.TColStdimportTColStd_HArray1OfRealfromOCP.BRepAdaptorimport(BRepAdaptor_Curve,BRepAdaptor_CompCurve,BRepAdaptor_Surface,)fromOCP.BRepBuilderAPIimport(BRepBuilderAPI_MakeVertex,BRepBuilderAPI_MakeEdge,BRepBuilderAPI_MakeFace,BRepBuilderAPI_MakePolygon,BRepBuilderAPI_MakeWire,BRepBuilderAPI_Sewing,BRepBuilderAPI_Copy,BRepBuilderAPI_GTransform,BRepBuilderAPI_Transform,BRepBuilderAPI_Transformed,BRepBuilderAPI_RightCorner,BRepBuilderAPI_RoundCorner,BRepBuilderAPI_MakeSolid,BRepBuilderAPI_NurbsConvert,)# properties used to store mass calculation resultfromOCP.GPropimportGProp_GPropsfromOCP.BRepGPropimportBRepGProp_Face,BRepGProp# used for mass calculationfromOCP.BRepPrimAPIimport(BRepPrimAPI_MakeBox,BRepPrimAPI_MakeCone,BRepPrimAPI_MakeCylinder,BRepPrimAPI_MakeTorus,BRepPrimAPI_MakeWedge,BRepPrimAPI_MakePrism,BRepPrimAPI_MakeRevol,BRepPrimAPI_MakeSphere,)fromOCP.BRepIntCurveSurfaceimportBRepIntCurveSurface_InterfromOCP.TopExpimportTopExp# Topology explorer# used for getting underlying geometry -- is this equivalent to brep adaptor?fromOCP.BRepimportBRep_Tool,BRep_BuilderfromOCP.TopoDSimport(TopoDS,TopoDS_Shape,TopoDS_Builder,TopoDS_Compound,TopoDS_Iterator,TopoDS_Wire,TopoDS_Face,TopoDS_Edge,TopoDS_Vertex,TopoDS_Solid,TopoDS_Shell,TopoDS_CompSolid,)fromOCP.GCimportGC_MakeArcOfCircle,GC_MakeArcOfEllipse# geometry constructionfromOCP.GCE2dimportGCE2d_MakeSegmentfromOCP.gceimportgce_MakeLin,gce_MakeDirfromOCP.GeomAPIimport(GeomAPI_Interpolate,GeomAPI_ProjectPointOnSurf,GeomAPI_ProjectPointOnCurve,GeomAPI_PointsToBSpline,GeomAPI_PointsToBSplineSurface,)fromOCP.BRepFillimportBRepFillfromOCP.BRepAlgoAPIimport(BRepAlgoAPI_Common,BRepAlgoAPI_Fuse,BRepAlgoAPI_Cut,BRepAlgoAPI_BooleanOperation,BRepAlgoAPI_Splitter,BRepAlgoAPI_Check,)fromOCP.Geomimport(Geom_BezierCurve,Geom_ConicalSurface,Geom_CylindricalSurface,Geom_Surface,Geom_Plane,Geom_BSplineCurve,Geom_Curve,)fromOCP.Geom2dimportGeom2d_LinefromOCP.Geom2dAPIimportGeom2dAPI_InterpolatefromOCP.BRepLibimportBRepLib,BRepLib_FindSurfacefromOCP.BRepOffsetAPIimport(BRepOffsetAPI_ThruSections,BRepOffsetAPI_MakePipeShell,BRepOffsetAPI_MakeThickSolid,BRepOffsetAPI_MakeOffset,)fromOCP.BRepFilletAPIimport(BRepFilletAPI_MakeChamfer,BRepFilletAPI_MakeFillet,BRepFilletAPI_MakeFillet2d,)fromOCP.TopToolsimport(TopTools_IndexedDataMapOfShapeListOfShape,TopTools_ListOfShape,TopTools_MapOfShape,TopTools_IndexedMapOfShape,)fromOCP.ShapeFiximportShapeFix_Shape,ShapeFix_Solid,ShapeFix_FacefromOCP.STEPControlimportSTEPControl_Writer,STEPControl_AsIsfromOCP.BRepMeshimportBRepMesh_IncrementalMeshfromOCP.StlAPIimportStlAPI_WriterfromOCP.ShapeUpgradeimportShapeUpgrade_UnifySameDomainfromOCP.BRepToolsimport(BRepTools,BRepTools_WireExplorer,BRepTools_ReShape,)fromOCP.LocOpeimportLocOpe_DPrismfromOCP.BRepCheckimportBRepCheck_AnalyzerfromOCP.Fontimport(Font_FontMgr,Font_FA_Regular,Font_FA_Italic,Font_FA_Bold,Font_SystemFont,)fromOCP.StdPrsimportStdPrs_BRepFont,StdPrs_BRepTextBuilderasFont_BRepTextBuilderfromOCP.Graphic3dimport(Graphic3d_HTA_LEFT,Graphic3d_HTA_CENTER,Graphic3d_HTA_RIGHT,Graphic3d_VTA_BOTTOM,Graphic3d_VTA_CENTER,Graphic3d_VTA_TOP,)fromOCP.NCollectionimportNCollection_Utf8StringfromOCP.BRepFeatimportBRepFeat_MakeDPrismfromOCP.BRepClass3dimportBRepClass3d_SolidClassifier,BRepClass3dfromOCP.TCollectionimportTCollection_AsciiStringfromOCP.TopLocimportTopLoc_LocationfromOCP.GeomAbsimport(GeomAbs_Shape,GeomAbs_C0,GeomAbs_G2,GeomAbs_C2,GeomAbs_Intersection,GeomAbs_JoinType,GeomAbs_IsoType,GeomAbs_CurveType,)fromOCP.BRepOffsetAPIimportBRepOffsetAPI_MakeFillingfromOCP.BRepOffsetimportBRepOffset_MakeOffset,BRepOffset_ModefromOCP.BOPAlgoimport(BOPAlgo_GlueEnum,BOPAlgo_Builder,BOPAlgo_BOP,BOPAlgo_FUSE,BOPAlgo_CUT,BOPAlgo_COMMON,)fromOCP.IFSelectimportIFSelect_ReturnStatusfromOCP.TopAbsimportTopAbs_ShapeEnum,TopAbs_OrientationfromOCP.ShapeAnalysisimport(ShapeAnalysis_FreeBounds,ShapeAnalysis_Edge,ShapeAnalysis_Wire,ShapeAnalysis_Surface,)fromOCP.TopToolsimportTopTools_HSequenceOfShapefromOCP.GCPntsimport(GCPnts_AbscissaPoint,GCPnts_QuasiUniformAbscissa,GCPnts_QuasiUniformDeflection,)fromOCP.GeomFillimport(GeomFill_Frenet,GeomFill_CorrectedFrenet,GeomFill_TrihedronLaw,)fromOCP.BRepProjimportBRepProj_ProjectionfromOCP.BRepExtremaimportBRepExtrema_DistShapeShapefromOCP.IVtkOCCimportIVtkOCC_Shape,IVtkOCC_ShapeMesherfromOCP.IVtkVTKimportIVtkVTK_ShapeData# for catching exceptionsfromOCP.StandardimportStandard_NoSuchObject,Standard_FailurefromOCP.Prs3dimportPrs3d_IsoAspectfromOCP.QuantityimportQuantity_ColorfromOCP.AspectimportAspect_TOL_SOLIDfromOCP.InterfaceimportInterface_StaticfromOCP.ShapeCustomimportShapeCustom,ShapeCustom_RestrictionParametersfromOCP.BRepAlgoimportBRepAlgo,BRepAlgo_NormalProjectionfromOCP.ChFi2dimportChFi2d_FilletAPI# For Wire.Fillet()fromOCP.GeomConvertimportGeomConvert_ApproxCurvefromOCP.ApproximportApprox_ParametrizationTypefromOCP.LProp3dimportLProp3d_CLPropsfromOCP.BinToolsimportBinToolsfromOCP.Adaptor3dimportAdaptor3d_IsoCurve,Adaptor3d_CurvefromOCP.GeomAdaptorimportGeomAdaptor_SurfacefromOCP.OSDimportOSD_ThreadPoolfrommathimportpi,sqrt,inf,radians,cosimportwarningsfrom..utilsimportdeprecateReal=Union[float,int]GlueLiteral=Literal["partial","full",None]TOLERANCE=1e-6shape_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_COMPSOLID:"CompSolid",ta.TopAbs_COMPOUND:"Compound",}shape_properties_LUT={ta.TopAbs_VERTEX:None,ta.TopAbs_EDGE:BRepGProp.LinearProperties_s,ta.TopAbs_WIRE:BRepGProp.LinearProperties_s,ta.TopAbs_FACE:BRepGProp.SurfaceProperties_s,ta.TopAbs_SHELL:BRepGProp.SurfaceProperties_s,ta.TopAbs_SOLID:BRepGProp.VolumeProperties_s,ta.TopAbs_COMPOUND:BRepGProp.VolumeProperties_s,}inverse_shape_LUT={v:kfork,vinshape_LUT.items()}downcast_LUT={ta.TopAbs_VERTEX:TopoDS.Vertex_s,ta.TopAbs_EDGE:TopoDS.Edge_s,ta.TopAbs_WIRE:TopoDS.Wire_s,ta.TopAbs_FACE:TopoDS.Face_s,ta.TopAbs_SHELL:TopoDS.Shell_s,ta.TopAbs_SOLID:TopoDS.Solid_s,ta.TopAbs_COMPSOLID:TopoDS.CompSolid_s,ta.TopAbs_COMPOUND:TopoDS.Compound_s,}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_SOLID:"CompSolid",ta.TopAbs_COMPOUND:"Compound",}ancestors_LUT={"Vertex":ta.TopAbs_EDGE,"Edge":ta.TopAbs_WIRE,"Wire":ta.TopAbs_FACE,"Face":ta.TopAbs_SHELL,"Shell":ta.TopAbs_SOLID,}T=TypeVar("T",bound="Shape")defshapetype(obj:TopoDS_Shape)->TopAbs_ShapeEnum:ifobj.IsNull():raiseValueError("Null TopoDS_Shape object")returnobj.ShapeType()[docs]defdowncast(obj:TopoDS_Shape)->TopoDS_Shape:""" Downcasts a TopoDS object to suitable specialized type """f_downcast:Any=downcast_LUT[shapetype(obj)]rv=f_downcast(obj)returnrv [docs]deffix(obj:TopoDS_Shape)->TopoDS_Shape:""" Fix a TopoDS object to suitable specialized type """sf=ShapeFix_Shape(obj)sf.Perform()returndowncast(sf.Shape()) [docs]classShape(object):""" Represents a shape in the system. Wraps TopoDS_Shape. """wrapped:TopoDS_ShapeforConstruction:bool[docs]def__init__(self,obj:TopoDS_Shape):self.wrapped=downcast(obj)self.forConstruction=False# Helps identify this solid through the use of an IDself.label="" [docs]defclean(self:T)->T:"""Experimental clean using ShapeUpgrade"""upgrader=ShapeUpgrade_UnifySameDomain(self.wrapped,True,True,True)upgrader.AllowInternalEdges(False)upgrader.Build()returnself.__class__(upgrader.Shape()) [docs]deffix(self:T)->T:"""Try to fix shape if not valid"""ifnotself.isValid():fixed=fix(self.wrapped)returnself.__class__(fixed)returnself [docs]@classmethoddefcast(cls,obj:TopoDS_Shape,forConstruction:bool=False)->"Shape":"Returns the right type of wrapper, given a OCCT object"tr=None# define the shape lookup table for castingconstructor_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_COMPSOLID:CompSolid,ta.TopAbs_COMPOUND:Compound,}t=shapetype(obj)# NB downcast is needed to handle TopoDS_Shape typestr=constructor_LUT[t](downcast(obj))tr.forConstruction=forConstructionreturntr [docs]defexportStl(self,fileName:str,tolerance:float=1e-3,angularTolerance:float=0.1,ascii:bool=False,relative:bool=True,parallel:bool=True,)->bool:""" Exports a shape to a specified STL file. :param fileName: The path and file name to write the STL output to. :param tolerance: A linear deflection setting which limits the distance between a curve and its tessellation. Setting this value too low will result in large meshes that can consume computing resources. Setting the value too high can result in meshes with a level of detail that is too low. Default is 1e-3, which is a good starting point for a range of cases. :param angularTolerance: Angular deflection setting which limits the angle between subsequent segments in a polyline. Default is 0.1. :param ascii: Export the file as ASCII (True) or binary (False) STL format. Default is binary. :param relative: If True, tolerance will be scaled by the size of the edge being meshed. Default is True. Setting this value to True may cause large features to become faceted, or small features dense. :param parallel: If True, OCCT will use parallel processing to mesh the shape. Default is True. """# The constructor used here automatically calls mesh.Perform(). https://dev.opencascade.org/doc/refman/html/class_b_rep_mesh___incremental_mesh.html#a3a383b3afe164161a3aa59a492180ac6BRepMesh_IncrementalMesh(self.wrapped,tolerance,relative,angularTolerance,parallel)writer=StlAPI_Writer()writer.ASCIIMode=asciireturnwriter.Write(self.wrapped,fileName) [docs]defexportStep(self,fileName:str,**kwargs)->IFSelect_ReturnStatus:""" Export this shape to a STEP file. kwargs is used to provide optional keyword arguments to configure the exporter. :param fileName: Path and filename for writing. :param write_pcurves: Enable or disable writing parametric curves to the STEP file. Default True. If False, writes STEP file without pcurves. This decreases the size of the resulting STEP file. :type write_pcurves: bool :param precision_mode: Controls the uncertainty value for STEP entities. Specify -1, 0, or 1. Default 0. See OCCT documentation. :type precision_mode: int """# Handle the extra settings for the STEP exportpcurves=1if"write_pcurves"inkwargsandnotkwargs["write_pcurves"]:pcurves=0precision_mode=kwargs["precision_mode"]if"precision_mode"inkwargselse0writer=STEPControl_Writer()Interface_Static.SetIVal_s("write.surfacecurve.mode",pcurves)Interface_Static.SetIVal_s("write.precision.mode",precision_mode)writer.Transfer(self.wrapped,STEPControl_AsIs)returnwriter.Write(fileName) [docs]defexportBrep(self,f:Union[str,BytesIO])->bool:""" Export this shape to a BREP file """rv=BRepTools.Write_s(self.wrapped,f)returnTrueifrvisNoneelserv [docs]@classmethoddefimportBrep(cls,f:Union[str,BytesIO])->"Shape":""" Import shape from a BREP file """s=TopoDS_Shape()builder=BRep_Builder()BRepTools.Read_s(s,f,builder)ifs.IsNull():raiseValueError(f"Could not import{f}")returncls.cast(s) [docs]defexportBin(self,f:Union[str,BytesIO])->bool:""" Export this shape to a binary BREP file. """rv=BinTools.Write_s(self.wrapped,f)returnTrueifrvisNoneelserv [docs]@classmethoddefimportBin(cls,f:Union[str,BytesIO])->"Shape":""" Import shape from a binary BREP file. """s=TopoDS_Shape()BinTools.Read_s(s,f)returncls.cast(s) [docs]defgeomType(self)->Geoms:""" Gets the underlying geometry type. Implementations can return any values desired, but the values the user uses in type filters should correspond to these. As an example, if a user does:: CQ(object).faces("%mytype") The expectation is that the geomType attribute will return 'mytype' The return values depend on the type of the shape: | Vertex: always 'Vertex' | Edge: LINE, CIRCLE, ELLIPSE, HYPERBOLA, PARABOLA, BEZIER, | BSPLINE, OFFSET, OTHER | Face: PLANE, CYLINDER, CONE, SPHERE, TORUS, BEZIER, BSPLINE, | REVOLUTION, EXTRUSION, OFFSET, OTHER | Solid: 'Solid' | Shell: 'Shell' | Compound: 'Compound' | Wire: 'Wire' :returns: A string according to the geometry type """tr:Any=geom_LUT[shapetype(self.wrapped)]ifisinstance(tr,str):rv=treliftrisBRepAdaptor_Curve:rv=geom_LUT_EDGE[tr(self.wrapped).GetType()]else:rv=geom_LUT_FACE[tr(self.wrapped).GetType()]returntcast(Geoms,rv) [docs]defhashCode(self)->int:""" Returns a hashed value denoting this shape. It is computed from the TShape and the Location. The Orientation is not used. """returnhash(self.wrapped) [docs]defisNull(self)->bool:""" Returns true if this shape is null. In other words, it references no underlying shape with the potential to be given a location and an orientation. """returnself.wrapped.IsNull() [docs]defisSame(self,other:"Shape")->bool:""" Returns True if other and this shape are same, i.e. if they share the same TShape with the same Locations. Orientations may differ. Also see :py:meth:`isEqual` """returnself.wrapped.IsSame(other.wrapped) [docs]defisEqual(self,other:"Shape")->bool:""" Returns True if two shapes are equal, i.e. if they share the same TShape with the same Locations and Orientations. Also see :py:meth:`isSame`. """returnself.wrapped.IsEqual(other.wrapped) [docs]defisValid(self)->bool:""" Returns True if no defect is detected on the shape S or any of its subshapes. See the OCCT docs on BRepCheck_Analyzer::IsValid for a full description of what is checked. """returnBRepCheck_Analyzer(self.wrapped).IsValid() [docs]defBoundingBox(self,tolerance:Optional[float]=None)->BoundBox:# need to implement that in GEOM""" Create a bounding box for this Shape. :param tolerance: Tolerance value passed to :class:`BoundBox` :returns: A :class:`BoundBox` object for this Shape """returnBoundBox._fromTopoDS(self.wrapped,tol=tolerance) [docs]defmirror(self,mirrorPlane:Union[Literal["XY","YX","XZ","ZX","YZ","ZY"],VectorLike]="XY",basePointVector:VectorLike=(0,0,0),)->"Shape":""" Applies a mirror transform to this Shape. Does not duplicate objects about the plane. :param mirrorPlane: The direction of the plane to mirror about - one of 'XY', 'XZ' or 'YZ' :param basePointVector: The origin of the plane to mirror about :returns: The mirrored shape """ifisinstance(mirrorPlane,str):ifmirrorPlane=="XY"ormirrorPlane=="YX":mirrorPlaneNormalVector=gp_Dir(0,0,1)elifmirrorPlane=="XZ"ormirrorPlane=="ZX":mirrorPlaneNormalVector=gp_Dir(0,1,0)elifmirrorPlane=="YZ"ormirrorPlane=="ZY":mirrorPlaneNormalVector=gp_Dir(1,0,0)else:ifisinstance(mirrorPlane,tuple):mirrorPlaneNormalVector=gp_Dir(*mirrorPlane)elifisinstance(mirrorPlane,Vector):mirrorPlaneNormalVector=mirrorPlane.toDir()ifisinstance(basePointVector,tuple):basePointVector=Vector(basePointVector)T=gp_Trsf()T.SetMirror(gp_Ax2(gp_Pnt(*basePointVector.toTuple()),mirrorPlaneNormalVector))returnself._apply_transform(T) @staticmethoddef_center_of_mass(shape:"Shape")->Vector:Properties=GProp_GProps()BRepGProp.VolumeProperties_s(shape.wrapped,Properties)returnVector(Properties.CentreOfMass())[docs]@staticmethoddefmatrixOfInertia(obj:"Shape")->List[List[float]]:""" Calculates the matrix of inertia of an object. Since the part's density is unknown, this result is inertia/density with units of [1/length]. :param obj: Compute the matrix of inertia of this object """Properties=GProp_GProps()calc_function=shape_properties_LUT[shapetype(obj.wrapped)]ifcalc_function:calc_function(obj.wrapped,Properties)moi=Properties.MatrixOfInertia()return[[moi.Value(i,j)forjinrange(1,4)]foriinrange(1,4)]raiseNotImplementedError [docs]defCenter(self)->Vector:""" :returns: The point of the center of mass of this Shape """returnShape.centerOfMass(self) [docs]defCenterOfBoundBox(self,tolerance:Optional[float]=None)->Vector:""" :param tolerance: Tolerance passed to the :py:meth:`BoundingBox` method :returns: Center of the bounding box of this shape """returnself.BoundingBox(tolerance=tolerance).center [docs]@staticmethoddefCombinedCenter(objects:Iterable["Shape"])->Vector:""" Calculates the center of mass of multiple objects. :param objects: A list of objects with mass """total_mass=sum(Shape.computeMass(o)foroinobjects)weighted_centers=[Shape.centerOfMass(o).multiply(Shape.computeMass(o))foroinobjects]sum_wc=weighted_centers[0]forwcinweighted_centers[1:]:sum_wc=sum_wc.add(wc)returnVector(sum_wc.multiply(1.0/total_mass)) @staticmethoddef_mass_calc_function(obj:"Shape")->Any:""" Helper to find the correct mass calculation function with special compound handling. """type_=shapetype(obj.wrapped)# special handling of compounds - first non-compound child is assumed to define the type of the operationiftype_==ta.TopAbs_COMPOUND:# if the compound is not empty check its childrenifobj:# first childchild=next(iter(obj))# if compound, go deeperwhilechild.ShapeType()=="Compound":child=next(iter(child))type_=shapetype(child.wrapped)# if the compound is empty assume it was meant to be a solidelse:type_=ta.TopAbs_SOLID# get the function based on dimensionality of the objectreturnshape_properties_LUT[type_][docs]@staticmethoddefcomputeMass(obj:"Shape",tol:Optional[float]=None)->float:""" Calculates the 'mass' of an object. :param obj: Compute the mass of this object :param tol: Numerical integration tolerance (optional). """Properties=GProp_GProps()calc_function=Shape._mass_calc_function(obj)calc_function(obj.wrapped,Properties,*((tol,)iftolelse()))returnProperties.Mass() [docs]@staticmethoddefcenterOfMass(obj:"Shape")->Vector:""" Calculates the center of 'mass' of an object. :param obj: Compute the center of mass of this object """Properties=GProp_GProps()calc_function=Shape._mass_calc_function(obj)calc_function(obj.wrapped,Properties)returnVector(Properties.CentreOfMass()) [docs]@staticmethoddefCombinedCenterOfBoundBox(objects:List["Shape"])->Vector:""" Calculates the center of a bounding box of multiple objects. :param objects: A list of objects """total_mass=len(objects)weighted_centers=[]foroinobjects:weighted_centers.append(BoundBox._fromTopoDS(o.wrapped).center)sum_wc=weighted_centers[0]forwcinweighted_centers[1:]:sum_wc=sum_wc.add(wc)returnVector(sum_wc.multiply(1.0/total_mass)) [docs]defClosed(self)->bool:""" :returns: The closedness flag """returnself.wrapped.Closed() defShapeType(self)->Shapes:returntcast(Shapes,shape_LUT[shapetype(self.wrapped)])def_entities(self,topo_type:Shapes)->Iterable[TopoDS_Shape]:shape_set=TopTools_IndexedMapOfShape()TopExp.MapShapes_s(self.wrapped,inverse_shape_LUT[topo_type],shape_set)returntcast(Iterable[TopoDS_Shape],shape_set)def_entitiesFrom(self,child_type:Shapes,parent_type:Shapes)->Dict["Shape",List["Shape"]]:res=TopTools_IndexedDataMapOfShapeListOfShape()TopExp.MapShapesAndAncestors_s(self.wrapped,inverse_shape_LUT[child_type],inverse_shape_LUT[parent_type],res,)out:Dict[Shape,List[Shape]]={}foriinrange(1,res.Extent()+1):out[Shape.cast(res.FindKey(i))]=[Shape.cast(el)forelinres.FindFromIndex(i)]returnout[docs]defVertices(self)->List["Vertex"]:""" :returns: All the vertices in this Shape """return[Vertex(i)foriinself._entities("Vertex")] [docs]defEdges(self)->List["Edge"]:""" :returns: All the edges in this Shape """return[Edge(i)foriinself._entities("Edge")ifnotBRep_Tool.Degenerated_s(TopoDS.Edge_s(i))] [docs]defCompounds(self)->List["Compound"]:""" :returns: All the compounds in this Shape """return[Compound(i)foriinself._entities("Compound")] [docs]defWires(self)->List["Wire"]:""" :returns: All the wires in this Shape """return[Wire(i)foriinself._entities("Wire")] [docs]defFaces(self)->List["Face"]:""" :returns: All the faces in this Shape """return[Face(i)foriinself._entities("Face")] [docs]defShells(self)->List["Shell"]:""" :returns: All the shells in this Shape """return[Shell(i)foriinself._entities("Shell")] [docs]defSolids(self)->List["Solid"]:""" :returns: All the solids in this Shape """return[Solid(i)foriinself._entities("Solid")] [docs]defCompSolids(self)->List["CompSolid"]:""" :returns: All the compsolids in this Shape """return[CompSolid(i)foriinself._entities("CompSolid")] def_filter(self,selector:Optional[Union[Selector,str]],objs:Iterable["Shape"])->"Shape":selectorObj:Selectorifselector:ifisinstance(selector,str):selectorObj=StringSyntaxSelector(selector)else:selectorObj=selectorselected=selectorObj.filter(list(objs))else:selected=list(objs)iflen(selected)==1:rv=selected[0]else:rv=Compound.makeCompound(selected)returnrv[docs]defvertices(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select vertices. """returnself._filter(selector,map(Shape.cast,self._entities("Vertex"))) [docs]defedges(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select edges. """returnself._filter(selector,map(Shape.cast,self._entities("Edge"))) [docs]defwires(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select wires. """returnself._filter(selector,map(Shape.cast,self._entities("Wire"))) [docs]deffaces(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select faces. """returnself._filter(selector,map(Shape.cast,self._entities("Face"))) [docs]defshells(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select shells. """returnself._filter(selector,map(Shape.cast,self._entities("Shell"))) [docs]defsolids(self,selector:Optional[Union[Selector,str]]=None)->"Shape":""" Select solids. """returnself._filter(selector,map(Shape.cast,self._entities("Solid"))) [docs]defArea(self)->float:""" :returns: The surface area of all faces in this Shape """Properties=GProp_GProps()BRepGProp.SurfaceProperties_s(self.wrapped,Properties)returnProperties.Mass() [docs]defVolume(self,tol:Optional[float]=None)->float:""" :returns: The volume of this Shape """# when density == 1, mass == volumereturnShape.computeMass(self,tol) def_apply_transform(self:T,Tr:gp_Trsf)->T:returnself.__class__(BRepBuilderAPI_Transform(self.wrapped,Tr,True).Shape())[docs]defrotate(self:T,startVector:VectorLike,endVector:VectorLike,angleDegrees:float)->T:""" Rotates a shape around an axis. :param startVector: start point of rotation axis :type startVector: either a 3-tuple or a Vector :param endVector: end point of rotation axis :type endVector: either a 3-tuple or a Vector :param angleDegrees: angle to rotate, in degrees :returns: a copy of the shape, rotated """iftype(startVector)==tuple:startVector=Vector(startVector)iftype(endVector)==tuple:endVector=Vector(endVector)Tr=gp_Trsf()Tr.SetRotation(gp_Ax1(Vector(startVector).toPnt(),(Vector(endVector)-Vector(startVector)).toDir(),),radians(angleDegrees),)returnself._apply_transform(Tr) [docs]deftranslate(self:T,vector:VectorLike)->T:""" Translates this shape through a transformation. """T=gp_Trsf()T.SetTranslation(Vector(vector).wrapped)returnself._apply_transform(T) [docs]defscale(self,factor:float)->"Shape":""" Scales this shape through a transformation. """T=gp_Trsf()T.SetScale(gp_Pnt(),factor)returnself._apply_transform(T) [docs]defcopy(self:T,mesh:bool=False)->T:""" Creates a new object that is a copy of this object. :param mesh: should I copy the triangulation too (default: False) :returns: a copy of the object """returnself.__class__(BRepBuilderAPI_Copy(self.wrapped,True,mesh).Shape()) [docs]deftransformShape(self,tMatrix:Matrix)->"Shape":""" Transforms this Shape by tMatrix. Also see :py:meth:`transformGeometry`. :param tMatrix: The transformation matrix :returns: a copy of the object, transformed by the provided matrix, with all objects keeping their type """r=Shape.cast(BRepBuilderAPI_Transform(self.wrapped,tMatrix.wrapped.Trsf()).Shape())r.forConstruction=self.forConstructionreturnr [docs]deftransformGeometry(self,tMatrix:Matrix)->"Shape":""" Transforms this shape by tMatrix. WARNING: transformGeometry will sometimes convert lines and circles to splines, but it also has the ability to handle skew and stretching transformations. If your transformation is only translation and rotation, it is safer to use :py:meth:`transformShape`, which doesn't change the underlying type of the geometry, but cannot handle skew transformations. :param tMatrix: The transformation matrix :returns: a copy of the object, but with geometry transformed instead of just rotated. """r=Shape.cast(BRepBuilderAPI_GTransform(self.wrapped,tMatrix.wrapped,True).Shape())r.forConstruction=self.forConstructionreturnr [docs]deflocation(self)->Location:""" Return the current location """returnLocation(self.wrapped.Location()) [docs]deflocate(self:T,loc:Location)->T:""" Apply a location in absolute sense to self. """self.wrapped.Location(loc.wrapped)returnself [docs]deflocated(self:T,loc:Location)->T:""" Apply a location in absolute sense to a copy of self. """r=self.__class__(self.wrapped.Located(loc.wrapped))r.forConstruction=self.forConstructionreturnr @multimethoddefmove(self:T,loc:Location)->T:""" Apply a location in relative sense (i.e. update current location) to self. """self.wrapped.Move(loc.wrapped)returnself@move.registerdefmove(self:T,x:Real=0,y:Real=0,z:Real=0,rx:Real=0,ry:Real=0,rz:Real=0,)->T:""" Apply translation and rotation in relative sense (i.e. update current location) to self. """self.wrapped.Move(Location(x,y,z,rx,ry,rz).wrapped)returnself[docs]@move.registerdefmove(self:T,loc:VectorLike)->T:""" Apply a VectorLike in relative sense (i.e. update current location) to self. """self.wrapped.Move(Location(loc).wrapped)returnself @multimethoddefmoved(self:T,loc:Location)->T:""" Apply a location in relative sense (i.e. update current location) to a copy of self. """r=self.__class__(self.wrapped.Moved(loc.wrapped))r.forConstruction=self.forConstructionreturnr@moved.registerdefmoved(self:T,loc1:Location,loc2:Location,*locs:Location)->T:""" Apply multiple locations. """returnself.moved((loc1,loc2)+locs)@moved.registerdefmoved(self:T,locs:Sequence[Location])->T:""" Apply multiple locations. """rv=[]forlinlocs:rv.append(self.wrapped.Moved(l.wrapped))return_compound_or_shape(rv)@moved.registerdefmoved(self:T,x:Real=0,y:Real=0,z:Real=0,rx:Real=0,ry:Real=0,rz:Real=0,)->T:""" Apply translation and rotation in relative sense to a copy of self. """returnself.moved(Location(x,y,z,rx,ry,rz))@moved.registerdefmoved(self:T,loc:VectorLike)->T:""" Apply a VectorLike in relative sense to a copy of self. """returnself.moved(Location(loc))@moved.registerdefmoved(self:T,loc1:VectorLike,loc2:VectorLike,*locs:VectorLike)->T:""" Apply multiple VectorLikes in relative sense to a copy of self. """returnself.moved((Location(loc1),Location(loc2))+tuple(Location(loc)forlocinlocs))[docs]@moved.registerdefmoved(self:T,loc:Sequence[VectorLike])->T:""" Apply multiple VectorLikes in relative sense to a copy of self. """returnself.moved(tuple(Location(l)forlinloc)) [docs]def__hash__(self)->int:returnself.hashCode() [docs]def__eq__(self,other)->bool:returnself.isSame(other)ifisinstance(other,Shape)elseFalse def_bool_op(self,args:Iterable["Shape"],tools:Iterable["Shape"],op:Union[BRepAlgoAPI_BooleanOperation,BRepAlgoAPI_Splitter],parallel:bool=True,)->"Shape":""" Generic boolean operation :param parallel: Sets the SetRunParallel flag, which enables parallel execution of boolean operations in OCC kernel """arg=TopTools_ListOfShape()forobjinargs:arg.Append(obj.wrapped)tool=TopTools_ListOfShape()forobjintools:tool.Append(obj.wrapped)op.SetArguments(arg)op.SetTools(tool)op.SetRunParallel(parallel)op.Build()returnShape.cast(op.Shape())[docs]defcut(self,*toCut:"Shape",tol:Optional[float]=None)->"Shape":""" Remove the positional arguments from this Shape. :param tol: Fuzzy mode tolerance """cut_op=BRepAlgoAPI_Cut()iftol:cut_op.SetFuzzyValue(tol)returnself._bool_op((self,),toCut,cut_op) [docs]deffuse(self,*toFuse:"Shape",glue:bool=False,tol:Optional[float]=None)->"Shape":""" Fuse the positional arguments with this Shape. :param glue: Sets the glue option for the algorithm, which allows increasing performance of the intersection of the input shapes :param tol: Fuzzy mode tolerance """fuse_op=BRepAlgoAPI_Fuse()ifglue:fuse_op.SetGlue(BOPAlgo_GlueEnum.BOPAlgo_GlueShift)iftol:fuse_op.SetFuzzyValue(tol)rv=self._bool_op((self,),toFuse,fuse_op)returnrv [docs]defintersect(self,*toIntersect:"Shape",tol:Optional[float]=None)->"Shape":""" Intersection of the positional arguments and this Shape. :param tol: Fuzzy mode tolerance """intersect_op=BRepAlgoAPI_Common()iftol:intersect_op.SetFuzzyValue(tol)returnself._bool_op((self,),toIntersect,intersect_op) [docs]deffacesIntersectedByLine(self,point:VectorLike,axis:VectorLike,tol:float=1e-4,direction:Optional[Literal["AlongAxis","Opposite"]]=None,):""" Computes the intersections between the provided line and the faces of this Shape :param point: Base point for defining a line :param axis: Axis on which the line rests :param tol: Intersection tolerance :param direction: Valid values: "AlongAxis", "Opposite"; If specified, will ignore all faces that are not in the specified direction including the face where the point lies if it is the case :returns: A list of intersected faces sorted by distance from point """oc_point=(gp_Pnt(*point.toTuple())ifisinstance(point,Vector)elsegp_Pnt(*point))oc_axis=(gp_Dir(Vector(axis).wrapped)ifnotisinstance(axis,Vector)elsegp_Dir(axis.wrapped))line=gce_MakeLin(oc_point,oc_axis).Value()shape=self.wrappedintersectMaker=BRepIntCurveSurface_Inter()intersectMaker.Init(shape,line,tol)faces_dist=[]# using a list instead of a dictionary to be able to sort itwhileintersectMaker.More():interPt=intersectMaker.Pnt()interDirMk=gce_MakeDir(oc_point,interPt)distance=oc_point.SquareDistance(interPt)# interDir is not done when `oc_point` and `oc_axis` have the same coordifinterDirMk.IsDone():interDir:Any=interDirMk.Value()else:interDir=Noneifdirection=="AlongAxis":if(interDirisnotNoneandnotinterDir.IsOpposite(oc_axis,tol)anddistance>tol):faces_dist.append((intersectMaker.Face(),distance))elifdirection=="Opposite":if(interDirisnotNoneandinterDir.IsOpposite(oc_axis,tol)anddistance>tol):faces_dist.append((intersectMaker.Face(),distance))elifdirectionisNone:faces_dist.append((intersectMaker.Face(),abs(distance)))# will sort all intersected faces by distance whatever the direction iselse:raiseValueError("Invalid direction specification.\nValid specification are 'AlongAxis' and 'Opposite'.")intersectMaker.Next()faces_dist.sort(key=lambdax:x[1])faces=[face[0]forfaceinfaces_dist]return[Face(face)forfaceinfaces] [docs]defsplit(self,*splitters:"Shape")->"Shape":""" Split this shape with the positional arguments. """split_op=BRepAlgoAPI_Splitter()returnself._bool_op((self,),splitters,split_op) [docs]defdistance(self,other:"Shape")->float:""" Minimal distance between two shapes """dist_calc=BRepExtrema_DistShapeShape(self.wrapped,other.wrapped)dist_calc.SetMultiThread(True)returndist_calc.Value() [docs]defdistances(self,*others:"Shape")->Iterator[float]:""" Minimal distances to between self and other shapes """dist_calc=BRepExtrema_DistShapeShape()dist_calc.SetMultiThread(True)dist_calc.LoadS1(self.wrapped)forsinothers:dist_calc.LoadS2(s.wrapped)dist_calc.Perform()yielddist_calc.Value() [docs]defmesh(self,tolerance:float,angularTolerance:float=0.1):""" Generate triangulation if none exists. """ifnotBRepTools.Triangulation_s(self.wrapped,tolerance):BRepMesh_IncrementalMesh(self.wrapped,tolerance,True,angularTolerance) deftessellate(self,tolerance:float,angularTolerance:float=0.1)->Tuple[List[Vector],List[Tuple[int,int,int]]]:self.mesh(tolerance,angularTolerance)vertices:List[Vector]=[]triangles:List[Tuple[int,int,int]]=[]offset=0forfinself.Faces():loc=TopLoc_Location()poly=BRep_Tool.Triangulation_s(f.wrapped,loc)ifpolyisNone:continueTrsf=loc.Transformation()reverse=(Trueiff.wrapped.Orientation()==TopAbs_Orientation.TopAbs_REVERSEDelseFalse)# add verticesvertices+=[Vector(v.X(),v.Y(),v.Z())forvin(poly.Node(i).Transformed(Trsf)foriinrange(1,poly.NbNodes()+1))]# add trianglestriangles+=[(t.Value(1)+offset-1,t.Value(3)+offset-1,t.Value(2)+offset-1,)ifreverseelse(t.Value(1)+offset-1,t.Value(2)+offset-1,t.Value(3)+offset-1,)fortinpoly.Triangles()]offset+=poly.NbNodes()returnvertices,triangles[docs]deftoSplines(self:T,degree:int=3,tolerance:float=1e-3,nurbs:bool=False)->T:""" Approximate shape with b-splines of the specified degree. :param degree: Maximum degree. :param tolerance: Approximation tolerance. :param nurbs: Use rational splines. """params=ShapeCustom_RestrictionParameters()result=ShapeCustom.BSplineRestriction_s(self.wrapped,tolerance,# 3D tolerancetolerance,# 2D tolerancedegree,1,# dumy value, degree is leadingga.GeomAbs_C0,ga.GeomAbs_C0,True,# set degree to be leadingnotnurbs,params,)returnself.__class__(result) [docs]deftoNURBS(self:T,)->T:""" Return a NURBS representation of a given shape. """bldr=BRepBuilderAPI_NurbsConvert(self.wrapped,Copy=True)returnself.__class__(bldr.Shape()) [docs]deftoVtkPolyData(self,tolerance:Optional[float]=None,angularTolerance:Optional[float]=None,normals:bool=False,)->vtkPolyData:""" Convert shape to vtkPolyData """vtk_shape=IVtkOCC_Shape(self.wrapped)shape_data=IVtkVTK_ShapeData()shape_mesher=IVtkOCC_ShapeMesher()drawer=vtk_shape.Attributes()drawer.SetUIsoAspect(Prs3d_IsoAspect(Quantity_Color(),Aspect_TOL_SOLID,1,0))drawer.SetVIsoAspect(Prs3d_IsoAspect(Quantity_Color(),Aspect_TOL_SOLID,1,0))iftolerance:drawer.SetDeviationCoefficient(tolerance)ifangularTolerance:drawer.SetDeviationAngle(angularTolerance)shape_mesher.Build(vtk_shape,shape_data)rv=shape_data.getVtkPolyData()# convert to triangles and split edgest_filter=vtkTriangleFilter()t_filter.SetInputData(rv)t_filter.Update()rv=t_filter.GetOutput()# compute normalsifnormals:n_filter=vtkPolyDataNormals()n_filter.SetComputePointNormals(True)n_filter.SetComputeCellNormals(True)n_filter.SetFeatureAngle(360)n_filter.SetInputData(rv)n_filter.Update()rv=n_filter.GetOutput()returnrv def_repr_javascript_(self):""" Jupyter 3D representation support """from.jupyter_toolsimportdisplayreturndisplay(self)._repr_javascript_()[docs]def__iter__(self)->Iterator["Shape"]:""" Iterate over subshapes. """it=TopoDS_Iterator(self.wrapped)whileit.More():yieldShape.cast(it.Value())it.Next() [docs]defancestors(self,shape:"Shape",kind:Shapes)->"Compound":""" Iterate over ancestors, i.e. shapes of same kind within shape that contain self. """shape_map=TopTools_IndexedDataMapOfShapeListOfShape()TopExp.MapShapesAndAncestors_s(shape.wrapped,shapetype(self.wrapped),inverse_shape_LUT[kind],shape_map)returnCompound.makeCompound(Shape.cast(s)forsinshape_map.FindFromKey(self.wrapped)) [docs]defsiblings(self,shape:"Shape",kind:Shapes,level:int=1)->"Compound":""" Iterate over siblings, i.e. shapes within shape that share subshapes of kind with self. """shape_map=TopTools_IndexedDataMapOfShapeListOfShape()TopExp.MapShapesAndAncestors_s(shape.wrapped,inverse_shape_LUT[kind],shapetype(self.wrapped),shape_map,)exclude=TopTools_MapOfShape()def_siblings(shapes,level):rv=set()forsinshapes:exclude.Add(s.wrapped)forsinshapes:rv.update(Shape.cast(el)forchildins._entities(kind)forelinshape_map.FindFromKey(child)ifnotexclude.Contains(el))returnrviflevel==1else_siblings(rv,level-1)returnCompound.makeCompound(_siblings([self],level)) [docs]def__add__(self,other:"Shape")->"Shape":""" Fuse self and other. """returnfuse(self,other) [docs]def__sub__(self,other:"Shape")->"Shape":""" Subtract other from self. """returncut(self,other) [docs]def__mul__(self,other:"Shape")->"Shape":""" Intersect self and other. """returnintersect(self,other) [docs]def__truediv__(self,other:"Shape")->"Shape":""" Split self with other. """returnsplit(self,other) [docs]defexport(self:T,fname:str,tolerance:float=0.1,angularTolerance:float=0.1,opt:Optional[Dict[str,Any]]=None,):""" Export Shape to file. """from.exportersimportexport# imported here to prevent circular importsexport(self,fname,tolerance=tolerance,angularTolerance=angularTolerance,opt=opt) [docs]def__getstate__(self)->Tuple[BytesIO,bool]:data=BytesIO()BinTools.Write_s(self.wrapped,data)data.seek(0)return(data,self.forConstruction) def__setstate__(self,data:Tuple[BytesIO,bool]):wrapped=TopoDS_Shape()BinTools.Read_s(wrapped,data[0])self.wrapped=wrappedself.forConstruction=data[1][docs]defreplace(self,old:"Shape",*new:"Shape")->Self:""" Replace old subshape with new subshapes. """tools:List[Shape]=[]forelinnew:ifisinstance(el,Compound):tools.extend(el)else:tools.append(el)bldr=BRepTools_ReShape()bldr.Replace(old.wrapped,compound(tools).wrapped)rv=bldr.Apply(self.wrapped)returnself.__class__(rv) [docs]defremove(self,*subshape:"Shape")->Self:""" Remove subshapes. """bldr=BRepTools_ReShape()forelinsubshape:bldr.Remove(el.wrapped)rv=bldr.Apply(self.wrapped)returnself.__class__(rv) [docs]classShapeProtocol(Protocol):@propertydefwrapped(self)->TopoDS_Shape:...def__init__(self,wrapped:TopoDS_Shape)->None:...defFaces(self)->List["Face"]:...defgeomType(self)->Geoms:... [docs]classVertex(Shape):""" A Single Point in Space """wrapped:TopoDS_Vertex[docs]def__init__(self,obj:TopoDS_Shape,forConstruction:bool=False):""" Create a vertex """super(Vertex,self).__init__(obj)self.forConstruction=forConstructionself.X,self.Y,self.Z=self.toTuple() deftoTuple(self)->Tuple[float,float,float]:geom_point=BRep_Tool.Pnt_s(self.wrapped)return(geom_point.X(),geom_point.Y(),geom_point.Z())[docs]defCenter(self)->Vector:""" The center of a vertex is itself! """returnVector(self.toTuple()) @classmethoddefmakeVertex(cls,x:float,y:float,z:float)->"Vertex":returncls(BRepBuilderAPI_MakeVertex(gp_Pnt(x,y,z)).Vertex()) ParamMode=Literal["length","parameter"]FrameMode=Literal["frenet","corrected"][docs]classMixin1DProtocol(ShapeProtocol,Protocol):def_approxCurve(self)->Geom_BSplineCurve:...def_curve(self)->Geom_Curve:...def_geomAdaptor(self)->Union[BRepAdaptor_Curve,BRepAdaptor_CompCurve]:...def_curve_and_param(self,d:float,mode:ParamMode)->Tuple[Union[BRepAdaptor_Curve,BRepAdaptor_CompCurve],float]:...defbounds(self)->Tuple[float,float]:...defparamAt(self,d:float)->float:...defpositionAt(self,d:float,mode:ParamMode="length",)->Vector:...deflocationAt(self,d:float,mode:ParamMode="length",frame:FrameMode="frenet",planar:bool=False,)->Location:...defcurvatureAt(self,d:float,mode:ParamMode="length",resolution:float=1e-6,)->float:...defparamsLength(self,locations:Iterable[float])->List[float]:... T1D=TypeVar("T1D",bound=Mixin1DProtocol)[docs]classMixin1D(object):def_bounds(self:Mixin1DProtocol)->Tuple[float,float]:returnself.bounds()[docs]defbounds(self:Mixin1DProtocol)->Tuple[float,float]:""" Parametric bounds of the curve. """curve=self._geomAdaptor()returncurve.FirstParameter(),curve.LastParameter() [docs]defstartPoint(self:Mixin1DProtocol)->Vector:""" :return: a vector representing the start point of this edge Note, circles may have the start and end points the same """curve=self._geomAdaptor()umin=curve.FirstParameter()returnVector(curve.Value(umin)) [docs]defendPoint(self:Mixin1DProtocol)->Vector:""" :return: a vector representing the end point of this edge. Note, circles may have the start and end points the same """curve=self._geomAdaptor()umax=curve.LastParameter()returnVector(curve.Value(umax)) def_approxCurve(self:Mixin1DProtocol)->Geom_BSplineCurve:""" Approximate curve adaptor into a real b-spline. Meant for handling of BRepAdaptor_CompCurve. """rv=GeomConvert_ApproxCurve(self._geomAdaptor(),TOLERANCE,GeomAbs_C2,MaxSegments=100,MaxDegree=3).Curve()returnrvdef_curve(self:Mixin1DProtocol)->Geom_Curve:""" Return the underlying curve. """curve=self._geomAdaptor()ifisinstance(curve,BRepAdaptor_Curve):rv=curve.Curve().Curve()# get the underlying curve objectelse:rv=self._approxCurve()# approximate the adaptor as a real curvereturnrv[docs]defparamAt(self:Mixin1DProtocol,d:Union[Real,Vector])->float:""" Compute parameter value at the specified normalized distance or a point. :param d: normalized distance [0, 1] or a point :return: parameter value """curve=self._geomAdaptor()ifisinstance(d,Vector):curve_=self._curve()rv=GeomAPI_ProjectPointOnCurve(d.toPnt(),curve_,curve.FirstParameter(),curve.LastParameter(),).LowerDistanceParameter()else:l=GCPnts_AbscissaPoint.Length_s(curve)rv=GCPnts_AbscissaPoint(curve,l*d,curve.FirstParameter()).Parameter()returnrv [docs]defparams(self:Mixin1DProtocol,pts:Iterable[Vector],tol=1e-6)->List[float]:""" Computes u values closest to given vectors. :param pts: the points to compute the parameters at. :return: list of u values. """us=[]curve=self._geomAdaptor()curve_=self._curve()umin=curve.FirstParameter()umax=curve.LastParameter()# get the first pointit=iter(pts)pt=next(it)proj=GeomAPI_ProjectPointOnCurve(pt.toPnt(),curve_,umin,umax)u=proj.LowerDistanceParameter()us.append(u)forptinit:proj.Perform(pt.toPnt())u=proj.LowerDistanceParameter()us.append(u)returnus [docs]defparamsLength(self:Mixin1DProtocol,locations:Iterable[float])->List[float]:""" Computes u values at given relative lengths. :param locations: list of distances. :returns: list of u values. :param pts: the points to compute the parameters at. """us=[]curve=self._geomAdaptor()L=GCPnts_AbscissaPoint.Length_s(curve)fordinlocations:u=GCPnts_AbscissaPoint(curve,L*d,curve.FirstParameter()).Parameter()us.append(u)returnus [docs]deftangentAt(self:Mixin1DProtocol,locationParam:float=0.5,mode:ParamMode="length",)->Vector:""" Compute tangent vector at the specified location. :param locationParam: distance or parameter value (default: 0.5) :param mode: position calculation mode (default: length) :return: tangent vector """curve=self._geomAdaptor()tmp=gp_Pnt()res=gp_Vec()ifmode=="length":param=self.paramAt(locationParam)else:param=locationParamcurve.D1(param,tmp,res)returnVector(gp_Dir(res)) [docs]deftangents(self:Mixin1DProtocol,locations:Iterable[float],mode:ParamMode="length",)->List[Vector]:""" Compute tangent vectors at the specified locations. :param locations: list of distances or parameters. :param mode: position calculation mode (default: length). :return: list of tangent vectors """curve=self._geomAdaptor()params:Iterable[float]ifmode=="length":params=self.paramsLength(locations)else:params=locationsrv=[]tmp=gp_Pnt()res=gp_Vec()forparaminparams:curve.D1(param,tmp,res)rv.append(Vector(gp_Dir(res)))returnrv [docs]defnormal(self:Mixin1DProtocol)->Vector:""" Calculate the normal Vector. Only possible for planar curves. :return: normal vector """curve=self._geomAdaptor()gtype=self.geomType()ifgtype=="CIRCLE":circ=curve.Circle()rv=Vector(circ.Axis().Direction())elifgtype=="ELLIPSE":ell=curve.Ellipse()rv=Vector(ell.Axis().Direction())else:fs=BRepLib_FindSurface(self.wrapped,OnlyPlane=True)surf=fs.Surface()ifisinstance(surf,Geom_Plane):pln=surf.Pln()rv=Vector(pln.Axis().Direction())else:raiseValueError("Normal not defined")returnrv defCenter(self:Mixin1DProtocol)->Vector:Properties=GProp_GProps()BRepGProp.LinearProperties_s(self.wrapped,Properties)returnVector(Properties.CentreOfMass())defLength(self:Mixin1DProtocol)->float:returnGCPnts_AbscissaPoint.Length_s(self._geomAdaptor())[docs]defradius(self:Mixin1DProtocol)->float:""" Calculate the radius. Note that when applied to a Wire, the radius is simply the radius of the first edge. :return: radius :raises ValueError: if kernel can not reduce the shape to a circular edge """geom=self._geomAdaptor()try:circ=geom.Circle()except(Standard_NoSuchObject,Standard_Failure)ase:raiseValueError("Shape could not be reduced to a circle")fromereturncirc.Radius() defIsClosed(self:Mixin1DProtocol)->bool:returnBRep_Tool.IsClosed_s(self.wrapped)def_curve_and_param(self:Mixin1DProtocol,d:float,mode:ParamMode)->Tuple[Union[BRepAdaptor_Curve,BRepAdaptor_CompCurve],float]:""" Helper that reurns the curve and u value """curve=self._geomAdaptor()ifmode=="length":param=self.paramAt(d)else:param=dreturncurve,param[docs]defpositionAt(self:Mixin1DProtocol,d:float,mode:ParamMode="length",)->Vector:""" Generate a position along the underlying curve. :param d: distance or parameter value :param mode: position calculation mode (default: length) :return: A Vector on the underlying curve located at the specified d value. """curve,param=self._curve_and_param(d,mode)returnVector(curve.Value(param)) [docs]defpositions(self:Mixin1DProtocol,ds:Iterable[float],mode:ParamMode="length",)->List[Vector]:""" Generate positions along the underlying curve. :param ds: distance or parameter values :param mode: position calculation mode (default: length) :return: A list of Vector objects. """return[self.positionAt(d,mode)fordinds] [docs]defsample(self:Mixin1DProtocol,n:Union[int,float])->Tuple[List[Vector],List[float]]:""" Sample a curve based on a number of points or deflection. :param n: Number of positions or deflection :return: A list of Vectors and a list of parameters. """gcpnts:Union[GCPnts_QuasiUniformAbscissa,GCPnts_QuasiUniformDeflection]ifisinstance(n,int):crv=self._geomAdaptor()gcpnts=GCPnts_QuasiUniformAbscissa(crv,n+1ifcrv.IsClosed()elsen)else:crv=self._geomAdaptor()gcpnts=GCPnts_QuasiUniformDeflection(crv,n)N_pts=gcpnts.NbPoints()params=[gcpnts.Parameter(i)foriinrange(1,N_ptsifcrv.IsClosed()elseN_pts+1)]pnts=[Vector(crv.Value(p))forpinparams]returnpnts,params [docs]deflocationAt(self:Mixin1DProtocol,d:float,mode:ParamMode="length",frame:FrameMode="frenet",planar:bool=False,)->Location:""" Generate a location along the underlying curve. :param d: distance or parameter value :param mode: position calculation mode (default: length) :param frame: moving frame calculation method (default: frenet) :param planar: planar mode :return: A Location object representing local coordinate system at the specified distance. """curve,param=self._curve_and_param(d,mode)law:GeomFill_TrihedronLawifframe=="frenet":law=GeomFill_Frenet()else:law=GeomFill_CorrectedFrenet()law.SetCurve(curve)tangent,normal,binormal=gp_Vec(),gp_Vec(),gp_Vec()law.D0(param,tangent,normal,binormal)pnt=curve.Value(param)T=gp_Trsf()ifplanar:T.SetTransformation(gp_Ax3(pnt,gp_Dir(0,0,1),gp_Dir(Vector(0,0,1).cross(Vector(tangent)).normalized().wrapped),),gp_Ax3(),)else:T.SetTransformation(gp_Ax3(pnt,gp_Dir(tangent.XYZ()),gp_Dir(normal.XYZ())),gp_Ax3())returnLocation(TopLoc_Location(T)) [docs]deflocations(self:Mixin1DProtocol,ds:Iterable[float],mode:ParamMode="length",frame:FrameMode="frenet",planar:bool=False,)->List[Location]:""" Generate locations along the curve. :param ds: distance or parameter values :param mode: position calculation mode (default: length) :param frame: moving frame calculation method (default: frenet) :param planar: planar mode :return: A list of Location objects representing local coordinate systems at the specified distances. """return[self.locationAt(d,mode,frame,planar)fordinds] [docs]defproject(self:T1D,face:"Face",d:VectorLike,closest:bool=True)->Union[T1D,List[T1D]]:""" Project onto a face along the specified direction. """# select the closest projection if requestedrv:Union[T1D,List[T1D]]bldr=BRepProj_Projection(self.wrapped,face.wrapped,Vector(d).toDir())shapes=Compound(bldr.Shape())ifclosest:dist_calc=BRepExtrema_DistShapeShape()dist_calc.LoadS1(self.wrapped)min_dist=infforelinshapes:dist_calc.LoadS2(el.wrapped)dist_calc.Perform()dist=dist_calc.Value()ifdist<min_dist:min_dist=distrv=tcast(T1D,el)else:rv=[tcast(T1D,el)forelinshapes]returnrv [docs]defcurvatureAt(self:Mixin1DProtocol,d:float,mode:ParamMode="length",resolution:float=1e-6,)->float:""" Calculate mean curvature along the underlying curve. :param d: distance or parameter value :param mode: position calculation mode (default: length) :param resolution: resolution of the calculation (default: 1e-6) :return: mean curvature value at the specified d value. """curve,param=self._curve_and_param(d,mode)props=LProp3d_CLProps(curve,param,2,resolution)returnprops.Curvature() [docs]defcurvatures(self:Mixin1DProtocol,ds:Iterable[float],mode:ParamMode="length",resolution:float=1e-6,)->List[float]:""" Calculate mean curvatures along the underlying curve. :param d: distance or parameter values :param mode: position calculation mode (default: length) :param resolution: resolution of the calculation (default: 1e-6) :return: mean curvature value at the specified d value. """return[self.curvatureAt(d,mode,resolution)fordinds] [docs]classEdge(Shape,Mixin1D):""" A trimmed curve that represents the border of a face """wrapped:TopoDS_Edgedef_geomAdaptor(self)->BRepAdaptor_Curve:""" Return the underlying geometry """returnBRepAdaptor_Curve(self.wrapped)[docs]defclose(self)->Union["Edge","Wire"]:""" Close an Edge """rv:Union[Wire,Edge]ifnotself.IsClosed():rv=Wire.assembleEdges((self,)).close()else:rv=selfreturnrv [docs]defarcCenter(self)->Vector:""" Center of an underlying circle or ellipse geometry. """g=self.geomType()a=self._geomAdaptor()ifg=="CIRCLE":rv=Vector(a.Circle().Position().Location())elifg=="ELLIPSE":rv=Vector(a.Ellipse().Position().Location())else:raiseValueError(f"{g} has no arc center")returnrv [docs]deftrim(self,u0:Real,u1:Real)->"Edge":""" Trim the edge in the parametric space to (u0, u1). NB: this operation is done on the base geometry. """bldr=BRepBuilderAPI_MakeEdge(self._geomAdaptor().Curve().Curve(),u0,u1)returnself.__class__(bldr.Shape()) [docs]defhasPCurve(self,f:"Face")->bool:""" Check if self has a pcurve defined on f. """returnShapeAnalysis_Edge().HasPCurve(self.wrapped,f.wrapped) @classmethoddefmakeCircle(cls,radius:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angle1:float=360.0,angle2:float=360,orientation=True,)->"Edge":pnt=Vector(pnt)dir=Vector(dir)circle_gp=gp_Circ(gp_Ax2(pnt.toPnt(),dir.toDir()),radius)ifangle1==angle2:# full circle casereturncls(BRepBuilderAPI_MakeEdge(circle_gp).Edge())else:# arc casecircle_geom=GC_MakeArcOfCircle(circle_gp,radians(angle1),radians(angle2),orientation).Value()returncls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())[docs]@classmethoddefmakeEllipse(cls,x_radius:float,y_radius:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),xdir:VectorLike=Vector(1,0,0),angle1:float=360.0,angle2:float=360.0,sense:Literal[-1,1]=1,)->"Edge":""" Makes an Ellipse centered at the provided point, having normal in the provided direction. :param cls: :param x_radius: x radius of the ellipse (along the x-axis of plane the ellipse should lie in) :param y_radius: y radius of the ellipse (along the y-axis of plane the ellipse should lie in) :param pnt: vector representing the center of the ellipse :param dir: vector representing the direction of the plane the ellipse should lie in :param angle1: start angle of arc :param angle2: end angle of arc (angle2 == angle1 return closed ellipse = default) :param sense: clockwise (-1) or counter clockwise (1) :return: an Edge """pnt_p=Vector(pnt).toPnt()dir_d=Vector(dir).toDir()xdir_d=Vector(xdir).toDir()ax1=gp_Ax1(pnt_p,dir_d)ax2=gp_Ax2(pnt_p,dir_d,xdir_d)ify_radius>x_radius:# swap x and y radius and rotate by 90° afterwards to create an ellipse with x_radius < y_radiuscorrection_angle=radians(90.0)ellipse_gp=gp_Elips(ax2,y_radius,x_radius).Rotated(ax1,correction_angle)else:correction_angle=0.0ellipse_gp=gp_Elips(ax2,x_radius,y_radius)ifangle1==angle2:# full ellipse caseellipse=cls(BRepBuilderAPI_MakeEdge(ellipse_gp).Edge())else:# arc case# take correction_angle into accountellipse_geom=GC_MakeArcOfEllipse(ellipse_gp,radians(angle1)-correction_angle,radians(angle2)-correction_angle,sense==1,).Value()ellipse=cls(BRepBuilderAPI_MakeEdge(ellipse_geom).Edge())returnellipse [docs]@classmethoddefmakeSpline(cls,listOfVector:List[Vector],tangents:Optional[Sequence[Vector]]=None,periodic:bool=False,parameters:Optional[Sequence[float]]=None,scale:bool=True,tol:float=1e-6,)->"Edge":""" Interpolate a spline through the provided points. :param listOfVector: a list of Vectors that represent the points :param tangents: tuple of Vectors specifying start and finish tangent :param periodic: creation of periodic curves :param parameters: the value of the parameter at each interpolation point. (The interpolated curve is represented as a vector-valued function of a scalar parameter.) If periodic == True, then len(parameters) must be len(intepolation points) + 1, otherwise len(parameters) must be equal to len(interpolation points). :param scale: whether to scale the specified tangent vectors before interpolating. Each tangent is scaled, so it's length is equal to the derivative of the Lagrange interpolated curve. I.e., set this to True, if you want to use only the direction of the tangent vectors specified by ``tangents``, but not their magnitude. :param tol: tolerance of the algorithm (consult OCC documentation). Used to check that the specified points are not too close to each other, and that tangent vectors are not too short. (In either case interpolation may fail.) :return: an Edge """pnts=TColgp_HArray1OfPnt(1,len(listOfVector))forix,vinenumerate(listOfVector):pnts.SetValue(ix+1,v.toPnt())ifparametersisNone:spline_builder=GeomAPI_Interpolate(pnts,periodic,tol)else:iflen(parameters)!=(len(listOfVector)+periodic):raiseValueError("There must be one parameter for each interpolation point ""(plus one if periodic), or none specified. Parameter count: "f"{len(parameters)}, point count:{len(listOfVector)}")parameters_array=TColStd_HArray1OfReal(1,len(parameters))forp_index,p_valueinenumerate(parameters):parameters_array.SetValue(p_index+1,p_value)spline_builder=GeomAPI_Interpolate(pnts,parameters_array,periodic,tol)iftangents:iflen(tangents)==2andlen(listOfVector)!=2:# Specify only initial and final tangent:t1,t2=tangentsspline_builder.Load(t1.wrapped,t2.wrapped,scale)else:iflen(tangents)!=len(listOfVector):raiseValueError(f"There must be one tangent for each interpolation point, "f"or just two end point tangents. Tangent count: "f"{len(tangents)}, point count:{len(listOfVector)}")# Specify a tangent for each interpolation point:tangents_array=TColgp_Array1OfVec(1,len(tangents))tangent_enabled_array=TColStd_HArray1OfBoolean(1,len(tangents))fort_index,t_valueinenumerate(tangents):tangent_enabled_array.SetValue(t_index+1,t_valueisnotNone)tangent_vec=t_valueift_valueisnotNoneelseVector()tangents_array.SetValue(t_index+1,tangent_vec.wrapped)spline_builder.Load(tangents_array,tangent_enabled_array,scale)spline_builder.Perform()ifnotspline_builder.IsDone():raiseValueError("B-spline interpolation failed")spline_geom=spline_builder.Curve()returncls(BRepBuilderAPI_MakeEdge(spline_geom).Edge()) [docs]@classmethoddefmakeSplineApprox(cls,listOfVector:List[Vector],tol:float=1e-3,smoothing:Optional[Tuple[float,float,float]]=None,minDeg:int=1,maxDeg:int=6,)->"Edge":""" Approximate a spline through the provided points. :param listOfVector: a list of Vectors that represent the points :param tol: tolerance of the algorithm (consult OCC documentation). :param smoothing: optional tuple of 3 weights use for variational smoothing (default: None) :param minDeg: minimum spline degree. Enforced only when smothing is None (default: 1) :param maxDeg: maximum spline degree (default: 6) :return: an Edge """pnts=TColgp_HArray1OfPnt(1,len(listOfVector))forix,vinenumerate(listOfVector):pnts.SetValue(ix+1,v.toPnt())ifsmoothing:spline_builder=GeomAPI_PointsToBSpline(pnts,*smoothing,DegMax=maxDeg,Tol3D=tol)else:spline_builder=GeomAPI_PointsToBSpline(pnts,DegMin=minDeg,DegMax=maxDeg,Tol3D=tol)ifnotspline_builder.IsDone():raiseValueError("B-spline approximation failed")spline_geom=spline_builder.Curve()returncls(BRepBuilderAPI_MakeEdge(spline_geom).Edge()) [docs]@classmethoddefmakeThreePointArc(cls,v1:VectorLike,v2:VectorLike,v3:VectorLike)->"Edge":""" Makes a three point arc through the provided points :param cls: :param v1: start vector :param v2: middle vector :param v3: end vector :return: an edge object through the three points """circle_geom=GC_MakeArcOfCircle(Vector(v1).toPnt(),Vector(v2).toPnt(),Vector(v3).toPnt()).Value()returncls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) [docs]@classmethoddefmakeTangentArc(cls,v1:VectorLike,v2:VectorLike,v3:VectorLike)->"Edge":""" Makes a tangent arc from point v1, in the direction of v2 and ends at v3. :param cls: :param v1: start vector :param v2: tangent vector :param v3: end vector :return: an edge """circle_geom=GC_MakeArcOfCircle(Vector(v1).toPnt(),Vector(v2).wrapped,Vector(v3).toPnt()).Value()returncls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) [docs]@classmethoddefmakeLine(cls,v1:VectorLike,v2:VectorLike)->"Edge":""" Create a line between two points :param v1: Vector that represents the first point :param v2: Vector that represents the second point :return: A linear edge between the two provided points """returncls(BRepBuilderAPI_MakeEdge(Vector(v1).toPnt(),Vector(v2).toPnt()).Edge()) [docs]@classmethoddefmakeBezier(cls,points:List[Vector])->"Edge":""" Create a cubic Bézier Curve from the points. :param points: a list of Vectors that represent the points. The edge will pass through the first and the last point, and the inner points are Bézier control points. :return: An edge """# Convert to a TColgp_Array1OfPntarr=TColgp_Array1OfPnt(1,len(points))fori,vinenumerate(points):arr.SetValue(i+1,Vector(v).toPnt())bez=Geom_BezierCurve(arr)returncls(BRepBuilderAPI_MakeEdge(bez).Edge()) [docs]classWire(Shape,Mixin1D):""" A series of connected, ordered Edges, that typically bounds a Face """wrapped:TopoDS_Wiredef_nbEdges(self)->int:""" Number of edges. """sa=ShapeAnalysis_Wire()sa.Load(self.wrapped)returnsa.NbEdges()def_geomAdaptor(self)->Union[BRepAdaptor_Curve,BRepAdaptor_CompCurve]:""" Return the underlying geometry. """rv:Union[BRepAdaptor_Curve,BRepAdaptor_CompCurve]ifself._nbEdges()==1:rv=self.Edges()[-1]._geomAdaptor()else:rv=BRepAdaptor_CompCurve(self.wrapped)returnrv[docs]defclose(self)->"Wire":""" Close a Wire """ifnotself.IsClosed():e=Edge.makeLine(self.endPoint(),self.startPoint())rv=Wire.combine((self,e))[0]else:rv=selfreturnrv [docs]@classmethoddefcombine(cls,listOfWires:Iterable[Union["Wire",Edge]],tol:float=1e-9)->List["Wire"]:""" Attempt to combine a list of wires and edges into a new wire. :param cls: :param listOfWires: :param tol: default 1e-9 :return: List[Wire] """edges_in=TopTools_HSequenceOfShape()wires_out=TopTools_HSequenceOfShape()foreinCompound.makeCompound(listOfWires).Edges():edges_in.Append(e.wrapped)ShapeAnalysis_FreeBounds.ConnectEdgesToWires_s(edges_in,tol,False,wires_out)return[cls(el)forelinwires_out] [docs]@classmethoddefassembleEdges(cls,listOfEdges:Iterable[Edge])->"Wire":""" Attempts to build a wire that consists of the edges in the provided list :param cls: :param listOfEdges: a list of Edge objects. The edges are not to be consecutive. :return: a wire with the edges assembled BRepBuilderAPI_MakeWire::Error() values: * BRepBuilderAPI_WireDone = 0 * BRepBuilderAPI_EmptyWire = 1 * BRepBuilderAPI_DisconnectedWire = 2 * BRepBuilderAPI_NonManifoldWire = 3 """wire_builder=BRepBuilderAPI_MakeWire()occ_edges_list=TopTools_ListOfShape()foreinlistOfEdges:occ_edges_list.Append(e.wrapped)wire_builder.Add(occ_edges_list)wire_builder.Build()ifnotwire_builder.IsDone():w=("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(w)returncls(wire_builder.Wire()) [docs]@classmethoddefmakeCircle(cls,radius:float,center:VectorLike,normal:VectorLike)->"Wire":""" Makes a Circle centered at the provided point, having normal in the provided direction :param radius: floating point radius of the circle, must be > 0 :param center: vector representing the center of the circle :param normal: vector representing the direction of the plane the circle should lie in """circle_edge=Edge.makeCircle(radius,center,normal)w=cls.assembleEdges([circle_edge])returnw [docs]@classmethoddefmakeEllipse(cls,x_radius:float,y_radius:float,center:VectorLike,normal:VectorLike,xDir:VectorLike,angle1:float=360.0,angle2:float=360.0,rotation_angle:float=0.0,closed:bool=True,)->"Wire":""" Makes an Ellipse centered at the provided point, having normal in the provided direction :param x_radius: floating point major radius of the ellipse (x-axis), must be > 0 :param y_radius: floating point minor radius of the ellipse (y-axis), must be > 0 :param center: vector representing the center of the circle :param normal: vector representing the direction of the plane the circle should lie in :param angle1: start angle of arc :param angle2: end angle of arc :param rotation_angle: angle to rotate the created ellipse / arc """ellipse_edge=Edge.makeEllipse(x_radius,y_radius,center,normal,xDir,angle1,angle2)ifangle1!=angle2andclosed:line=Edge.makeLine(ellipse_edge.endPoint(),ellipse_edge.startPoint())w=cls.assembleEdges([ellipse_edge,line])else:w=cls.assembleEdges([ellipse_edge])ifrotation_angle!=0.0:w=w.rotate(center,Vector(center)+Vector(normal),rotation_angle)returnw [docs]@classmethoddefmakePolygon(cls,listOfVertices:Iterable[VectorLike],forConstruction:bool=False,close:bool=False,)->"Wire":""" Construct a polygonal wire from points. """wire_builder=BRepBuilderAPI_MakePolygon()forvinlistOfVertices:wire_builder.Add(Vector(v).toPnt())ifclose:wire_builder.Close()w=cls(wire_builder.Wire())w.forConstruction=forConstructionreturnw [docs]@classmethoddefmakeHelix(cls,pitch:float,height:float,radius:float,center:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angle:float=360.0,lefthand:bool=False,)->"Wire":""" Make a helix with a given pitch, height and radius By default a cylindrical surface is used to create the helix. If the fourth parameter is set (the apex given in degree) a conical surface is used instead' """# 1. build underlying cylindrical/conical surfaceifangle==360.0:geom_surf:Geom_Surface=Geom_CylindricalSurface(gp_Ax3(Vector(center).toPnt(),Vector(dir).toDir()),radius)else:geom_surf=Geom_ConicalSurface(gp_Ax3(Vector(center).toPnt(),Vector(dir).toDir()),radians(angle),radius,)# 2. construct an segment in the u,v domainiflefthand:geom_line=Geom2d_Line(gp_Pnt2d(0.0,0.0),gp_Dir2d(-2*pi,pitch))else:geom_line=Geom2d_Line(gp_Pnt2d(0.0,0.0),gp_Dir2d(2*pi,pitch))# 3. put it together into a wiren_turns=height/pitchu_start=geom_line.Value(0.0)u_stop=geom_line.Value(n_turns*sqrt((2*pi)**2+pitch**2))geom_seg=GCE2d_MakeSegment(u_start,u_stop).Value()e=BRepBuilderAPI_MakeEdge(geom_seg,geom_surf).Edge()# 4. Convert to wire and fix building 3d geom from 2d geomw=BRepBuilderAPI_MakeWire(e).Wire()BRepLib.BuildCurves3d_s(w,1e-6,MaxSegment=2000)# NB: preliminary valuesreturncls(w) [docs]defstitch(self,other:"Wire")->"Wire":"""Attempt to stitch wires"""wire_builder=BRepBuilderAPI_MakeWire()wire_builder.Add(TopoDS.Wire_s(self.wrapped))wire_builder.Add(TopoDS.Wire_s(other.wrapped))wire_builder.Build()returnself.__class__(wire_builder.Wire()) [docs]defoffset2D(self,d:float,kind:Literal["arc","intersection","tangent"]="arc")->List["Wire"]:"""Offsets a planar wire"""kind_dict={"arc":GeomAbs_JoinType.GeomAbs_Arc,"intersection":GeomAbs_JoinType.GeomAbs_Intersection,"tangent":GeomAbs_JoinType.GeomAbs_Tangent,}offset=BRepOffsetAPI_MakeOffset()offset.Init(kind_dict[kind])offset.AddWire(self.wrapped)offset.Perform(d)obj=downcast(offset.Shape())ifisinstance(obj,TopoDS_Compound):rv=[self.__class__(el.wrapped)forelinCompound(obj)]else:rv=[self.__class__(obj)]returnrv [docs]deffillet2D(self,radius:float,vertices:Iterable[Vertex])->"Wire":""" Apply 2D fillet to a wire """f=Face.makeFromWires(self)returnf.fillet2D(radius,vertices).outerWire() [docs]defchamfer2D(self,d:float,vertices:Iterable[Vertex])->"Wire":""" Apply 2D chamfer to a wire """f=Face.makeFromWires(self)returnf.chamfer2D(d,vertices).outerWire() [docs]deffillet(self,radius:float,vertices:Optional[Iterable[Vertex]]=None)->"Wire":""" Apply 2D or 3D fillet to a wire :param radius: the radius of the fillet, must be > zero :param vertices: the vertices to delete (where the fillet will be applied). By default all vertices are deleted except ends of open wires. :return: A wire with filleted corners """edges=list(self)all_vertices=self.Vertices()n_edges=len(edges)n_vertices=len(all_vertices)newEdges=[]currentEdge=edges[0]verticesSet=set(vertices)ifverticeselseset()foriinrange(n_edges):ifi==n_edges-1andnotself.IsClosed():breaknextEdge=edges[(i+1)%n_edges]# Create a plane that is spanned by currentEdge and nextEdgecurrentDir=currentEdge.tangentAt(1)nextDir=nextEdge.tangentAt(0)normalDir=currentDir.cross(nextDir)# Check conditions for skipping fillet:# 1. The edges are parallel# 2. The vertex is not in the vertices white listifnormalDir.Length==0or(all_vertices[(i+1)%n_vertices]notinverticesSetandbool(verticesSet)):newEdges.append(currentEdge)currentEdge=nextEdgecontinue# Prepare for using ChFi2d_FilletAPIpointInPlane=currentEdge.Center().toPnt()cornerPlane=gp_Pln(pointInPlane,normalDir.toDir())filletMaker=ChFi2d_FilletAPI(currentEdge.wrapped,nextEdge.wrapped,cornerPlane)ok=filletMaker.Perform(radius)ifnotok:raiseValueError(f"Failed fillet at vertex{i+1}!")# Get the result of the fillet operationthePoint=next(iter(nextEdge)).Center().toPnt()res_arc=filletMaker.Result(thePoint,currentEdge.wrapped,nextEdge.wrapped)newEdges.append(currentEdge)newEdges.append(Edge(res_arc))currentEdge=nextEdge# Add the last edge unless we are closed, since then# currentEdge is the first edge, which was already added# (and clipped)ifnotself.IsClosed():newEdges.append(currentEdge)returnWire.assembleEdges(newEdges) [docs]defVertices(self)->List[Vertex]:""" Ordered list of vertices of the wire. """rv=[]exp=BRepTools_WireExplorer(self.wrapped)rv.append(Vertex(exp.CurrentVertex()))whileexp.More():exp.Next()rv.append(Vertex(exp.CurrentVertex()))# handle closed wires correcltyifself.IsClosed():rv=rv[:-1]returnrv [docs]def__iter__(self)->Iterator[Edge]:""" Iterate over edges in an ordered way. """exp=BRepTools_WireExplorer(self.wrapped)whileexp.Current():yieldEdge(exp.Current())exp.Next() [docs]classFace(Shape):""" a bounded surface that represents part of the boundary of a solid """wrapped:TopoDS_Facedef_geomAdaptor(self)->Geom_Surface:""" Return the underlying geometry """returnBRep_Tool.Surface_s(self.wrapped)def_uvBounds(self)->Tuple[float,float,float,float]:returnself.uvBounds()[docs]defuvBounds(self)->Tuple[float,float,float,float]:""" Parametric bounds (u_min, u_max, v_min, v_max). """returnBRepTools.UVBounds_s(self.wrapped) [docs]defparamAt(self,pt:VectorLike)->Tuple[float,float]:""" Computes the (u,v) pair closest to a given vector. :returns: (u, v) tuple :param pt: the location to compute the normal at. :type pt: a vector that lies on or close to the surface. """# get the geometrysurface=self._geomAdaptor()# project point on surfaceprojector=GeomAPI_ProjectPointOnSurf(Vector(pt).toPnt(),surface)u,v=projector.LowerDistanceParameters()returnu,v [docs]defparams(self,pts:Iterable[VectorLike],tol:float=1e-9)->Tuple[List[float],List[float]]:""" Computes (u,v) pairs closest to given vectors. :returns: list of (u, v) tuples :param pts: the points to compute the normals at. :type pts: a list of vectors that lie on the surface. """us=[]vs=[]# get the geometrysurface=self._geomAdaptor()# construct the projectorprojector=ShapeAnalysis_Surface(surface)# get the first pointit=iter(pts)pt=next(it)uv=projector.ValueOfUV(Vector(pt).toPnt(),tol)us.append(uv.X())vs.append(uv.Y())forptinit:uv=projector.NextValueOfUV(uv,Vector(pt).toPnt(),tol)us.append(uv.X())vs.append(uv.Y())returnus,vs [docs]defpositionAt(self,u:Real,v:Real)->Vector:""" Computes the position vector at the desired location in the u,v parameter space. :returns: a vector representing the position :param u: the u parametric location to compute the normal at. :param v: the v parametric location to compute the normal at. """p=gp_Pnt()vn=gp_Vec()BRepGProp_Face(self.wrapped).Normal(u,v,p,vn)returnVector(p) [docs]defpositions(self,uvs:Iterable[Tuple[Real,Real]])->List[Vector]:""" Computes position vectors at the desired locations in the u,v parameter space. :returns: list of vectors corresponding to the requested u,v positions :param uvs: iterable of u,v pairs. """p=gp_Pnt()vn=gp_Vec()rv=[]foru,vinuvs:BRepGProp_Face(self.wrapped).Normal(u,v,p,vn)rv.append(Vector(p))returnrv @multimethoddefnormalAt(self,locationVector:Optional[VectorLike]=None)->Vector:""" Computes the normal vector at the desired location on the face. :returns: a vector representing the direction :param locationVector: the location to compute the normal at. If none, the center of the face is used. :type locationVector: a vector that lies on the surface. """# get the geometrysurface=self._geomAdaptor()iflocationVectorisNone:u0,u1,v0,v1=self._uvBounds()u=0.5*(u0+u1)v=0.5*(v0+v1)else:# project point on surfaceprojector=GeomAPI_ProjectPointOnSurf(Vector(locationVector).toPnt(),surface)u,v=projector.LowerDistanceParameters()p=gp_Pnt()vn=gp_Vec()BRepGProp_Face(self.wrapped).Normal(u,v,p,vn)returnVector(vn).normalized()[docs]@normalAt.registerdefnormalAt(self,u:Real,v:Real)->Tuple[Vector,Vector]:""" Computes the normal vector at the desired location in the u,v parameter space. :returns: a vector representing the normal direction and the position :param u: the u parametric location to compute the normal at. :param v: the v parametric location to compute the normal at. """p=gp_Pnt()vn=gp_Vec()BRepGProp_Face(self.wrapped).Normal(u,v,p,vn)returnVector(vn).normalized(),Vector(p) [docs]defnormals(self,us:Iterable[Real],vs:Iterable[Real])->Tuple[List[Vector],List[Vector]]:""" Computes the normal vectors at the desired locations in the u,v parameter space. :returns: a tuple of list of vectors representing the normal directions and the positions :param us: the u parametric locations to compute the normal at. :param vs: the v parametric locations to compute the normal at. """rv_n=[]rv_p=[]p=gp_Pnt()vn=gp_Vec()BGP=BRepGProp_Face(self.wrapped)foru,vinzip(us,vs):BGP.Normal(u,v,p,vn)rv_n.append(Vector(vn).normalized())rv_p.append(Vector(p))returnrv_n,rv_p [docs]defCenter(self)->Vector:Properties=GProp_GProps()BRepGProp.SurfaceProperties_s(self.wrapped,Properties)returnVector(Properties.CentreOfMass()) defouterWire(self)->Wire:returnWire(BRepTools.OuterWire_s(self.wrapped))definnerWires(self)->List[Wire]:outer=self.outerWire()return[wforwinself.Wires()ifnotw.isSame(outer)][docs]@classmethoddefmakeNSidedSurface(cls,edges:Iterable[Union[Edge,Wire]],constraints:Iterable[Union[Edge,Wire,VectorLike,gp_Pnt]],continuity:GeomAbs_Shape=GeomAbs_C0,degree:int=3,nbPtsOnCur:int=15,nbIter:int=2,anisotropy:bool=False,tol2d:float=0.00001,tol3d:float=0.0001,tolAng:float=0.01,tolCurv:float=0.1,maxDeg:int=8,maxSegments:int=9,)->"Face":""" Returns a surface enclosed by a closed polygon defined by 'edges' and 'constraints'. :param edges: edges :type edges: list of edges or wires :param constraints: constraints :type constraints: list of points or edges :param continuity: OCC.Core.GeomAbs continuity condition :param degree: >=2 :param nbPtsOnCur: number of points on curve >= 15 :param nbIter: number of iterations >= 2 :param anisotropy: bool Anisotropy :param tol2d: 2D tolerance >0 :param tol3d: 3D tolerance >0 :param tolAng: angular tolerance :param tolCurv: tolerance for curvature >0 :param maxDeg: highest polynomial degree >= 2 :param maxSegments: greatest number of segments >= 2 """n_sided=BRepOffsetAPI_MakeFilling(degree,nbPtsOnCur,nbIter,anisotropy,tol2d,tol3d,tolAng,tolCurv,maxDeg,maxSegments,)# outer edgesforelinedges:ifisinstance(el,Edge):n_sided.Add(el.wrapped,continuity)else:forel_edgeinel.Edges():n_sided.Add(el_edge.wrapped,continuity)# (inner) constraintsforcinconstraints:ifisinstance(c,gp_Pnt):n_sided.Add(c)elifisinstance(c,Vector):n_sided.Add(c.toPnt())elifisinstance(c,tuple):n_sided.Add(Vector(c).toPnt())elifisinstance(c,Edge):n_sided.Add(c.wrapped,GeomAbs_C0,False)elifisinstance(c,Wire):foreinc.Edges():n_sided.Add(e.wrapped,GeomAbs_C0,False)else:raiseValueError(f"Invalid constraint{c}")# build, fix and returnn_sided.Build()face=n_sided.Shape()returnFace(face).fix() @classmethoddefmakePlane(cls,length:Optional[float]=None,width:Optional[float]=None,basePnt:VectorLike=(0,0,0),dir:VectorLike=(0,0,1),)->"Face":basePnt=Vector(basePnt)dir=Vector(dir)pln_geom=gp_Pln(basePnt.toPnt(),dir.toDir())iflengthandwidth:pln_shape=BRepBuilderAPI_MakeFace(pln_geom,-width*0.5,width*0.5,-length*0.5,length*0.5).Face()else:pln_shape=BRepBuilderAPI_MakeFace(pln_geom).Face()returncls(pln_shape)@overload@classmethoddefmakeRuledSurface(cls,edgeOrWire1:Edge,edgeOrWire2:Edge)->"Face":...@overload@classmethoddefmakeRuledSurface(cls,edgeOrWire1:Wire,edgeOrWire2:Wire)->"Face":...[docs]@classmethoddefmakeRuledSurface(cls,edgeOrWire1,edgeOrWire2):""" makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface Create a ruled surface out of two edges or wires. If wires are used then these must have the same number of edges """ifisinstance(edgeOrWire1,Wire):returncls.cast(BRepFill.Shell_s(edgeOrWire1.wrapped,edgeOrWire2.wrapped))else:returncls.cast(BRepFill.Face_s(edgeOrWire1.wrapped,edgeOrWire2.wrapped)) [docs]@classmethoddefmakeFromWires(cls,outerWire:Wire,innerWires:List[Wire]=[])->"Face":""" Makes a planar face from one or more wires """ifinnerWiresandnotouterWire.IsClosed():raiseValueError("Cannot build face(s): outer wire is not closed")# check if wires are coplanarws=Compound.makeCompound([outerWire]+innerWires)ifnotBRepLib_FindSurface(ws.wrapped,OnlyPlane=True).Found():raiseValueError("Cannot build face(s): wires not planar")# fix outer wiresf_s=ShapeFix_Shape(outerWire.wrapped)sf_s.Perform()wo=TopoDS.Wire_s(sf_s.Shape())face_builder=BRepBuilderAPI_MakeFace(wo,True)forwininnerWires:ifnotw.IsClosed():raiseValueError("Cannot build face(s): inner wire is not closed")face_builder.Add(w.wrapped)face_builder.Build()ifnotface_builder.IsDone():raiseValueError(f"Cannot build face(s):{face_builder.Error()}")face=face_builder.Face()sf_f=ShapeFix_Face(face)sf_f.FixOrientation()sf_f.Perform()returncls(sf_f.Result()) [docs]@classmethoddefmakeSplineApprox(cls,points:List[List[Vector]],tol:float=1e-2,smoothing:Optional[Tuple[float,float,float]]=None,minDeg:int=1,maxDeg:int=3,)->"Face":""" Approximate a spline surface through the provided points. :param points: a 2D list of Vectors that represent the points :param tol: tolerance of the algorithm (consult OCC documentation). :param smoothing: optional tuple of 3 weights use for variational smoothing (default: None) :param minDeg: minimum spline degree. Enforced only when smothing is None (default: 1) :param maxDeg: maximum spline degree (default: 6) """points_=TColgp_HArray2OfPnt(1,len(points),1,len(points[0]))fori,viinenumerate(points):forj,vinenumerate(vi):points_.SetValue(i+1,j+1,v.toPnt())ifsmoothing:spline_builder=GeomAPI_PointsToBSplineSurface(points_,*smoothing,DegMax=maxDeg,Tol3D=tol)else:spline_builder=GeomAPI_PointsToBSplineSurface(points_,DegMin=minDeg,DegMax=maxDeg,Tol3D=tol)ifnotspline_builder.IsDone():raiseValueError("B-spline approximation failed")spline_geom=spline_builder.Surface()returncls(BRepBuilderAPI_MakeFace(spline_geom,Precision.Confusion_s()).Face()) [docs]deffillet2D(self,radius:float,vertices:Iterable[Vertex])->"Face":""" Apply 2D fillet to a face """fillet_builder=BRepFilletAPI_MakeFillet2d(self.wrapped)forvinvertices:fillet_builder.AddFillet(v.wrapped,radius)fillet_builder.Build()returnself.__class__(fillet_builder.Shape()) [docs]defchamfer2D(self,d:float,vertices:Iterable[Vertex])->"Face":""" Apply 2D chamfer to a face """chamfer_builder=BRepFilletAPI_MakeFillet2d(self.wrapped)edge_map=self._entitiesFrom("Vertex","Edge")forvinvertices:edges=edge_map[v]iflen(edges)<2:raiseValueError("Cannot chamfer at this location")e1,e2=edgeschamfer_builder.AddChamfer(TopoDS.Edge_s(e1.wrapped),TopoDS.Edge_s(e2.wrapped),d,d)chamfer_builder.Build()returnself.__class__(chamfer_builder.Shape()).fix() [docs]deftoPln(self)->gp_Pln:""" Convert this face to a gp_Pln. Note the Location of the resulting plane may not equal the center of this face, however the resulting plane will still contain the center of this face. """adaptor=BRepAdaptor_Surface(self.wrapped)returnadaptor.Plane() [docs]defthicken(self,thickness:float)->"Solid":""" Return a thickened face """builder=BRepOffset_MakeOffset()builder.Initialize(self.wrapped,thickness,1.0e-6,BRepOffset_Mode.BRepOffset_Skin,False,False,GeomAbs_Intersection,True,)# The last True is important to make a solidbuilder.MakeOffsetShape()returnSolid(builder.Shape()) @classmethoddefconstructOn(cls,f:"Face",outer:"Wire",*inner:"Wire")->Self:returnf.trim(outer,*inner)defproject(self,other:"Face",d:VectorLike)->"Face":outer_p=tcast(Wire,self.outerWire().project(other,d))inner_p=(tcast(Wire,w.project(other,d))forwinself.innerWires())returnself.constructOn(other,outer_p,*inner_p)[docs]deftoArcs(self,tolerance:float=1e-3)->"Face":""" Approximate planar face with arcs and straight line segments. :param tolerance: Approximation tolerance. """returnself.__class__(BRepAlgo.ConvertFace_s(self.wrapped,tolerance)) [docs]@multimethoddeftrim(self,u0:Real,u1:Real,v0:Real,v1:Real,tol:Real=1e-6)->Self:""" Trim the face in the (u,v) space to (u0, u1)x(v1, v2). NB: this operation is done on the base geometry. """bldr=BRepBuilderAPI_MakeFace(self._geomAdaptor(),u0,u1,v0,v1,tol)returnself.__class__(bldr.Shape()) @trim.registerdef_(self,pt1:Tuple[Real,Real],pt2:Tuple[Real,Real],pt3:Tuple[Real,Real],*pts:Tuple[Real,Real],)->Self:""" Trim the face using a polyline defined in the (u,v) space. """segs_uv=[]geom=self._geomAdaptor()# build (u,v) segmentsforel1,el2inzip((pt1,pt2,pt3,*pts),(pt2,pt3,*pts,pt1)):segs_uv.append(GCE2d_MakeSegment(gp_Pnt2d(*el1),gp_Pnt2d(*el2)).Value())# convert to edgesedges=[]forseginsegs_uv:edges.append(BRepBuilderAPI_MakeEdge(seg,geom).Edge())# convert to a wirebuilder=BRepBuilderAPI_MakeWire()tmp=TopTools_ListOfShape()foredgeinedges:tmp.Append(edge)builder.Add(tmp)w=builder.Wire()BRepLib.BuildCurves3d_s(w)# construct the final trimmed facereturnself.constructOn(self,Wire(w))@trim.registerdef_(self,outer:Wire,*inner:Wire)->Self:""" Trim using wires. The provided wires need to have a pcurve on self. """bldr=BRepBuilderAPI_MakeFace(self._geomAdaptor(),outer.wrapped)forwininner:bldr.Add(TopoDS.Wire_s(w.wrapped))returnself.__class__(bldr.Face()).fix()[docs]defisoline(self,param:Real,direction:Literal["u","v"]="v")->Edge:""" Construct an isoline. """u1,u2,v1,v2=self._uvBounds()ifdirection=="u":iso=GeomAbs_IsoType.GeomAbs_IsoUp1,p2=v1,v2else:iso=GeomAbs_IsoType.GeomAbs_IsoVp1,p2=u1,u2adaptor=Adaptor3d_IsoCurve(GeomAdaptor_Surface(self._geomAdaptor()),iso,param)returnEdge(_adaptor_curve_to_edge(adaptor,p1,p2)) [docs]defisolines(self,params:Iterable[Real],direction:Literal["u","v"]="v")->List[Edge]:""" Construct multiple isolines. """return[self.isoline(p,direction)forpinparams] [docs]defextend(self,d:float,umin:bool=True,umax:bool=True,vmin:bool=True,vmax:bool=True,)->"Face":""" Extend a face. Does not work well in periodic directions. :param d: length of the extension. :param umin: extend along the umin isoline. :param umax: extend along the umax isoline. :param vmin: extend along the vmin isoline. :param vmax: extend along the vmax isoline. """# convert to NURBS if neededtmp=self.toNURBS()ifself.geomType()!="BSPLINE"elseselfrv=TopoDS_Face()BRepLib.ExtendFace_s(tmp.wrapped,d,umin,umax,vmin,vmax,rv)returnself.__class__(rv) [docs]defaddHole(self,*inner:Wire|Edge)->Self:""" Add one or more holes. """bldr=BRepBuilderAPI_MakeFace(self.wrapped)forwininner:bldr.Add(TopoDS.Wire_s(w.wrappedifisinstance(w,Wire)elsewire(w).wrapped))returnself.__class__(bldr.Face()).fix() [docs]classShell(Shape):""" the outer boundary of a surface """wrapped:TopoDS_Shell[docs]@classmethoddefmakeShell(cls,listOfFaces:Iterable[Face])->"Shell":""" Makes a shell from faces. """shell_builder=BRepBuilderAPI_Sewing()forfaceinlistOfFaces:shell_builder.Add(face.wrapped)shell_builder.Perform()s=shell_builder.SewedShape()returncls(s) TS=TypeVar("TS",bound=ShapeProtocol)[docs]classMixin3D(object):[docs]deffillet(self:Any,radius:float,edgeList:Iterable[Edge])->Any:""" Fillets the specified edges of this solid. :param radius: float > 0, the radius of the fillet :param edgeList: a list of Edge objects, which must belong to this solid :return: Filleted solid """nativeEdges=[e.wrappedforeinedgeList]fillet_builder=BRepFilletAPI_MakeFillet(self.wrapped)foreinnativeEdges:fillet_builder.Add(radius,e)returnself.__class__(fillet_builder.Shape()) [docs]defchamfer(self:Any,length:float,length2:Optional[float],edgeList:Iterable[Edge])->Any:""" Chamfers the specified edges of this solid. :param length: length > 0, the length (length) of the chamfer :param length2: length2 > 0, optional parameter for asymmetrical chamfer. Should be `None` if not required. :param edgeList: a list of Edge objects, which must belong to this solid :return: Chamfered solid """nativeEdges=[e.wrappedforeinedgeList]# make a edge --> faces mappingedge_face_map=TopTools_IndexedDataMapOfShapeListOfShape()TopExp.MapShapesAndAncestors_s(self.wrapped,ta.TopAbs_EDGE,ta.TopAbs_FACE,edge_face_map)# note: we prefer 'length' word to 'radius' as opposed to FreeCAD's APIchamfer_builder=BRepFilletAPI_MakeChamfer(self.wrapped)iflength2:d1=lengthd2=length2else:d1=lengthd2=lengthforeinnativeEdges:face=edge_face_map.FindFromKey(e).First()chamfer_builder.Add(d1,d2,e,TopoDS.Face_s(face))# NB: edge_face_map return a generic TopoDS_Shapereturnself.__class__(chamfer_builder.Shape()) [docs]defshell(self:Any,faceList:Optional[Iterable[Face]],thickness:float,tolerance:float=0.0001,kind:Literal["arc","intersection"]="arc",)->Any:""" Make a shelled solid of self. :param faceList: List of faces to be removed, which must be part of the solid. Can be an empty list. :param thickness: Floating point thickness. Positive shells outwards, negative shells inwards. :param tolerance: Modelling tolerance of the method, default=0.0001. :return: A shelled solid. """kind_dict={"arc":GeomAbs_JoinType.GeomAbs_Arc,"intersection":GeomAbs_JoinType.GeomAbs_Intersection,}occ_faces_list=TopTools_ListOfShape()shell_builder=BRepOffsetAPI_MakeThickSolid()iffaceList:forfinfaceList:occ_faces_list.Append(f.wrapped)shell_builder.MakeThickSolidByJoin(self.wrapped,occ_faces_list,thickness,tolerance,Intersection=True,Join=kind_dict[kind],)shell_builder.Build()iffaceList:rv=self.__class__(shell_builder.Shape())else:# if no faces provided a watertight solid will be constructeds1=self.__class__(shell_builder.Shape()).Shells()[0].wrappeds2=self.Shells()[0].wrapped# s1 can be outer or inner shell depending on the thickness signifthickness>0:sol=BRepBuilderAPI_MakeSolid(s1,s2)else:sol=BRepBuilderAPI_MakeSolid(s2,s1)# fix needed for the orientationsrv=self.__class__(sol.Shape()).fix()returnrv [docs]defisInside(self:ShapeProtocol,point:VectorLike,tolerance:float=1.0e-6)->bool:""" Returns whether or not the point is inside a solid or compound object within the specified tolerance. :param point: tuple or Vector representing 3D point to be tested :param tolerance: tolerance for inside determination, default=1.0e-6 :return: bool indicating whether or not point is within solid """ifisinstance(point,Vector):point=point.toTuple()solid_classifier=BRepClass3d_SolidClassifier(self.wrapped)solid_classifier.Perform(gp_Pnt(*point),tolerance)returnsolid_classifier.State()==ta.TopAbs_INorsolid_classifier.IsOnAFace() @multimethoddefdprism(self:TS,basis:Optional[Face],profiles:List[Wire],depth:Optional[Real]=None,taper:Real=0,upToFace:Optional[Face]=None,thruAll:bool=True,additive:bool=True,)->"Solid":""" Make a prismatic feature (additive or subtractive) :param basis: face to perform the operation on :param profiles: list of profiles :param depth: depth of the cut or extrusion :param upToFace: a face to extrude until :param thruAll: cut thruAll :return: a Solid object """sorted_profiles=sortWiresByBuildOrder(profiles)faces=[Face.makeFromWires(p[0],p[1:])forpinsorted_profiles]returnself.dprism(basis,faces,depth,taper,upToFace,thruAll,additive)[docs]@dprism.registerdefdprism(self:TS,basis:Optional[Face],faces:List[Face],depth:Optional[Real]=None,taper:Real=0,upToFace:Optional[Face]=None,thruAll:bool=True,additive:bool=True,)->"Solid":shape:Union[TopoDS_Shape,TopoDS_Solid]=self.wrappedforfaceinfaces:feat=BRepFeat_MakeDPrism(shape,face.wrapped,basis.wrappedifbasiselseTopoDS_Face(),radians(taper),additive,False,)ifupToFaceisnotNone:feat.Perform(upToFace.wrapped)elifthruAllordepthisNone:feat.PerformThruAll()else:feat.Perform(depth)shape=feat.Shape()returnself.__class__(shape) [docs]classSolid(Shape,Mixin3D):""" a single solid """wrapped:TopoDS_Solid[docs]@classmethod@deprecate()definterpPlate(cls,surf_edges,surf_pts,thickness,degree=3,nbPtsOnCur=15,nbIter=2,anisotropy=False,tol2d=0.00001,tol3d=0.0001,tolAng=0.01,tolCurv=0.1,maxDeg=8,maxSegments=9,)->Union["Solid",Face]:""" Returns a plate surface that is 'thickness' thick, enclosed by 'surf_edge_pts' points, and going through 'surf_pts' points. :param surf_edges: list of [x,y,z] float ordered coordinates or list of ordered or unordered wires :param surf_pts: list of [x,y,z] float coordinates (uses only edges if []) :param thickness: thickness may be negative or positive depending on direction, (returns 2D surface if 0) :param degree: >=2 :param nbPtsOnCur: number of points on curve >= 15 :param nbIter: number of iterations >= 2 :param anisotropy: bool Anisotropy :param tol2d: 2D tolerance >0 :param tol3d: 3D tolerance >0 :param tolAng: angular tolerance :param tolCurv: tolerance for curvature >0 :param maxDeg: highest polynomial degree >= 2 :param maxSegments: greatest number of segments >= 2 """# POINTS CONSTRAINTS: list of (x,y,z) points, optional.pts_array=[gp_Pnt(*pt)forptinsurf_pts]# EDGE CONSTRAINTS# If a list of wires is provided, make a closed wireifnotisinstance(surf_edges,list):surf_edges=[o.vals()[0]foroinsurf_edges.all()]surf_edges=Wire.assembleEdges(surf_edges)w=surf_edges.wrapped# If a list of (x,y,z) points provided, build closed polygonifisinstance(surf_edges,list):e_array=[Vector(*e)foreinsurf_edges]wire_builder=BRepBuilderAPI_MakePolygon()foreine_array:# Create polygon from edgeswire_builder.Add(e.toPnt())wire_builder.Close()w=wire_builder.Wire()edges=[iforiinShape(w).Edges()]# MAKE SURFACEcontinuity=GeomAbs_C0# Fixed, changing to anything else crashes.face=Face.makeNSidedSurface(edges,pts_array,continuity,degree,nbPtsOnCur,nbIter,anisotropy,tol2d,tol3d,tolAng,tolCurv,maxDeg,maxSegments,)# THICKEN SURFACEif(abs(thickness)>0):# abs() because negative values are allowed to set direction of thickeningreturnface.thicken(thickness)else:# Return 2D surface onlyreturnface [docs]@staticmethoddefisSolid(obj:Shape)->bool:""" Returns true if the object is a solid, false otherwise """ifhasattr(obj,"ShapeType"):ifobj.ShapeType()=="Solid"or(obj.ShapeType()=="Compound"andlen(obj.Solids())>0):returnTruereturnFalse [docs]@classmethoddefmakeSolid(cls,shell:Shell)->"Solid":""" Makes a solid from a single shell. """returncls(ShapeFix_Solid().SolidFromShell(shell.wrapped)) [docs]@classmethoddefmakeBox(cls,length:float,width:float,height:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),)->"Solid":""" 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) """returncls(BRepPrimAPI_MakeBox(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),length,width,height).Shape()) [docs]@classmethoddefmakeCone(cls,radius1:float,radius2:float,height:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angleDegrees:float=360,)->"Solid":""" Make a cone with given radii and height By default pnt=Vector(0,0,0), dir=Vector(0,0,1) and angle=360 """returncls(BRepPrimAPI_MakeCone(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),radius1,radius2,height,radians(angleDegrees),).Shape()) [docs]@classmethoddefmakeCylinder(cls,radius:float,height:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angleDegrees:float=360,)->"Solid":""" 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 """returncls(BRepPrimAPI_MakeCylinder(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),radius,height,radians(angleDegrees),).Shape()) [docs]@classmethoddefmakeTorus(cls,radius1:float,radius2:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angleDegrees1:float=0,angleDegrees2:float=360,)->"Solid":""" makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) -- Make a torus with a given radii and angles By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0 ,angle1=360 and angle=360 """returncls(BRepPrimAPI_MakeTorus(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),radius1,radius2,radians(angleDegrees1),radians(angleDegrees2),).Shape()) [docs]@classmethoddefmakeLoft(cls,listOfWire:List[Wire],ruled:bool=False)->"Solid":""" makes a loft from a list of wires The wires will be converted into faces when possible-- it is presumed that nobody ever actually wants to make an infinitely thin shell for a real FreeCADPart. """# the True flag requests building a solid instead of a shell.iflen(listOfWire)<2:raiseValueError("More than one wire is required")loft_builder=BRepOffsetAPI_ThruSections(True,ruled)forwinlistOfWire:loft_builder.AddWire(w.wrapped)loft_builder.Build()returncls(loft_builder.Shape()) [docs]@classmethoddefmakeWedge(cls,dx:float,dy:float,dz:float,xmin:float,zmin:float,xmax:float,zmax:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),)->"Solid":""" Make a wedge located in pnt By default pnt=Vector(0,0,0) and dir=Vector(0,0,1) """returncls(BRepPrimAPI_MakeWedge(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),dx,dy,dz,xmin,zmin,xmax,zmax,).Solid()) [docs]@classmethoddefmakeSphere(cls,radius:float,pnt:VectorLike=Vector(0,0,0),dir:VectorLike=Vector(0,0,1),angleDegrees1:float=0,angleDegrees2:float=90,angleDegrees3:float=360,)->"Shape":""" 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 """returncls(BRepPrimAPI_MakeSphere(gp_Ax2(Vector(pnt).toPnt(),Vector(dir).toDir()),radius,radians(angleDegrees1),radians(angleDegrees2),radians(angleDegrees3),).Shape()) @classmethoddef_extrudeAuxSpine(cls,wire:TopoDS_Wire,spine:TopoDS_Wire,auxSpine:TopoDS_Wire)->TopoDS_Shape:""" Helper function for extrudeLinearWithRotation """extrude_builder=BRepOffsetAPI_MakePipeShell(spine)extrude_builder.SetMode(auxSpine,False)# auxiliary spineextrude_builder.Add(wire)extrude_builder.Build()extrude_builder.MakeSolid()returnextrude_builder.Shape()@multimethoddefextrudeLinearWithRotation(cls,outerWire:Wire,innerWires:List[Wire],vecCenter:VectorLike,vecNormal:VectorLike,angleDegrees:Real,)->"Solid":""" Creates a 'twisted prism' by extruding, while simultaneously rotating around the extrusion vector. Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the construction methods used here are different enough that they should be separate. At a high level, the steps followed are: (1) accept a set of wires (2) create another set of wires like this one, but which are transformed and rotated (3) create a ruledSurface between the sets of wires (4) create a shell and compute the resulting object :param outerWire: the outermost wire :param innerWires: a list of inner wires :param vecCenter: the center point about which to rotate. the axis of rotation is defined by vecNormal, located at vecCenter. :param vecNormal: a vector along which to extrude the wires :param angleDegrees: the angle to rotate through while extruding :return: a Solid object """# make straight spinestraight_spine_e=Edge.makeLine(vecCenter,vecCenter.add(vecNormal))straight_spine_w=Wire.combine([straight_spine_e,])[0].wrapped# make an auxiliary spinepitch=360.0/angleDegrees*vecNormal.Lengthradius=1aux_spine_w=Wire.makeHelix(pitch,vecNormal.Length,radius,center=vecCenter,dir=vecNormal).wrapped# extrude the outer wireouter_solid=cls._extrudeAuxSpine(outerWire.wrapped,straight_spine_w,aux_spine_w)# extrude inner wiresinner_solids=[cls._extrudeAuxSpine(w.wrapped,straight_spine_w,aux_spine_w)forwininnerWires]# combine the inner solids into compoundinner_comp=Compound._makeCompound(inner_solids)# subtract from the outer solidreturncls(BRepAlgoAPI_Cut(outer_solid,inner_comp).Shape())[docs]@classmethod@extrudeLinearWithRotation.registerdefextrudeLinearWithRotation(cls,face:Face,vecCenter:VectorLike,vecNormal:VectorLike,angleDegrees:Real,)->"Solid":returncls.extrudeLinearWithRotation(face.outerWire(),face.innerWires(),vecCenter,vecNormal,angleDegrees) @multimethoddefextrudeLinear(cls,outerWire:Wire,innerWires:List[Wire],vecNormal:VectorLike,taper:Real=0,)->"Solid":""" Attempt to extrude the list of wires into a prismatic solid in the provided direction :param outerWire: the outermost wire :param innerWires: a list of inner wires :param vecNormal: a vector along which to extrude the wires :param taper: taper angle, default=0 :return: a Solid object The wires must not intersect Extruding wires is very non-trivial. Nested wires imply very different geometry, and there are many geometries that are invalid. In general, the following conditions must be met: * all wires must be closed * there cannot be any intersecting or self-intersecting wires * wires must be listed from outside in * more than one levels of nesting is not supported reliably This method will attempt to sort the wires, but there is much work remaining to make this method reliable. """iftaper==0:face=Face.makeFromWires(outerWire,innerWires)else:face=Face.makeFromWires(outerWire)returncls.extrudeLinear(face,vecNormal,taper)[docs]@classmethod@extrudeLinear.registerdefextrudeLinear(cls,face:Face,vecNormal:VectorLike,taper:Real=0,)->"Solid":iftaper==0:prism_builder:Any=BRepPrimAPI_MakePrism(face.wrapped,Vector(vecNormal).wrapped,True)else:faceNormal=face.normalAt()d=1ifvecNormal.getAngle(faceNormal)<radians(90.0)else-1# Divided by cos of taper angle to ensure the height chosen by the user is respectedprism_builder=LocOpe_DPrism(face.wrapped,(d*vecNormal.Length)/cos(radians(taper)),d*radians(taper),)returncls(prism_builder.Shape()) @multimethoddefrevolve(cls,outerWire:Wire,innerWires:List[Wire],angleDegrees:Real,axisStart:VectorLike,axisEnd:VectorLike,)->"Solid":""" Attempt to revolve the list of wires into a solid in the provided direction :param outerWire: the outermost wire :param innerWires: a list of inner wires :param angleDegrees: the angle to revolve through. :type angleDegrees: float, anything less than 360 degrees will leave the shape open :param axisStart: the start point of the axis of rotation :param axisEnd: the end point of the axis of rotation :return: a Solid object The wires must not intersect * all wires must be closed * there cannot be any intersecting or self-intersecting wires * wires must be listed from outside in * more than one levels of nesting is not supported reliably * the wire(s) that you're revolving cannot be centered This method will attempt to sort the wires, but there is much work remaining to make this method reliable. """face=Face.makeFromWires(outerWire,innerWires)returncls.revolve(face,angleDegrees,axisStart,axisEnd)[docs]@classmethod@revolve.registerdefrevolve(cls,face:Face,angleDegrees:Real,axisStart:VectorLike,axisEnd:VectorLike,)->"Solid":v1=Vector(axisStart)v2=Vector(axisEnd)v2=v2-v1revol_builder=BRepPrimAPI_MakeRevol(face.wrapped,gp_Ax1(v1.toPnt(),v2.toDir()),radians(angleDegrees),True)returncls(revol_builder.Shape()) _transModeDict={"transformed":BRepBuilderAPI_Transformed,"round":BRepBuilderAPI_RoundCorner,"right":BRepBuilderAPI_RightCorner,}@classmethoddef_setSweepMode(cls,builder:BRepOffsetAPI_MakePipeShell,path:Union[Wire,Edge],mode:Union[Vector,Wire,Edge],)->bool:rotate=Falseifisinstance(mode,Vector):ax=gp_Ax2()ax.SetLocation(path.startPoint().toPnt())ax.SetDirection(mode.toDir())builder.SetMode(ax)rotate=Trueelifisinstance(mode,(Wire,Edge)):builder.SetMode(cls._toWire(mode).wrapped,True)returnrotate@staticmethoddef_toWire(p:Union[Edge,Wire])->Wire:ifisinstance(p,Edge):rv=Wire.assembleEdges([p,])else:rv=preturnrv@multimethoddefsweep(cls,outerWire:Wire,innerWires:List[Wire],path:Union[Wire,Edge],makeSolid:bool=True,isFrenet:bool=False,mode:Union[Vector,Wire,Edge,None]=None,transitionMode:Literal["transformed","round","right"]="transformed",)->"Shape":""" Attempt to sweep the list of wires into a prismatic solid along the provided path :param outerWire: the outermost wire :param innerWires: a list of inner wires :param path: The wire to sweep the face resulting from the wires over :param makeSolid: return Solid or Shell (default True) :param isFrenet: Frenet mode (default False) :param mode: additional sweep mode parameters :param transitionMode: handling of profile orientation at C1 path discontinuities. Possible values are {'transformed','round', 'right'} (default: 'right'). :return: a Solid object """p=cls._toWire(path)shapes=[]forwin[outerWire]+innerWires:builder=BRepOffsetAPI_MakePipeShell(p.wrapped)translate=Falserotate=False# handle sweep modeifmode:rotate=cls._setSweepMode(builder,path,mode)else:builder.SetMode(isFrenet)builder.SetTransitionMode(cls._transModeDict[transitionMode])builder.Add(w.wrapped,translate,rotate)builder.Build()ifmakeSolid:builder.MakeSolid()shapes.append(Shape.cast(builder.Shape()))rv,inner_shapes=shapes[0],shapes[1:]ifinner_shapes:rv=rv.cut(*inner_shapes)returnrv[docs]@classmethod@sweep.registerdefsweep(cls,face:Face,path:Union[Wire,Edge],makeSolid:bool=True,isFrenet:bool=False,mode:Union[Vector,Wire,Edge,None]=None,transitionMode:Literal["transformed","round","right"]="transformed",)->"Shape":returncls.sweep(face.outerWire(),face.innerWires(),path,makeSolid,isFrenet,mode,transitionMode,) [docs]@classmethoddefsweep_multi(cls,profiles:Iterable[Union[Wire,Face]],path:Union[Wire,Edge],makeSolid:bool=True,isFrenet:bool=False,mode:Union[Vector,Wire,Edge,None]=None,)->"Solid":""" Multi section sweep. Only single outer profile per section is allowed. :param profiles: list of profiles :param path: The wire to sweep the face resulting from the wires over :param mode: additional sweep mode parameters. :return: a Solid object """ifisinstance(path,Edge):w=Wire.assembleEdges([path,]).wrappedelse:w=path.wrappedbuilder=BRepOffsetAPI_MakePipeShell(w)translate=Falserotate=Falseifmode:rotate=cls._setSweepMode(builder,path,mode)else:builder.SetMode(isFrenet)forpinprofiles:w=p.wrappedifisinstance(p,Wire)elsep.outerWire().wrappedbuilder.Add(w,translate,rotate)builder.Build()ifmakeSolid:builder.MakeSolid()returncls(builder.Shape()) [docs]defouterShell(self)->Shell:""" Returns outer shell. """returnShell(BRepClass3d.OuterShell_s(self.wrapped)) [docs]definnerShells(self)->List[Shell]:""" Returns inner shells. """outer=self.outerShell()return[sforsinself.Shells()ifnots.isSame(outer)] [docs]defaddCavity(self,*shells:Union[Shell,"Solid"])->Self:""" Add one or more cavities. """builder=BRepBuilderAPI_MakeSolid(self.wrapped)# if a solid is provided only outer shell is addedforshinshells:builder.Add(sh.wrappedifisinstance(sh,Shell)elsesh.outerShell().wrapped)# fix orientationssf=ShapeFix_Solid(builder.Solid())sf.Perform()returnself.__class__(sf.Solid()) [docs]classCompSolid(Shape,Mixin3D):""" a single compsolid """wrapped:TopoDS_CompSolid [docs]classCompound(Shape,Mixin3D):""" a collection of disconnected solids """wrapped:TopoDS_Compound@staticmethoddef_makeCompound(listOfShapes:Iterable[TopoDS_Shape])->TopoDS_Compound:comp=TopoDS_Compound()comp_builder=TopoDS_Builder()comp_builder.MakeCompound(comp)forsinlistOfShapes:comp_builder.Add(comp,s)returncomp[docs]defremove(self,*shape:Shape):""" Remove the specified shapes. """comp_builder=TopoDS_Builder()forsinshape:comp_builder.Remove(self.wrapped,s.wrapped) [docs]@classmethoddefmakeCompound(cls,listOfShapes:Iterable[Shape])->"Compound":""" Create a compound out of a list of shapes """returncls(cls._makeCompound((s.wrappedforsinlistOfShapes))) [docs]@classmethoddefmakeText(cls,text:str,size:float,height:float,font:str="Arial",fontPath:Optional[str]=None,kind:Literal["regular","bold","italic"]="regular",halign:Literal["center","left","right"]="center",valign:Literal["center","top","bottom"]="center",position:Plane=Plane.XY(),)->"Shape":""" Create a 3D text """font_kind={"regular":Font_FA_Regular,"bold":Font_FA_Bold,"italic":Font_FA_Italic,}[kind]mgr=Font_FontMgr.GetInstance_s()iffontPathandmgr.CheckFont(TCollection_AsciiString(fontPath).ToCString()):font_t=Font_SystemFont(TCollection_AsciiString(fontPath))font_t.SetFontPath(font_kind,TCollection_AsciiString(fontPath))mgr.RegisterFont(font_t,True)else:font_t=mgr.FindFont(TCollection_AsciiString(font),font_kind)builder=Font_BRepTextBuilder()font_i=StdPrs_BRepFont(NCollection_Utf8String(font_t.FontName().ToCString()),font_kind,float(size),)ifhalign=="left":theHAlign=Graphic3d_HTA_LEFTelifhalign=="center":theHAlign=Graphic3d_HTA_CENTERelse:# halign == "right"theHAlign=Graphic3d_HTA_RIGHTifvalign=="bottom":theVAlign=Graphic3d_VTA_BOTTOMelifvalign=="center":theVAlign=Graphic3d_VTA_CENTERelse:# valign == "top":theVAlign=Graphic3d_VTA_TOPtext_flat=Shape(builder.Perform(font_i,NCollection_Utf8String(text),theHAlign=theHAlign,theVAlign=theVAlign,))ifheight!=0:vecNormal=text_flat.Faces()[0].normalAt()*heighttext_3d=BRepPrimAPI_MakePrism(text_flat.wrapped,vecNormal.wrapped)rv=cls(text_3d.Shape()).transformShape(position.rG)else:rv=text_flat.transformShape(position.rG)returnrv [docs]def__bool__(self)->bool:""" Check if empty. """returnTopoDS_Iterator(self.wrapped).More() [docs]defcut(self,*toCut:"Shape",tol:Optional[float]=None)->"Compound":""" Remove the positional arguments from this Shape. :param tol: Fuzzy mode tolerance """cut_op=BRepAlgoAPI_Cut()iftol:cut_op.SetFuzzyValue(tol)returntcast(Compound,self._bool_op(self,toCut,cut_op)) [docs]deffuse(self,*toFuse:Shape,glue:bool=False,tol:Optional[float]=None)->"Compound":""" Fuse shapes together """fuse_op=BRepAlgoAPI_Fuse()ifglue:fuse_op.SetGlue(BOPAlgo_GlueEnum.BOPAlgo_GlueShift)iftol:fuse_op.SetFuzzyValue(tol)args=tuple(self)+toFuseiflen(args)<=1:rv:Shape=args[0]else:rv=self._bool_op(args[:1],args[1:],fuse_op)# fuse_op.RefineEdges()# fuse_op.FuseEdges()returntcast(Compound,rv) [docs]defintersect(self,*toIntersect:"Shape",tol:Optional[float]=None)->"Compound":""" Intersection of the positional arguments and this Shape. :param tol: Fuzzy mode tolerance """intersect_op=BRepAlgoAPI_Common()iftol:intersect_op.SetFuzzyValue(tol)returntcast(Compound,self._bool_op(self,toIntersect,intersect_op)) [docs]defancestors(self,shape:"Shape",kind:Shapes)->"Compound":""" Iterate over ancestors, i.e. shapes of same kind within shape that contain elements of self. """shape_map=TopTools_IndexedDataMapOfShapeListOfShape()shapetypes=set(shapetype(ch.wrapped)forchinself)fortinshapetypes:TopExp.MapShapesAndAncestors_s(shape.wrapped,t,inverse_shape_LUT[kind],shape_map)returnCompound.makeCompound(Shape.cast(a)forsinselfforainshape_map.FindFromKey(s.wrapped)) [docs]defsiblings(self,shape:"Shape",kind:Shapes,level:int=1)->"Compound":""" Iterate over siblings, i.e. shapes within shape that share subshapes of kind with the elements of self. """shape_map=TopTools_IndexedDataMapOfShapeListOfShape()shapetypes=set(shapetype(ch.wrapped)forchinself)fortinshapetypes:TopExp.MapShapesAndAncestors_s(shape.wrapped,inverse_shape_LUT[kind],t,shape_map,)exclude=TopTools_MapOfShape()def_siblings(shapes,level):rv=set()forsinshapes:exclude.Add(s.wrapped)forsinshapes:rv.update(Shape.cast(el)forchildins._entities(kind)forelinshape_map.FindFromKey(child)ifnotexclude.Contains(el))returnrviflevel==1else_siblings(rv,level-1)returnCompound.makeCompound(_siblings(self,level)) [docs]defsortWiresByBuildOrder(wireList:List[Wire])->List[List[Wire]]:"""Tries to determine how wires should be combined into faces. Assume: The wires make up one or more faces, which could have 'holes' Outer wires are listed ahead of inner wires there are no wires inside wires inside wires ( IE, islands -- we can deal with that later on ) none of the wires are construction wires Compute: one or more sets of wires, with the outer wire listed first, and inner ones Returns, list of lists. """# check if we have something to sort at alliflen(wireList)<2:return[wireList,]# make a Face, NB: this might return a compound of facesfaces=Face.makeFromWires(wireList[0],wireList[1:])rv=[]forfaceinfaces.Faces():rv.append([face.outerWire(),]+face.innerWires())returnrv [docs]defwiresToFaces(wireList:List[Wire])->List[Face]:""" Convert wires to a list of faces. """returnFace.makeFromWires(wireList[0],wireList[1:]).Faces() [docs]defedgesToWires(edges:Iterable[Edge],tol:float=1e-6)->List[Wire]:""" Convert edges to a list of wires. """edges_in=TopTools_HSequenceOfShape()wires_out=TopTools_HSequenceOfShape()foreinedges:edges_in.Append(e.wrapped)ShapeAnalysis_FreeBounds.ConnectEdgesToWires_s(edges_in,tol,False,wires_out)return[Wire(el)forelinwires_out] #%% utilitiesdef_get(s:Shape,ts:Union[Shapes,Tuple[Shapes,...]])->Iterable[Shape]:""" Get desired shapes or raise an error. """# convert input into tupleifisinstance(ts,tuple):types=tselse:types=(ts,)# validate the underlying shape, compounds are unpackedt=s.ShapeType()iftintypes:yieldselift=="Compound":forelins:ifel.ShapeType()ints:yieldelelse:raiseValueError(f"Required type(s):{types}; encountered{el.ShapeType()}")else:raiseValueError(f"Required type(s):{types}; encountered{t}")def_get_one(s:Shape,ts:Union[Shapes,Tuple[Shapes,...]])->Shape:""" Get one shape or raise an error. """# convert input into tupleifisinstance(ts,tuple):types=tselse:types=(ts,)# validate the underlying shape, compounds are unpackedt=s.ShapeType()iftintypes:rv=selift=="Compound":forelins:ifel.ShapeType()ints:rv=elbreakelse:raiseValueError(f"Required type(s):{types}, encountered{el.ShapeType()}")else:raiseValueError(f"Required type(s):{types}; encountered{t}")returnrvdef_get_one_wire(s:Shape)->Wire:""" Get one wire or edge and convert to wire. """rv=_get_one(s,("Wire","Edge"))ifisinstance(rv,Wire):returnrvelse:returnWire.assembleEdges((rv,))def_get_wires(s:Shape)->Iterable[Shape]:""" Get wires or wires from edges. """t=s.ShapeType()ift=="Wire":yieldselift=="Edge":yieldWire.assembleEdges((tcast(Edge,s),))elift=="Compound":forelins:yield from_get_wires(el)else:raiseValueError(f"Required type(s): Edge, Wire; encountered{t}")def_get_edges(*shapes:Shape)->Iterable[Shape]:""" Get edges or edges from wires. """forsinshapes:t=s.ShapeType()ift=="Edge":yieldselift=="Wire":yield from_get_edges(s.edges())elift=="Compound":forelins:yield from_get_edges(el)else:raiseValueError(f"Required type(s): Edge, Wire; encountered{t}")def_get_wire_lists(s:Sequence[Shape])->List[List[Union[Wire,Vertex]]]:""" Get lists of wires for sweeping or lofting. """wire_lists:List[List[Union[Wire,Vertex]]]=[]ix_last=len(s)-1fori,elinenumerate(s):ifi==0:try:wire_lists=[[w]forwin_get_wires(el)]exceptValueError:# if no wires were detected, try verticeswire_lists=[[v]forvinel.Vertices()]# if not faces and vertices were detected return an empty listifnotwire_lists:breakelifi==ix_last:try:forwire_list,winzip(wire_lists,_get_wires(el)):wire_list.append(w)exceptValueError:forwire_list,vinzip(wire_lists,el.Vertices()):wire_list.append(v)else:forwire_list,winzip(wire_lists,_get_wires(el)):wire_list.append(w)returnwire_listsdef_get_face_lists(s:Sequence[Shape])->List[List[Union[Face,Vertex]]]:""" Get lists of faces for sweeping or lofting. First and last shape can be a vertex. """face_lists:List[List[Union[Face,Vertex]]]=[]ix_last=len(s)-1fori,elinenumerate(s):ifi==0:face_lists=[[f]forfinel.Faces()]# if no faces were detected, try verticesifnotface_listsandnotel.edges():face_lists=[[v]forvinel.Vertices()]# if not faces and vertices were detected return an empty listifnotface_lists:breakelifi==ix_last:# try to add facesfaces=el.Faces()iflen(faces)==len(face_lists):forface_list,finzip(face_lists,faces):face_list.append(f)else:forface_list,vinzip(face_lists,el.Vertices()):face_list.append(v)else:forface_list,finzip(face_lists,el.Faces()):face_list.append(f)# check if the result makes sense - needed in loft to switch to wire modeifany(isinstance(el[0],Vertex)andisinstance(el[1],Vertex)forelinface_lists):return[]returnface_listsdef_normalize(s:Shape)->Shape:""" Apply some normalizations: - Shell with only one Face -> Face. - Compound with only one element -> element. """t=s.ShapeType()rv=sift=="Shell":faces=s.Faces()iflen(faces)==1andnotBRep_Tool.IsClosed_s(s.wrapped):rv=faces[0]elift=="Compound":objs=list(s)iflen(objs)==1:rv=objs[0]returnrvdef_compound_or_shape(s:Union[TopoDS_Shape,List[TopoDS_Shape]])->Shape:""" Convert a list of TopoDS_Shape to a Shape or a Compound. """ifisinstance(s,TopoDS_Shape):rv=_normalize(Shape.cast(s))eliflen(s)==1:rv=_normalize(Shape.cast(s[0]))else:rv=Compound.makeCompound([_normalize(Shape.cast(el))forelins])returnrvdef_pts_to_harray(pts:Sequence[VectorLike])->TColgp_HArray1OfPnt:""" Convert a sequence of Vector to a TColgp harray (OCCT specific). """rv=TColgp_HArray1OfPnt(1,len(pts))fori,pinenumerate(pts):rv.SetValue(i+1,Vector(p).toPnt())returnrvdef_pts_to_harray2D(pts:Sequence[Tuple[Real,Real]])->TColgp_HArray1OfPnt2d:""" Convert a sequence of 2d points to a TColgp harray (OCCT specific). """rv=TColgp_HArray1OfPnt2d(1,len(pts))fori,pinenumerate(pts):rv.SetValue(i+1,gp_Pnt2d(*p))returnrvdef_floats_to_harray(vals:Sequence[float])->TColStd_HArray1OfReal:""" Convert a sequence of floats to a TColstd harray (OCCT specific). """rv=TColStd_HArray1OfReal(1,len(vals))fori,valinenumerate(vals):rv.SetValue(i+1,val)returnrvdef_shapes_to_toptools_list(s:Iterable[Shape])->TopTools_ListOfShape:""" Convert an iterable of Shape to a TopTools list (OCCT specific). """rv=TopTools_ListOfShape()forelins:rv.Append(el.wrapped)returnrvdef_toptools_list_to_shapes(tl:TopTools_ListOfShape)->List[Shape]:""" Convert a TopTools list (OCCT specific) to a compound. """return[_normalize(Shape.cast(el))forelintl]_geomabsshape_dict=dict(C0=GeomAbs_Shape.GeomAbs_C0,C1=GeomAbs_Shape.GeomAbs_C1,C2=GeomAbs_Shape.GeomAbs_C2,C3=GeomAbs_Shape.GeomAbs_C3,CN=GeomAbs_Shape.GeomAbs_CN,G1=GeomAbs_Shape.GeomAbs_G1,G2=GeomAbs_Shape.GeomAbs_G2,)def_to_geomabshape(name:str)->GeomAbs_Shape:""" Convert a literal to GeomAbs_Shape enum (OCCT specific). """return_geomabsshape_dict[name.upper()]_parametrization_dict=dict(uniform=Approx_ParametrizationType.Approx_IsoParametric,chordal=Approx_ParametrizationType.Approx_ChordLength,centripetal=Approx_ParametrizationType.Approx_Centripetal,)def_to_parametrization(name:str)->Approx_ParametrizationType:""" Convert a literal to Approx_ParametrizationType enum (OCCT specific). """return_parametrization_dict[name.lower()]def_adaptor_curve_to_edge(crv:Adaptor3d_Curve,p1:float,p2:float)->TopoDS_Edge:GCT=GeomAbs_CurveTypet=crv.GetType()ift==GCT.GeomAbs_BSplineCurve:bldr=BRepBuilderAPI_MakeEdge(crv.BSpline(),p1,p2)elift==GCT.GeomAbs_BezierCurve:bldr=BRepBuilderAPI_MakeEdge(crv.Bezier(),p1,p2)elift==GCT.GeomAbs_Circle:bldr=BRepBuilderAPI_MakeEdge(crv.Circle(),p1,p2)elift==GCT.GeomAbs_Line:bldr=BRepBuilderAPI_MakeEdge(crv.Line(),p1,p2)elift==GCT.GeomAbs_Ellipse:bldr=BRepBuilderAPI_MakeEdge(crv.Ellipse(),p1,p2)elift==GCT.GeomAbs_Hyperbola:bldr=BRepBuilderAPI_MakeEdge(crv.Hyperbola(),p1,p2)elift==GCT.GeomAbs_Parabola:bldr=BRepBuilderAPI_MakeEdge(crv.Parabola(),p1,p2)elift==GCT.GeomAbs_OffsetCurve:bldr=BRepBuilderAPI_MakeEdge(crv.OffsetCurve(),p1,p2)else:raiseValueError(r"{t} is not a supported curve type")returnbldr.Edge()#%% alternative constructorsShapeHistory=Dict[Union[Shape,str],Shape][docs]@multimethoddefedgeOn(base:Shape,pts:Sequence[Tuple[Real,Real]],periodic:bool=False,tol:float=1e-6,)->Shape:""" Build an edge on a face from points in (u,v) space. """f=_get_one(base,"Face")# interpolate the u,v pointsspline_bldr=Geom2dAPI_Interpolate(_pts_to_harray2D(pts),periodic,tol)spline_bldr.Perform()# build the final edgerv=BRepBuilderAPI_MakeEdge(spline_bldr.Curve(),f._geomAdaptor()).Edge()BRepLib.BuildCurves3d_s(rv)return_compound_or_shape(rv) @edgeOn.registerdef_(fbase:Shape,edg:Shape,*edgs:Shape,tol:float=1e-6,N:int=20,):""" Map one or more edges onto a base face in the u,v space. """f=_get_one(fbase,"Face")rvs:List[TopoDS_Shape]=[]forelin_get_edges(edg,*edgs):# sample the original curvepts3D,params=el.sample(N)# convert to 2D points ignoring the z coordpts=[(el.x,el.y)forelinpts3D]# handle periodicityt0,t1=el._bounds()el_crv=el._geomAdaptor()periodic=False# periodic (and closed)ifel_crv.IsPeriodic()andel_crv.IsClosed():periodic=Trueparams.append(t0+el_crv.Period())# only closedelifel_crv.IsClosed():pts.append(pts[0])params.append(t1)# interpolate the u,v pointsspline_bldr=Geom2dAPI_Interpolate(_pts_to_harray2D(pts),_floats_to_harray(params),periodic,tol)spline_bldr.Perform()# build the final edgerv=BRepBuilderAPI_MakeEdge(spline_bldr.Curve(),f._geomAdaptor()).Edge()BRepLib.BuildCurves3d_s(rv)rvs.append(rv)return_compound_or_shape(rvs)[docs]defwireOn(base:Shape,w:Shape,tol=1e-6,N=20)->Shape:""" Map a wire onto a base face in the u,v space. """rvs=[edgeOn(base,e,tol=tol,N=N)foreinw.Edges()]returnwire(rvs) @multimethoddefwire(*s:Shape)->Shape:""" Build wire from edges. """builder=BRepBuilderAPI_MakeWire()edges=_shapes_to_toptools_list(eforelinsforein_get_edges(el))builder.Add(edges)return_compound_or_shape(builder.Shape())[docs]@wire.registerdefwire(s:Sequence[Shape])->Shape:returnwire(*s) @multimethoddefface(*s:Shape)->Shape:""" Build face from edges or wires. """fromOCP.BOPAlgoimportBOPAlgo_Toolsws=Compound.makeCompound(wforelinsforwin_get_wires(el)).wrappedrv=TopoDS_Compound()status=BOPAlgo_Tools.WiresToFaces_s(ws,rv)ifnotstatus:raiseValueError("Face construction failed")return_get_one(_compound_or_shape(rv),"Face")[docs]@face.registerdefface(s:Sequence[Shape])->Shape:""" Build face from a sequence of edges or wires. """returnface(*s) [docs]deffaceOn(base:Shape,*fcs:Shape,tol=1e-6,N=20)->Shape:""" Build face(s) on base by mapping planar face(s) onto the (u,v) space of base. """rv:Shapervs=[]# get a facefbase=_get_one(base,"Face")# iterate over all facesforelinfcs:forfcinel.Faces():# construct pcurves and trim in one gorvs.append(fbase.trim(wireOn(fbase,fc.outerWire(),tol=tol,N=N),*(wireOn(fbase,w,tol=tol,N=N)forwinfc.innerWires()),))iflen(rvs)==1:rv=rvs[0]else:rv=compound(rvs)returnrv def_process_sewing_history(builder:BRepBuilderAPI_Sewing,faces:List[Face],history:Optional[ShapeHistory],):""" Reusable helper for processing sewing history. """# fill history if providedifhistoryisnotNone:# collect shapes present in the history dictfork,vinhistory.items():ifisinstance(k,str):history[k]=Face(builder.Modified(v.wrapped))# store all top-level shape relationsforfinfaces:history[f]=Face(builder.Modified(f.wrapped))@multimethoddefshell(*s:Shape,tol:float=1e-6,manifold:bool=True,ctx:Optional[Sequence[Shape]|Shape]=None,history:Optional[ShapeHistory]=None,)->Shape:""" Build shell from faces. If ctx is specified, local sewing is performed. """builder=BRepBuilderAPI_Sewing(tol,option4=notmanifold)ifctx:ifisinstance(ctx,Shape):builder.Load(ctx.wrapped)else:builder.Load(compound(ctx).wrapped)faces:list[Face]=[]forelins:forfin_get(el,"Face"):builder.Add(f.wrapped)faces.append(f)builder.Perform()sewed=builder.SewedShape()_process_sewing_history(builder,faces,history)# for one face sewing will not produce a shellifsewed.ShapeType()==TopAbs_ShapeEnum.TopAbs_FACE:rv=TopoDS_Shell()builder=TopoDS_Builder()builder.MakeShell(rv)builder.Add(rv,sewed)else:rv=sewedreturn_compound_or_shape(rv)[docs]@shell.registerdefshell(s:Sequence[Shape],tol:float=1e-6,manifold:bool=True,ctx:Optional[Sequence[Shape]|Shape]=None,history:Optional[ShapeHistory]=None,)->Shape:""" Build shell from a sequence of faces. If ctx is specified, local sewing is performed. """returnshell(*s,tol=tol,manifold=manifold,ctx=ctx,history=history) @multimethoddefsolid(s1:Shape,*sn:Shape,tol:float=1e-6,history:Optional[ShapeHistory]=None,)->Shape:""" Build solid from faces or shells. """builder=ShapeFix_Solid()# get both Shells and Facess=[s1,*sn]shells_faces=[fforelinsforfin_get(el,("Shell","Face"))]# if no shells are present, use faces to construct themshells=[el.wrappedforelinshells_facesifel.ShapeType()=="Shell"]ifnotshells:faces=[elforelinshells_faces]shells=[shell(*faces,tol=tol,history=history).wrapped]rvs=[builder.SolidFromShell(sh)forshinshells]return_compound_or_shape(rvs)[docs]@solid.registerdefsolid(s:Sequence[Shape],inner:Optional[Sequence[Shape]]=None,tol:float=1e-6,history:Optional[ShapeHistory]=None,)->Shape:""" Build solid from a sequence of faces. """builder=BRepBuilderAPI_MakeSolid()builder.Add(shell(*s,tol=tol,history=history).wrapped)ifinner:forshin_get(shell(*inner,tol=tol,history=history),"Shell"):builder.Add(sh.wrapped)# fix orientationssf=ShapeFix_Solid(builder.Solid())sf.Perform()return_compound_or_shape(sf.Solid()) @multimethoddefcompound(*s:Shape)->Shape:""" Build compound from shapes. """rv=TopoDS_Compound()builder=TopoDS_Builder()builder.MakeCompound(rv)forelins:builder.Add(rv,el.wrapped)returnCompound(rv)[docs]@compound.registerdefcompound(s:Sequence[Shape])->Shape:""" Build compound from a sequence of shapes. """returncompound(*s) #%% primitives@multimethoddefvertex(x:Real,y:Real,z:Real)->Shape:""" Construct a vertex from coordinates. """return_compound_or_shape(BRepBuilderAPI_MakeVertex(gp_Pnt(x,y,z)).Vertex())[docs]@vertex.registerdefvertex(p:VectorLike):""" Construct a vertex from VectorLike. """return_compound_or_shape(BRepBuilderAPI_MakeVertex(Vector(p).toPnt()).Vertex()) [docs]defsegment(p1:VectorLike,p2:VectorLike)->Shape:""" Construct a segment from two points. """return_compound_or_shape(BRepBuilderAPI_MakeEdge(Vector(p1).toPnt(),Vector(p2).toPnt()).Edge()) [docs]defpolyline(*pts:VectorLike)->Shape:""" Construct a polyline from points. """builder=BRepBuilderAPI_MakePolygon()forpinpts:builder.Add(Vector(p).toPnt())return_compound_or_shape(builder.Wire()) [docs]defpolygon(*pts:VectorLike)->Shape:""" Construct a polygon (closed polyline) from points. """builder=BRepBuilderAPI_MakePolygon()forpinpts:builder.Add(Vector(p).toPnt())builder.Close()return_compound_or_shape(builder.Wire()) [docs]defrect(w:float,h:float)->Shape:""" Construct a rectangle. """returnpolygon((-w/2,-h/2,0),(w/2,-h/2,0),(w/2,h/2,0),(-w/2,h/2,0)) @multimethoddefspline(*pts:VectorLike,tol:float=1e-6,periodic:bool=False)->Shape:""" Construct a spline from points. """data=_pts_to_harray(pts)builder=GeomAPI_Interpolate(data,periodic,tol)builder.Perform()return_compound_or_shape(BRepBuilderAPI_MakeEdge(builder.Curve()).Edge())[docs]@spline.registerdefspline(pts:Sequence[VectorLike],tgts:Optional[Sequence[VectorLike]]=None,params:Optional[Sequence[float]]=None,tol:float=1e-6,periodic:bool=False,scale:bool=True,)->Shape:""" Construct a spline from a sequence points. """data=_pts_to_harray(pts)ifparamsisnotNone:args=(data,_floats_to_harray(params),periodic,tol)else:args=(data,periodic,tol)builder=GeomAPI_Interpolate(*args)iftgtsisnotNone:builder.Load(Vector(tgts[0]).wrapped,Vector(tgts[1]).wrapped,scale)builder.Perform()return_compound_or_shape(BRepBuilderAPI_MakeEdge(builder.Curve()).Edge()) [docs]defcircle(r:float)->Shape:""" Construct a circle. """return_compound_or_shape(BRepBuilderAPI_MakeEdge(gp_Circ(gp_Ax2(Vector().toPnt(),Vector(0,0,1).toDir()),r)).Edge()) [docs]defellipse(r1:float,r2:float)->Shape:""" Construct an ellipse. """return_compound_or_shape(BRepBuilderAPI_MakeEdge(gp_Elips(gp_Ax2(Vector().toPnt(),Vector(0,0,1).toDir()),r1,r2)).Edge()) @multimethoddefplane(w:Real,l:Real)->Shape:""" Construct a finite planar face. """pln_geom=gp_Pln(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir())return_compound_or_shape(BRepBuilderAPI_MakeFace(pln_geom,-w/2,w/2,-l/2,l/2).Face())[docs]@plane.registerdefplane()->Shape:""" Construct an infinite planar face. This is a crude approximation. Truly infinite faces in OCCT do not work as expected in all contexts. """INF=1e60pln_geom=gp_Pln(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir())return_compound_or_shape(BRepBuilderAPI_MakeFace(pln_geom,-INF,INF,-INF,INF).Face()) [docs]defbox(w:float,l:float,h:float)->Shape:""" Construct a solid box. """return_compound_or_shape(BRepPrimAPI_MakeBox(gp_Ax2(Vector(-w/2,-l/2,0).toPnt(),Vector(0,0,1).toDir()),w,l,h).Shape()) [docs]defcylinder(d:float,h:float)->Shape:""" Construct a solid cylinder. """return_compound_or_shape(BRepPrimAPI_MakeCylinder(gp_Ax2(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir()),d/2,h,2*pi).Shape()) [docs]defsphere(d:float)->Shape:""" Construct a solid sphere. """return_compound_or_shape(BRepPrimAPI_MakeSphere(gp_Ax2(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir()),d/2,).Shape()) [docs]deftorus(d1:float,d2:float)->Shape:""" Construct a solid torus. """return_compound_or_shape(BRepPrimAPI_MakeTorus(gp_Ax2(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir()),d1/2,d2/2,0,2*pi,).Shape()) @multimethoddefcone(d1:Real,d2:Real,h:Real)->Shape:""" Construct a partial solid cone. """return_compound_or_shape(BRepPrimAPI_MakeCone(gp_Ax2(Vector(0,0,0).toPnt(),Vector(0,0,1).toDir()),d1/2,d2/2,h,2*pi,).Shape())[docs]@cone.registerdefcone(d:Real,h:Real)->Shape:""" Construct a full solid cone. """returncone(d,0,h) @multimethoddeftext(txt:str,size:Real,font:str="Arial",path:Optional[str]=None,kind:Literal["regular","bold","italic"]="regular",halign:Literal["center","left","right"]="center",valign:Literal["center","top","bottom"]="center",)->Shape:""" Create a flat text. """builder=Font_BRepTextBuilder()font_kind={"regular":Font_FA_Regular,"bold":Font_FA_Bold,"italic":Font_FA_Italic,}[kind]mgr=Font_FontMgr.GetInstance_s()ifpathandmgr.CheckFont(TCollection_AsciiString(path).ToCString()):font_t=Font_SystemFont(TCollection_AsciiString(path))font_t.SetFontPath(font_kind,TCollection_AsciiString(path))mgr.RegisterFont(font_t,True)else:font_t=mgr.FindFont(TCollection_AsciiString(font),font_kind)font_i=StdPrs_BRepFont(NCollection_Utf8String(font_t.FontName().ToCString()),font_kind,float(size),)ifhalign=="left":theHAlign=Graphic3d_HTA_LEFTelifhalign=="center":theHAlign=Graphic3d_HTA_CENTERelse:theHAlign=Graphic3d_HTA_RIGHTifvalign=="bottom":theVAlign=Graphic3d_VTA_BOTTOMelifvalign=="center":theVAlign=Graphic3d_VTA_CENTERelse:theVAlign=Graphic3d_VTA_TOPrv=builder.Perform(font_i,NCollection_Utf8String(txt),theHAlign=theHAlign,theVAlign=theVAlign)returnclean(compound(_compound_or_shape(rv).Faces()).fuse())@text.registerdeftext(txt:str,size:Real,spine:Shape,planar:bool=False,font:str="Arial",path:Optional[str]=None,kind:Literal["regular","bold","italic"]="regular",halign:Literal["center","left","right"]="center",valign:Literal["center","top","bottom"]="center",)->Shape:""" Create a text on a spine. """spine=_get_one_wire(spine)L=spine.Length()rv=[]forelintext(txt,size,font,path,kind,halign,valign).Faces():pos=el.BoundingBox().center.x# positionrv.append(el.moved(-pos).moved(rx=-90ifplanarelse0,ry=-90).moved(spine.locationAt(pos/L)))return_normalize(compound(rv))[docs]@text.registerdeftext(txt:str,size:Real,spine:Shape,base:Shape,font:str="Arial",path:Optional[str]=None,kind:Literal["regular","bold","italic"]="regular",halign:Literal["center","left","right"]="center",valign:Literal["center","top","bottom"]="center",)->Shape:""" Create a text on a spine and a base surface. """base=_get_one(base,"Face")tmp=text(txt,size,spine,False,font,path,kind,halign,valign)rv=[]forfintmp.Faces():rv.append(f.project(base,f.normalAt()))return_normalize(compound(rv)) #%% opsdef_bool_op(s1:Shape,s2:Shape,builder:Union[BRepAlgoAPI_BooleanOperation,BRepAlgoAPI_Splitter],tol:float=0.0,parallel:bool=True,):arg=TopTools_ListOfShape()arg.Append(s1.wrapped)tool=TopTools_ListOfShape()tool.Append(s2.wrapped)builder.SetArguments(arg)builder.SetTools(tool)builder.SetRunParallel(parallel)builder.SetUseOBB(True)iftol:builder.SetFuzzyValue(tol)builder.Build()def_set_glue(builder:BOPAlgo_Builder,glue:GlueLiteral):ifglue:builder.SetGlue(BOPAlgo_GlueEnum.BOPAlgo_GlueFullifglue=="full"elseBOPAlgo_GlueEnum.BOPAlgo_GlueShift)def_set_builder_options(builder:BOPAlgo_Builder,tol:float):builder.SetRunParallel(True)builder.SetUseOBB(True)builder.SetNonDestructive(True)iftol:builder.SetFuzzyValue(tol)[docs]defsetThreads(n:int):""" Set number of threads to be used by boolean operations. """pool=OSD_ThreadPool.DefaultPool_s()pool.Init(n) [docs]deffuse(s1:Shape,s2:Shape,*shapes:Shape,tol:float=0.0,glue:GlueLiteral=None,)->Shape:""" Fuse at least two shapes. """builder=BOPAlgo_BOP()builder.SetOperation(BOPAlgo_FUSE)_set_glue(builder,glue)_set_builder_options(builder,tol)builder.AddArgument(s1.wrapped)builder.AddTool(s2.wrapped)forsinshapes:builder.AddTool(s.wrapped)builder.Perform()return_compound_or_shape(builder.Shape()) [docs]defcut(s1:Shape,s2:Shape,tol:float=0.0,glue:GlueLiteral=None)->Shape:""" Subtract two shapes. """builder=BOPAlgo_BOP()builder.SetOperation(BOPAlgo_CUT)_set_glue(builder,glue)_set_builder_options(builder,tol)builder.AddArgument(s1.wrapped)builder.AddTool(s2.wrapped)builder.Perform()return_compound_or_shape(builder.Shape()) [docs]defintersect(s1:Shape,s2:Shape,tol:float=0.0,glue:GlueLiteral=None)->Shape:""" Intersect two shapes. """builder=BOPAlgo_BOP()builder.SetOperation(BOPAlgo_COMMON)_set_glue(builder,glue)_set_builder_options(builder,tol)builder.AddArgument(s1.wrapped)builder.AddTool(s2.wrapped)builder.Perform()return_compound_or_shape(builder.Shape()) [docs]defsplit(s1:Shape,s2:Shape,tol:float=0.0)->Shape:""" Split one shape with another. """builder=BRepAlgoAPI_Splitter()_bool_op(s1,s2,builder,tol)return_compound_or_shape(builder.Shape()) [docs]defimprint(*shapes:Shape,tol:float=0.0,glue:GlueLiteral="full",history:Optional[ShapeHistory]=None,)->Shape:""" Imprint arbitrary number of shapes. """builder=BOPAlgo_Builder()_set_glue(builder,glue)_set_builder_options(builder,tol)forsinshapes:builder.AddArgument(s.wrapped)builder.Perform()# fill history if providedifhistoryisnotNone:images=builder.Images()# collect shapes present in the history dictfork,vinhistory.items():ifisinstance(k,str):try:history[k]=_compound_or_shape(list(images.Find(v.wrapped)))exceptStandard_NoSuchObject:pass# store all top-level shape relationsforsinshapes:try:history[s]=_compound_or_shape(list(images.Find(s.wrapped)))exceptStandard_NoSuchObject:passreturn_compound_or_shape(builder.Shape()) [docs]defclean(s:Shape)->Shape:""" Clean superfluous edges and faces. """builder=ShapeUpgrade_UnifySameDomain(s.wrapped,True,True,True)builder.AllowInternalEdges(False)builder.Build()return_compound_or_shape(builder.Shape()) [docs]deffill(s:Shape,constraints:Sequence[Union[Shape,VectorLike]]=())->Shape:""" Fill edges/wire possibly obeying constraints. """builder=BRepOffsetAPI_MakeFilling()forein_get_edges(s):builder.Add(e.wrapped,GeomAbs_C0)forcinconstraints:ifisinstance(c,Shape):forein_get_edges(c):builder.Add(e.wrapped,GeomAbs_C0,False)else:builder.Add(Vector(c).toPnt())builder.Build()return_compound_or_shape(builder.Shape()) [docs]defcap(s:Shape,ctx:Shape,constraints:Sequence[Union[Shape,VectorLike]]=())->Shape:""" Fill edges/wire possibly obeying constraints and try to connect smoothly to the context shape. """builder=BRepOffsetAPI_MakeFilling()builder.SetResolParam(2,15,5)forein_get_edges(s):f=_get_one(e.ancestors(ctx,"Face"),"Face")builder.Add(e.wrapped,f.wrapped,GeomAbs_G2,True)forcinconstraints:ifisinstance(c,Shape):forein_get_edges(c):builder.Add(e.wrapped,GeomAbs_C0,False)else:builder.Add(Vector(c).toPnt())builder.Build()return_compound_or_shape(builder.Shape()) [docs]deffillet(s:Shape,e:Shape,r:float)->Shape:""" Fillet selected edges in a given shell or solid. """builder=BRepFilletAPI_MakeFillet(_get_one(s,("Shell","Solid")).wrapped,)forelin_get_edges(e.edges()):builder.Add(r,el.wrapped)builder.Build()return_compound_or_shape(builder.Shape()) [docs]defchamfer(s:Shape,e:Shape,d:float)->Shape:""" Chamfer selected edges in a given shell or solid. """builder=BRepFilletAPI_MakeChamfer(_get_one(s,("Shell","Solid")).wrapped,)forelin_get_edges(e.edges()):builder.Add(d,el.wrapped)builder.Build()return_compound_or_shape(builder.Shape()) [docs]defextrude(s:Shape,d:VectorLike)->Shape:""" Extrude a shape. """results=[]forelin_get(s,("Vertex","Edge","Wire","Face")):builder=BRepPrimAPI_MakePrism(el.wrapped,Vector(d).wrapped)builder.Build()results.append(builder.Shape())return_compound_or_shape(results) [docs]defrevolve(s:Shape,p:VectorLike,d:VectorLike,a:float=360):""" Revolve a shape. """results=[]ax=gp_Ax1(Vector(p).toPnt(),Vector(d).toDir())forelin_get(s,("Vertex","Edge","Wire","Face")):builder=BRepPrimAPI_MakeRevol(el.wrapped,ax,radians(a))builder.Build()results.append(builder.Shape())return_compound_or_shape(results) [docs]defoffset(s:Shape,t:float,cap=True,both:bool=False,tol:float=1e-6)->Shape:""" Offset or thicken faces or shells. """def_offset(t):results=[]forelin_get(s,("Face","Shell")):builder=BRepOffset_MakeOffset()builder.Initialize(el.wrapped,t,tol,BRepOffset_Mode.BRepOffset_Skin,False,False,GeomAbs_Intersection,cap,)builder.MakeOffsetShape()results.append(builder.Shape())returnresultsifboth:results_pos=_offset(t)results_neg=_offset(-t)results_both=[Shape(el1)+Shape(el2)forel1,el2inzip(results_pos,results_neg)]iflen(results_both)==1:rv=results_both[0]else:rv=Compound.makeCompound(results_both)else:results=_offset(t)rv=_compound_or_shape(results)returnrv @multimethoddefsweep(s:Shape,path:Shape,aux:Optional[Shape]=None,cap:bool=False)->Shape:""" Sweep edge, wire or face along a path. For faces cap has no effect. Do not mix faces with other types. """spine=_get_one_wire(path)results=[]def_make_builder():rv=BRepOffsetAPI_MakePipeShell(spine.wrapped)ifaux:rv.SetMode(_get_one_wire(aux).wrapped,True)else:rv.SetMode(False)returnrv# try to get facesfaces=s.Faces()# if faces were suppliediffaces:forfinfaces:tmp=sweep(f.outerWire(),path,aux,True)# if needed subtract two sweepsinner_wires=f.innerWires()ifinner_wires:tmp-=sweep(compound(inner_wires),path,aux,True)results.append(tmp.wrapped)# otherwise sweep wireselse:forwin_get_wires(s):builder=_make_builder()builder.Add(w.wrapped,False,False)builder.Build()ifcap:builder.MakeSolid()results.append(builder.Shape())return_compound_or_shape(results)[docs]@sweep.registerdefsweep(s:Sequence[Shape],path:Shape,aux:Optional[Shape]=None,cap:bool=False)->Shape:""" Sweep edges, wires or faces along a path, multiple sections are supported. For faces cap has no effect. Do not mix faces with other types. """spine=_get_one_wire(path)results=[]def_make_builder():rv=BRepOffsetAPI_MakePipeShell(spine.wrapped)ifaux:rv.SetMode(_get_one_wire(aux).wrapped,True)else:rv.SetMode(False)returnrv# try to construct sweeps using facesforelin_get_face_lists(s):# build outer partbuilder=_make_builder()forfinel:builder.Add(f.outerWire().wrapped,False,False)builder.Build()builder.MakeSolid()# build inner partsbuilders_inner=[]# initialize buildersforwinel[0].innerWires():builder_inner=_make_builder()builder_inner.Add(w.wrapped,False,False)builders_inner.append(builder_inner)# add remaining sectionsforfinel[1:]:forbuilder_inner,winzip(builders_inner,f.innerWires()):builder_inner.Add(w.wrapped,False,False)# actually buildinner_parts=[]forbuilder_innerinbuilders_inner:builder_inner.Build()builder_inner.MakeSolid()inner_parts.append(Shape(builder_inner.Shape()))results.append((Shape(builder.Shape())-compound(inner_parts)).wrapped)# if no faces were provided try with wiresifnotresults:# construct sweepsforelin_get_wire_lists(s):builder=_make_builder()forwinel:builder.Add(w.wrapped,False,False)builder.Build()ifcap:builder.MakeSolid()results.append(builder.Shape())return_compound_or_shape(results) @multimethoddefloft(s:Sequence[Shape],cap:bool=False,ruled:bool=False,continuity:Literal["C1","C2","C3"]="C2",parametrization:Literal["uniform","chordal","centripetal"]="uniform",degree:int=3,compat:bool=True,smoothing:bool=False,weights:Tuple[float,float,float]=(1,1,1),)->Shape:""" Loft edges, wires or faces. For faces cap has no effect. Do not mix faces with other types. """results=[]def_make_builder(cap):rv=BRepOffsetAPI_ThruSections(cap,ruled)rv.SetMaxDegree(degree)rv.CheckCompatibility(compat)rv.SetContinuity(_to_geomabshape(continuity))rv.SetParType(_to_parametrization(parametrization))rv.SetSmoothing(smoothing)rv.SetCriteriumWeight(*weights)returnrv# try to construct lofts using facesforelin_get_face_lists(s):# build outer partbuilder=_make_builder(True)# used to check if building inner parts makes sensehas_vertex=Falseforfinel:ifisinstance(f,Face):builder.AddWire(f.outerWire().wrapped)else:builder.AddVertex(f.wrapped)has_vertex=Truebuilder.Build()builder.Check()builders_inner=[]# only initialize inner builders if no vertex was encounteredifnothas_vertex:# initialize buildersforwinel[0].innerWires():builder_inner=_make_builder(True)builder_inner.AddWire(w.wrapped)builders_inner.append(builder_inner)# add remaining sectionsforfinel[1:]:forbuilder_inner,winzip(builders_inner,f.innerWires()):builder_inner.AddWire(w.wrapped)# actually buildinner_parts=[]forbuilder_innerinbuilders_inner:builder_inner.Build()builder_inner.Check()inner_parts.append(Shape(builder_inner.Shape()))results.append((Shape(builder.Shape())-compound(inner_parts)).wrapped)# otherwise construct using wiresifnotresults:forelin_get_wire_lists(s):builder=_make_builder(cap)forwinel:ifisinstance(w,Wire):builder.AddWire(w.wrapped)else:builder.AddVertex(w.wrapped)builder.Build()builder.Check()results.append(builder.Shape())return_compound_or_shape(results)[docs]@loft.registerdefloft(*s:Shape,cap:bool=False,ruled:bool=False,continuity:Literal["C1","C2","C3"]="C2",parametrization:Literal["uniform","chordal","centripetal"]="uniform",degree:int=3,compat:bool=True,smoothing:bool=False,weights:Tuple[float,float,float]=(1,1,1),)->Shape:""" Variadic loft overload. """returnloft(s,cap,ruled,continuity,parametrization,degree,compat) [docs]defproject(s:Shape,base:Shape,continuity:Literal["C1","C2","C3"]="C2",degree:int=3,maxseg:int=30,tol:float=1e-4,):""" Project s onto base using normal projection. """bldr=BRepAlgo_NormalProjection(base.wrapped)bldr.SetParams(tol,tol**(2/3),_to_geomabshape(continuity),degree,maxseg)forelin_get_edges(s):bldr.Add(s.wrapped)bldr.Build()return_compound_or_shape(bldr.Projection()) #%% diagnotics[docs]defcheck(s:Shape,results:Optional[List[Tuple[List[Shape],Any]]]=None,tol:Optional[float]=None,)->bool:""" Check if a shape is valid. """analyzer=BRepAlgoAPI_Check(s.wrapped)analyzer.SetRunParallel(True)analyzer.SetUseOBB(True)analyzer.Perform()rv=analyzer.IsValid()iftol:analyzer.SetFuzzyValue(tol)# output detailed results if requestedifresultsisnotNone:results.clear()forrinanalyzer.Result():results.append((_toptools_list_to_shapes(r.GetFaultyShapes1()),r.GetCheckStatus()))returnrv [docs]defisSubshape(s1:Shape,s2:Shape)->bool:""" Check if s1 is a subshape of s2. """shape_map=TopTools_IndexedDataMapOfShapeListOfShape()TopExp.MapShapesAndAncestors_s(s2.wrapped,shapetype(s1.wrapped),inverse_shape_LUT[s2.ShapeType()],shape_map)returnshape_map.Contains(s1.wrapped) #%% properties[docs]defclosest(s1:Shape,s2:Shape)->Tuple[Vector,Vector]:""" Closest points between two shapes. """# configureext=BRepExtrema_DistShapeShape()ext.SetMultiThread(True)# load shapesext.LoadS1(s1.wrapped)ext.LoadS2(s2.wrapped)# performassertext.Perform()returnVector(ext.PointOnShape1(1)),Vector(ext.PointOnShape2(1))