1414from OCP .STEPCAFControl import STEPCAFControl_Writer
1515from OCP .STEPControl import STEPControl_StepModelType
1616from OCP .IFSelect import IFSelect_ReturnStatus
17+ from OCP .TDF import TDF_Label
18+ from OCP .TDataStd import TDataStd_Name
19+ from OCP .TDocStd import TDocStd_Document
1720from OCP .XCAFApp import XCAFApp_Application
21+ from OCP .XCAFDoc import XCAFDoc_DocumentTool , XCAFDoc_ColorGen
1822from OCP .XmlDrivers import (
1923 XmlDrivers_DocumentStorageDriver ,
2024 XmlDrivers_DocumentRetrievalDriver ,
2832
2933from ..assembly import AssemblyProtocol , toCAF , toVTK , toFusedCAF
3034from ..geom import Location
35+ from ..shapes import Shape , Compound
36+ from ..assembly import Color
3137
3238
3339class ExportModes :
@@ -42,7 +48,7 @@ def exportAssembly(
4248 assy : AssemblyProtocol ,
4349 path : str ,
4450 mode : STEPExportModeLiterals = "default" ,
45- ** kwargs
51+ ** kwargs ,
4652) -> bool :
4753 """
4854 Export an assembly to a STEP file.
@@ -99,6 +105,168 @@ def exportAssembly(
99105 return status == IFSelect_ReturnStatus .IFSelect_RetDone
100106
101107
108+ def exportStepMeta (
109+ assy : AssemblyProtocol ,
110+ path : str ,
111+ write_pcurves : bool = True ,
112+ precision_mode : int = 0 ,
113+ ) -> bool :
114+ """
115+ Export an assembly to a STEP file with faces tagged with names and colors. This is done as a
116+ separate method from the main STEP export because this is not compatible with the fused mode
117+ and also flattens the hierarchy of the STEP.
118+
119+ Layers are used because some software does not understand the ADVANCED_FACE entity and needs
120+ names attached to layers instead.
121+
122+ :param assy: assembly
123+ :param path: Path and filename for writing
124+ :param write_pcurves: Enable or disable writing parametric curves to the STEP file. Default True.
125+ If False, writes STEP file without pcurves. This decreases the size of the resulting STEP file.
126+ :param precision_mode: Controls the uncertainty value for STEP entities. Specify -1, 0, or 1. Default 0.
127+ See OCCT documentation.
128+ """
129+
130+ pcurves = 1
131+ if not write_pcurves :
132+ pcurves = 0
133+
134+ # Initialize the XCAF document that will allow the STEP export
135+ app = XCAFApp_Application .GetApplication_s ()
136+ doc = TDocStd_Document (TCollection_ExtendedString ("XmlOcaf" ))
137+ app .InitDocument (doc )
138+
139+ # Shape and color tools
140+ shape_tool = XCAFDoc_DocumentTool .ShapeTool_s (doc .Main ())
141+ color_tool = XCAFDoc_DocumentTool .ColorTool_s (doc .Main ())
142+ layer_tool = XCAFDoc_DocumentTool .LayerTool_s (doc .Main ())
143+
144+ def _process_child (child : AssemblyProtocol , assy_label : TDF_Label ):
145+ """
146+ Process a child part which is not a subassembly.
147+ :param child: Child part to process (we should already have filtered out subassemblies)
148+ :param assy_label: The label for the assembly to add this part to
149+ :return: None
150+ """
151+
152+ child_items = None
153+
154+ # We combine these because the metadata could be stored at the parent or child level
155+ combined_names = {** assy ._subshape_names , ** child ._subshape_names }
156+ combined_colors = {** assy ._subshape_colors , ** child ._subshape_colors }
157+ combined_layers = {** assy ._subshape_layers , ** child ._subshape_layers }
158+
159+ # Collect all of the shapes in the child object
160+ if child .obj :
161+ child_items = (
162+ child .obj
163+ if isinstance (child .obj , Shape )
164+ else Compound .makeCompound (
165+ s for s in child .obj .vals () if isinstance (s , Shape )
166+ ),
167+ child .name ,
168+ child .loc ,
169+ child .color ,
170+ )
171+
172+ if child_items :
173+ shape , name , loc , color = child_items
174+
175+ # Handle shape name, color and location
176+ part_label = shape_tool .AddShape (shape .wrapped , False )
177+ TDataStd_Name .Set_s (part_label , TCollection_ExtendedString (name ))
178+ if color :
179+ color_tool .SetColor (part_label , color .wrapped , XCAFDoc_ColorGen )
180+ shape_tool .AddComponent (assy_label , part_label , loc .wrapped )
181+
182+ # If this assembly has shape metadata, add it to the shape
183+ if (
184+ len (combined_names ) > 0
185+ or len (combined_colors ) > 0
186+ or len (combined_layers ) > 0
187+ ):
188+ names = combined_names
189+ colors = combined_colors
190+ layers = combined_layers
191+
192+ # Step through every face in the shape, and see if any metadata needs to be attached to it
193+ for face in shape .Faces ():
194+ if face in names or face in shape in colors or face in layers :
195+ # Add the face as a subshape
196+ face_label = shape_tool .AddSubShape (part_label , face .wrapped )
197+
198+ # In some cases the face may not be considered part of the shape, so protect
199+ # against that
200+ if not face_label .IsNull ():
201+ # Set the ADVANCED_FACE label, even though the layer holds the same data
202+ if face in names :
203+ TDataStd_Name .Set_s (
204+ face_label , TCollection_ExtendedString (names [face ])
205+ )
206+
207+ # Set the individual face color
208+ if face in colors :
209+ color_tool .SetColor (
210+ face_label , colors [face ].wrapped , XCAFDoc_ColorGen ,
211+ )
212+
213+ # Also add a layer to hold the face label data
214+ if face in layers :
215+ layer_label = layer_tool .AddLayer (
216+ TCollection_ExtendedString (layers [face ])
217+ )
218+ layer_tool .SetLayer (face_label , layer_label )
219+
220+ def _process_assembly (
221+ assy : AssemblyProtocol , parent_label : Optional [TDF_Label ] = None
222+ ):
223+ """
224+ Recursively process the assembly and its children.
225+ :param assy: Assembly to process
226+ :param parent_label: The parent label for the assembly
227+ :return: None
228+ """
229+ # Use the assembly name if the user set it
230+ assembly_name = assy .name if assy .name else str (uuid .uuid1 ())
231+
232+ # Create the top level object that will hold all the subassemblies and parts
233+ assy_label = shape_tool .NewShape ()
234+ TDataStd_Name .Set_s (assy_label , TCollection_ExtendedString (assembly_name ))
235+
236+ # Handle subassemblies
237+ if parent_label :
238+ shape_tool .AddComponent (parent_label , assy_label , assy .loc .wrapped )
239+
240+ # The children may be parts or assemblies
241+ for child in assy .children :
242+ # Child is a part
243+ if len (list (child .children )) == 0 :
244+ _process_child (child , assy_label )
245+ # Child is a subassembly
246+ else :
247+ _process_assembly (child , assy_label )
248+
249+ _process_assembly (assy )
250+
251+ # Update the assemblies
252+ shape_tool .UpdateAssemblies ()
253+
254+ # Set up the writer and write the STEP file
255+ session = XSControl_WorkSession ()
256+ writer = STEPCAFControl_Writer (session , False )
257+ Interface_Static .SetIVal_s ("write.stepcaf.subshapes.name" , 1 )
258+ writer .SetColorMode (True )
259+ writer .SetLayerMode (True )
260+ writer .SetNameMode (True )
261+ Interface_Static .SetIVal_s ("write.surfacecurve.mode" , pcurves )
262+ Interface_Static .SetIVal_s ("write.precision.mode" , precision_mode )
263+ writer .Transfer (doc , STEPControl_StepModelType .STEPControl_AsIs )
264+
265+ status = writer .Write (path )
266+
267+ return status == IFSelect_ReturnStatus .IFSelect_RetDone
268+
269+
102270def exportCAF (assy : AssemblyProtocol , path : str ) -> bool :
103271 """
104272 Export an assembly to a OCAF xml file (internal OCCT format).
0 commit comments