Skip to content

Commit

Permalink
Incomplete experimental glTF exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Mar 7, 2024
1 parent 5419b20 commit fde0ef7
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 26 deletions.
4 changes: 4 additions & 0 deletions src/build123d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from build123d.topology import *
from build123d.drafting import *
from build123d.persistence import modify_copyreg
from build123d.exporters3d import *

from .version import version as __version__

Expand Down Expand Up @@ -207,4 +208,7 @@
# Topology Exploration
"topo_explore_connected_edges",
"topo_explore_common_vertex",
# 3D Exporters
"export_step_alt",
"export_gltf",
]
173 changes: 147 additions & 26 deletions src/build123d/exporters3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,28 @@

import OCP.TopAbs as ta
from anytree import PreOrderIter
from OCP.BRepMesh import BRepMesh_IncrementalMesh
from OCP.BRepTools import BRepTools
from OCP.IFSelect import IFSelect_ReturnStatus
from OCP.IGESControl import IGESControl_Controller
from OCP.Interface import Interface_Static
from OCP.Message import Message_ProgressRange
from OCP.RWGltf import RWGltf_CafWriter
from OCP.STEPCAFControl import STEPCAFControl_Controller, STEPCAFControl_Writer
from OCP.STEPControl import STEPControl_Controller, STEPControl_StepModelType
from OCP.TCollection import TCollection_ExtendedString
from OCP.TCollection import TCollection_ExtendedString, TCollection_AsciiString
from OCP.TColStd import TColStd_IndexedDataMapOfStringString
from OCP.TDataStd import TDataStd_Name
from OCP.TDF import TDF_Label
from OCP.TDocStd import TDocStd_Document
from OCP.TopExp import TopExp_Explorer
from OCP.XCAFApp import XCAFApp_Application
from OCP.XCAFDoc import XCAFDoc_ColorType, XCAFDoc_DocumentTool
from OCP.XSControl import XSControl_WorkSession

from build123d import Compound, Curve, Part, Shape, Sketch, Unit
from build123d.build_enums import Unit
from build123d.geometry import Location
from build123d.topology import Compound, Curve, Part, Shape, Sketch


class PrecisionMode(Enum):
Expand All @@ -65,33 +73,27 @@ def __repr__(self):
return f"<{self.__class__.__name__}.{self.name}>"


def export_step_with_color_label(
to_export: Shape,
path: str,
unit: Unit = Unit.MM,
write_pcurves: bool = True,
precision_mode: PrecisionMode = PrecisionMode.AVERAGE,
) -> bool:
"""export_step
def _create_xde(to_export: Shape, unit: Unit = Unit.MM) -> TDocStd_Document:
"""create_xde
Export a build123d Shape or assembly with color and label attributes.
Note that if the color of a node in an assembly isn't set, it will be
assigned the color of its nearest ancestor.
An OpenCASCADE Technology (OCCT) XDE (eXtended Data Exchange) document is a
sophisticated framework designed for comprehensive CAD data management,
facilitating not just the geometric modeling of shapes but also the handling
of associated metadata, such as material properties, color, layering, and
annotations. This integrated environment supports the organization and
manipulation of complex assemblies and parts, enabling users to navigate,
edit, and query both the structure and attributes of CAD models. XDE's
architecture is particularly beneficial for applications requiring detailed
interoperability between different CAD systems, offering robust tools for
data exchange, visualization, and modification while preserving the integrity
and attributes of the CAD data across various stages of product development.
Args:
to_export (Shape): object or assembly
path (str): step file path
unit (Unit, optional): shape units. Defaults to Unit.MM.
write_pcurves (bool, optional): write parametric curves to the STEP file.
Defaults to True.
precision_mode (bool, optional): geometric data precision.
Defaults to PrecisionMode.AVERAGE.
Raises:
RuntimeError: Unknown Compound type
Returns:
bool: success
TDocStd_Document: XDE document
"""
# Create the XCAF document
doc = TDocStd_Document(TCollection_ExtendedString("XmlOcaf"))
Expand All @@ -117,6 +119,7 @@ def export_step_with_color_label(
# Get the tools for handling shapes & colors section of the XCAF document.
shape_tool = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main())
color_tool = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())
# shape_tool.SetAutoNaming_s(True)

# Add all the shapes in the b3d object either as a single object or assembly
is_assembly = isinstance(to_export, Compound) and len(to_export.children) > 0
Expand Down Expand Up @@ -145,8 +148,7 @@ def export_step_with_color_label(
raise RuntimeError("Unknown Compound type")

while explorer.More():
current_shape = explorer.Current()
sub_nodes.append(current_shape)
sub_nodes.append(explorer.Current())
explorer.Next()

sub_node_labels = [
Expand Down Expand Up @@ -176,8 +178,44 @@ def export_step_with_color_label(
)

shape_tool.UpdateAssemblies()

# print(f"Is document valid: {XCAFDoc_DocumentTool.IsXCAFDocument_s(doc)}")

return doc


def export_step_alt(
to_export: Shape,
path: str,
unit: Unit = Unit.MM,
write_pcurves: bool = True,
precision_mode: PrecisionMode = PrecisionMode.AVERAGE,
) -> bool:
"""export_step
Export a build123d Shape or assembly with color and label attributes.
Note that if the color of a node in an assembly isn't set, it will be
assigned the color of its nearest ancestor.
Args:
to_export (Shape): object or assembly
path (str): step file path
unit (Unit, optional): shape units. Defaults to Unit.MM.
write_pcurves (bool, optional): write parametric curves to the STEP file.
Defaults to True.
precision_mode (bool, optional): geometric data precision.
Defaults to PrecisionMode.AVERAGE.
Raises:
RuntimeError: Unknown Compound type
Returns:
bool: success
"""

# Create the XCAF document
doc = _create_xde(to_export, unit)

session = XSControl_WorkSession()
writer = STEPCAFControl_Writer(session, False)
writer.SetColorMode(True)
Expand Down Expand Up @@ -206,6 +244,89 @@ def export_step_with_color_label(
Interface_Static.SetIVal_s("write.precision.mode", precision_mode.value)
writer.Transfer(doc, STEPControl_StepModelType.STEPControl_AsIs)

status = writer.Write(path)
status = writer.Write(path) == IFSelect_ReturnStatus.IFSelect_RetDone

if not status:
raise RuntimeError("Failed to write glTF file")

return status


def export_gltf(
to_export: Shape,
path: str,
unit: Unit = Unit.MM,
binary: bool = False,
linear_deflection: float = 0.001,
angular_deflection: float = 0.1,
) -> bool:
"""export_gltf
The glTF (GL Transmission Format) specification primarily focuses on the efficient
transmission and loading of 3D models as a compact, binary format that is directly
renderable by graphics APIs like WebGL, OpenGL, and Vulkan. It's designed to store
detailed 3D model data, including meshes (vertices, normals, textures, etc.),
animations, materials, and scene hierarchy, among other aspects.
Args:
to_export (Shape): object or assembly
path (str): glTF file path
unit (Unit, optional): shape units. Defaults to Unit.MM.
binary (bool, optional): output format. Defaults to False.
linear_deflection (float, optional): 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. The default is a good starting point for a range of cases.
Defaults to 1e-3.
angular_deflection (float, optional): Angular deflection setting which limits
the angle between subsequent segments in a polyline. Defaults to 0.1.
Raises:
RuntimeError: Tessellation failed
RuntimeError: Failed to write glTF file
Returns:
bool: write status
"""

# map from OCCT's right-handed +Z up coordinate system to glTF's right-handed +Y up coordinate system
# https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units
original_location = to_export.location
to_export.location *= Location((0, 0, 0), (1, 0, 0), -90)

# Tessellate the object(s)
mesh_tool = BRepMesh_IncrementalMesh(
to_export.wrapped, linear_deflection, True, angular_deflection, True
)
mesh_tool.Perform()

# Check Tessellation
node: Shape
for node in PreOrderIter(to_export):
explorer = TopExp_Explorer(node.wrapped, ta.TopAbs_FACE)
while explorer.More():
face = explorer.Current()
if not BRepTools.Triangulation_s(face, linear_deflection):
raise RuntimeError("Tessellation failed")
explorer.Next()

# Create the XCAF document
doc: TDocStd_Document = _create_xde(to_export, unit)

# Write the glTF file
writer = RWGltf_CafWriter(theFile=TCollection_AsciiString(path), theIsBinary=binary)
index_map = TColStd_IndexedDataMapOfStringString()
progress = Message_ProgressRange()
status = writer.Perform(doc, index_map, progress)

# Reset tessellation
BRepTools.Clean_s(to_export.wrapped)

# Reset original orientation
to_export.location = original_location

if not status:
raise RuntimeError("Failed to write glTF file")

return status == IFSelect_ReturnStatus.IFSelect_RetDone
return status

0 comments on commit fde0ef7

Please sign in to comment.