|
1 |
| -from typing import Union, Iterable, Tuple, Dict, overload, Optional, Any, List |
| 1 | +from typing import Union, Iterable, Tuple, Dict, overload, Optional, Any, List, cast |
2 | 2 | from typing_extensions import Protocol
|
3 | 3 | from math import degrees
|
4 | 4 |
|
5 | 5 | from OCP.TDocStd import TDocStd_Document
|
6 | 6 | from OCP.TCollection import TCollection_ExtendedString
|
7 |
| -from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType |
| 7 | +from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType, XCAFDoc_ColorGen |
8 | 8 | from OCP.XCAFApp import XCAFApp_Application
|
9 | 9 | from OCP.TDataStd import TDataStd_Name
|
10 | 10 | from OCP.TDF import TDF_Label
|
11 | 11 | from OCP.TopLoc import TopLoc_Location
|
12 | 12 | from OCP.Quantity import Quantity_ColorRGBA
|
| 13 | +from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse |
| 14 | +from OCP.TopTools import TopTools_ListOfShape |
| 15 | +from OCP.BOPAlgo import BOPAlgo_GlueEnum |
| 16 | +from OCP.TopoDS import TopoDS_Shape |
13 | 17 |
|
14 | 18 | from vtkmodules.vtkRenderingCore import (
|
15 | 19 | vtkActor,
|
@@ -112,6 +116,10 @@ def parent(self) -> Optional["AssemblyProtocol"]:
|
112 | 116 | def color(self) -> Optional[Color]:
|
113 | 117 | ...
|
114 | 118 |
|
| 119 | + @property |
| 120 | + def obj(self) -> AssemblyObjects: |
| 121 | + ... |
| 122 | + |
115 | 123 | @property
|
116 | 124 | def shapes(self) -> Iterable[Shape]:
|
117 | 125 | ...
|
@@ -289,3 +297,117 @@ def toJSON(
|
289 | 297 | rv.extend(toJSON(child, loc, color, tolerance))
|
290 | 298 |
|
291 | 299 | return rv
|
| 300 | + |
| 301 | + |
| 302 | +def toFusedCAF( |
| 303 | + assy: AssemblyProtocol, glue: bool = False, tol: Optional[float] = None, |
| 304 | +) -> Tuple[TDF_Label, TDocStd_Document]: |
| 305 | + """ |
| 306 | + Converts the assembly to a fused compound and saves that within the document |
| 307 | + to be exported in a way that preserves the face colors. Because of the use of |
| 308 | + boolean operations in this method, performance may be slow in some cases. |
| 309 | +
|
| 310 | + :param assy: Assembly that is being converted to a fused compound for the document. |
| 311 | + """ |
| 312 | + |
| 313 | + # Prepare the document |
| 314 | + app = XCAFApp_Application.GetApplication_s() |
| 315 | + doc = TDocStd_Document(TCollection_ExtendedString("XmlOcaf")) |
| 316 | + app.InitDocument(doc) |
| 317 | + |
| 318 | + # Shape and color tools |
| 319 | + shape_tool = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main()) |
| 320 | + color_tool = XCAFDoc_DocumentTool.ColorTool_s(doc.Main()) |
| 321 | + |
| 322 | + # To fuse the parts of the assembly together |
| 323 | + fuse_op = BRepAlgoAPI_Fuse() |
| 324 | + args = TopTools_ListOfShape() |
| 325 | + tools = TopTools_ListOfShape() |
| 326 | + |
| 327 | + # If there is only one solid, there is no reason to fuse, and it will likely cause problems anyway |
| 328 | + top_level_shape = None |
| 329 | + |
| 330 | + # Walk the entire assembly, collecting the located shapes and colors |
| 331 | + shapes: List[Shape] = [] |
| 332 | + colors = [] |
| 333 | + |
| 334 | + def extract_shapes(assy, parent_loc=None, parent_color=None): |
| 335 | + |
| 336 | + loc = parent_loc * assy.loc if parent_loc else assy.loc |
| 337 | + color = assy.color if assy.color else parent_color |
| 338 | + |
| 339 | + for shape in assy.shapes: |
| 340 | + shapes.append(shape.moved(loc).copy()) |
| 341 | + colors.append(color) |
| 342 | + |
| 343 | + for ch in assy.children: |
| 344 | + extract_shapes(ch, loc, color) |
| 345 | + |
| 346 | + extract_shapes(assy) |
| 347 | + |
| 348 | + # Initialize with a dummy value for mypy |
| 349 | + top_level_shape = cast(TopoDS_Shape, None) |
| 350 | + |
| 351 | + # If the tools are empty, it means we only had a single shape and do not need to fuse |
| 352 | + if not shapes: |
| 353 | + raise Exception(f"Error: Assembly {assy.name} has no shapes.") |
| 354 | + elif len(shapes) == 1: |
| 355 | + # There is only one shape and we only need to make sure it is a Compound |
| 356 | + # This seems to be needed to be able to add subshapes (i.e. faces) correctly |
| 357 | + sh = shapes[0] |
| 358 | + if sh.ShapeType() != "Compound": |
| 359 | + top_level_shape = Compound.makeCompound((sh,)).wrapped |
| 360 | + elif sh.ShapeType() == "Compound": |
| 361 | + sh = sh.fuse(glue=glue, tol=tol) |
| 362 | + top_level_shape = Compound.makeCompound((sh,)).wrapped |
| 363 | + shapes = [sh] |
| 364 | + else: |
| 365 | + # Set the shape lists up so that the fuse operation can be performed |
| 366 | + args.Append(shapes[0].wrapped) |
| 367 | + |
| 368 | + for shape in shapes[1:]: |
| 369 | + tools.Append(shape.wrapped) |
| 370 | + |
| 371 | + # Allow the caller to configure the fuzzy and glue settings |
| 372 | + if tol: |
| 373 | + fuse_op.SetFuzzyValue(tol) |
| 374 | + if glue: |
| 375 | + fuse_op.SetGlue(BOPAlgo_GlueEnum.BOPAlgo_GlueShift) |
| 376 | + |
| 377 | + fuse_op.SetArguments(args) |
| 378 | + fuse_op.SetTools(tools) |
| 379 | + fuse_op.Build() |
| 380 | + |
| 381 | + top_level_shape = fuse_op.Shape() |
| 382 | + |
| 383 | + # Add the fused shape as the top level object in the document |
| 384 | + top_level_lbl = shape_tool.AddShape(top_level_shape, False) |
| 385 | + TDataStd_Name.Set_s(top_level_lbl, TCollection_ExtendedString(assy.name)) |
| 386 | + |
| 387 | + # Walk the assembly->part->shape->face hierarchy and add subshapes for all the faces |
| 388 | + for color, shape in zip(colors, shapes): |
| 389 | + for face in shape.Faces(): |
| 390 | + # See if the face can be treated as-is |
| 391 | + cur_lbl = shape_tool.AddSubShape(top_level_lbl, face.wrapped) |
| 392 | + if color and not cur_lbl.IsNull() and not fuse_op.IsDeleted(face.wrapped): |
| 393 | + color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen) |
| 394 | + |
| 395 | + # Handle any modified faces |
| 396 | + modded_list = fuse_op.Modified(face.wrapped) |
| 397 | + |
| 398 | + for mod in modded_list: |
| 399 | + # Add the face as a subshape and set its color to match the parent assembly component |
| 400 | + cur_lbl = shape_tool.AddSubShape(top_level_lbl, mod) |
| 401 | + if color and not cur_lbl.IsNull() and not fuse_op.IsDeleted(mod): |
| 402 | + color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen) |
| 403 | + |
| 404 | + # Handle any generated faces |
| 405 | + gen_list = fuse_op.Generated(face.wrapped) |
| 406 | + |
| 407 | + for gen in gen_list: |
| 408 | + # Add the face as a subshape and set its color to match the parent assembly component |
| 409 | + cur_lbl = shape_tool.AddSubShape(top_level_lbl, gen) |
| 410 | + if color and not cur_lbl.IsNull(): |
| 411 | + color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen) |
| 412 | + |
| 413 | + return top_level_lbl, doc |
0 commit comments