-
Notifications
You must be signed in to change notification settings - Fork 358
Add STEP Import for Assemblies #1779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 36 commits
821aa1e
c3d5573
da9844a
7b674c8
288c0a0
4045c58
d708acd
aa09d31
f88ad35
6493135
e51a7cb
36d7035
f29056e
14e002b
687bc2b
6b360c2
73f007e
5e19332
5f4c4c2
e5bccb2
d5bc317
9b48507
65f38c8
54e23ee
2810bc2
3438df7
086fa8a
6365512
578fd60
c90e476
31557e2
83a433f
cafad47
43e2e00
26a64f3
c85ec9c
46c84c6
c75d0c8
d0714fd
d69856a
c8e0c08
7639fdf
eedc16f
ee2b834
2b8a8eb
c7821ee
f88aa9a
71e309a
7da1eab
8251d92
1543908
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
from OCP.TCollection import TCollection_ExtendedString | ||
from OCP.Quantity import Quantity_Color, Quantity_ColorRGBA | ||
from OCP.TDocStd import TDocStd_Document | ||
from OCP.IFSelect import IFSelect_RetDone | ||
from OCP.STEPCAFControl import STEPCAFControl_Reader | ||
from OCP.XCAFDoc import ( | ||
XCAFDoc_DocumentTool, | ||
XCAFDoc_ColorGen, | ||
XCAFDoc_ColorSurf, | ||
XCAFDoc_GraphNode, | ||
) | ||
from OCP.TDF import TDF_Label, TDF_LabelSequence, TDF_AttributeIterator, TDF_DataSet | ||
from OCP.TDataStd import TDataStd_Name | ||
|
||
import cadquery as cq | ||
from ..assembly import AssemblyProtocol | ||
|
||
|
||
def importStep(assy: AssemblyProtocol, path: str): | ||
""" | ||
Import a step file into an assembly. | ||
|
||
:param assy: An Assembly object that will be packed with the contents of the STEP file. | ||
:param path: Path and filename to the STEP file to read. | ||
|
||
:return: None | ||
""" | ||
|
||
# Document that the step file will be read into | ||
doc = TDocStd_Document(TCollection_ExtendedString("XmlOcaf")) | ||
|
||
# Create and configure a STEP reader | ||
step_reader = STEPCAFControl_Reader() | ||
step_reader.SetColorMode(True) | ||
step_reader.SetNameMode(True) | ||
step_reader.SetLayerMode(True) | ||
step_reader.SetSHUOMode(True) | ||
|
||
# Read the STEP file | ||
status = step_reader.ReadFile(path) | ||
if status != IFSelect_RetDone: | ||
raise ValueError(f"Error reading STEP file: {path}") | ||
|
||
# Transfer the contents of the STEP file to the document | ||
step_reader.Transfer(doc) | ||
|
||
# Shape and color tools for extracting XCAF data | ||
shape_tool = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main()) | ||
color_tool = XCAFDoc_DocumentTool.ColorTool_s(doc.Main()) | ||
layer_tool = XCAFDoc_DocumentTool.LayerTool_s(doc.Main()) | ||
|
||
def _process_simple_shape(label, parent_location=None, parent_name=None): | ||
shape = shape_tool.GetShape_s(label) | ||
|
||
# Tracks the RGB color value and whether or not it was found | ||
cq_color = None | ||
jmwright marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Load the name of the part in the assembly, if it is present | ||
name = None | ||
jmwright marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if parent_name is not None and parent_name != assy.name: | ||
name = parent_name | ||
else: | ||
name_attr = TDataStd_Name() | ||
label.FindAttribute(TDataStd_Name.GetID_s(), name_attr) | ||
name = str(name_attr.Get().ToExtString()) | ||
|
||
# Process the color for the shape, which could be of different types | ||
color = Quantity_Color() | ||
cq_color = cq.Color(0.50, 0.50, 0.50) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the magic parameters? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are the RGB values that most of the CQ visualization methods seem to be using as a default with assemblies (gray/silver). The values are not standardized as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, default color should be class Assembly(object):
"""Nested assembly of Workplane and Shape objects defining their relative positions."""
loc: Location
name: str
color: Optional[Color]
metadata: Dict[str, Any]
obj: AssemblyObjects
parent: Optional["Assembly"]
children: List["Assembly"]
objects: Dict[str, "Assembly"]
constraints: List[Constraint] |
||
if color_tool.GetColor_s(label, XCAFDoc_ColorSurf, color): | ||
r = color.Red() | ||
g = color.Green() | ||
b = color.Blue() | ||
cq_color = cq.Color(r, g, b) | ||
|
||
# Handle the location if it was passed down form a parent component | ||
if parent_location is not None: | ||
assy.add( | ||
cq.Shape.cast(shape), | ||
name=name, | ||
color=cq_color, | ||
loc=cq.Location(parent_location), | ||
) | ||
else: | ||
assy.add(cq.Shape.cast(shape), name=name, color=cq_color) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why isn't this covered. Is parent location not optional? |
||
|
||
# Check all the attributes of all the children to find the subshapes and any names | ||
for j in range(label.NbChildren()): | ||
child_label = label.FindChild(j + 1) | ||
attr_iterator = TDF_AttributeIterator(child_label) | ||
while attr_iterator.More(): | ||
current_attr = attr_iterator.Value() | ||
|
||
# Get the type name of the attribute so that we can decide how to handle it | ||
if current_attr.DynamicType().Name() == "TNaming_NamedShape": | ||
jmwright marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Save the shape so that we can add it to the subshape data | ||
cur_shape = current_attr.Get() | ||
|
||
# Find the layer name, if there is one set for this shape | ||
layers = TDF_LabelSequence() | ||
layer_tool.GetLayers(child_label, layers) | ||
for i in range(1, layers.Length() + 1): | ||
lbl = layers.Value(i) | ||
name_attr = TDataStd_Name() | ||
lbl.FindAttribute(TDataStd_Name.GetID_s(), name_attr) | ||
|
||
# Extract the layer name for the shape here | ||
layer_name = name_attr.Get().ToExtString() | ||
|
||
# Add the layer as a subshape entry on the assembly | ||
assy.addSubshape(cur_shape, layer=layer_name) | ||
|
||
# Find the subshape color, if there is one set for this shape | ||
color = Quantity_ColorRGBA() | ||
# Extract the color, if present on the shape | ||
if color_tool.GetColor(cur_shape, XCAFDoc_ColorSurf, color): | ||
rgb = color.GetRGB() | ||
cq_color = cq.Color( | ||
rgb.Red(), rgb.Green(), rgb.Blue(), color.Alpha() | ||
) | ||
|
||
# Save the color info via the assembly subshape mechanism | ||
assy.addSubshape(cur_shape, color=cq_color) | ||
elif current_attr.DynamicType().Name() == "XCAFDoc_GraphNode": | ||
# Step up one level to try to get the name from the parent | ||
lbl = current_attr.GetFather(1).Label() | ||
|
||
# Step through and search for the name attribute | ||
it = TDF_AttributeIterator(lbl) | ||
while it.More(): | ||
new_attr = it.Value() | ||
if new_attr.DynamicType().Name() == "TDataStd_Name": | ||
# Save this as the name of the subshape | ||
assy.addSubshape( | ||
cur_shape, name=new_attr.Get().ToExtString(), | ||
) | ||
it.Next() | ||
|
||
attr_iterator.Next() | ||
|
||
def process_label(label, parent_location=None, parent_name=None): | ||
""" | ||
Recursive function that allows us to process the hierarchy of the assembly as represented | ||
in the step file. | ||
""" | ||
|
||
# Handle reference labels | ||
if shape_tool.IsReference_s(label): | ||
ref_label = TDF_Label() | ||
shape_tool.GetReferredShape_s(label, ref_label) | ||
process_label(ref_label, parent_location, parent_name) | ||
|
||
return | ||
|
||
# See if this is an assembly (or sub-assembly) | ||
if shape_tool.IsAssembly_s(label): | ||
name_attr = TDataStd_Name() | ||
label.FindAttribute(TDataStd_Name.GetID_s(), name_attr) | ||
name = name_attr.Get().ToExtString() | ||
|
||
# Recursively process its components (children) | ||
comp_labels = TDF_LabelSequence() | ||
shape_tool.GetComponents_s(label, comp_labels) | ||
|
||
for i in range(comp_labels.Length()): | ||
sub_label = comp_labels.Value(i + 1) | ||
# Get the location of the sub-label, if it exists | ||
loc = shape_tool.GetLocation_s(sub_label) | ||
|
||
# Pass down the location or other context as needed | ||
# Add the parent location if it exists | ||
if parent_location is not None: | ||
loc = parent_location * loc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this suspicious. Assy applies locations in a relative sense. It shouldn't be needed to construct absolute locations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll have to check, but I think I added this because of nested sub-assemblies There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed. If the parent location is not passed through the call stack, nested subassemblies will break. I added the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, but that is actually suspicious. When you construct an assy you do not need combine (i.e. multiply) the locations explicitly. |
||
|
||
process_label(sub_label, loc, name) | ||
|
||
return | ||
|
||
# Check to see if we have an endpoint shape and process it | ||
if shape_tool.IsSimpleShape_s(label): | ||
_process_simple_shape(label, parent_location, parent_name) | ||
|
||
# Get the shapes in the assembly | ||
labels = TDF_LabelSequence() | ||
shape_tool.GetFreeShapes(labels) | ||
|
||
# Use the first label to pull the top-level assembly information | ||
name_attr = TDataStd_Name() | ||
labels.Value(1).FindAttribute(TDataStd_Name.GetID_s(), name_attr) | ||
assy.name = str(name_attr.Get().ToExtString()) | ||
|
||
# Make sure there is a top-level assembly | ||
if shape_tool.IsTopLevel(labels.Value(1)) and shape_tool.IsAssembly_s( | ||
labels.Value(1) | ||
): | ||
process_label(labels.Value(1)) | ||
else: | ||
raise ValueError("Step file does not contain an assembly") |
Uh oh!
There was an error while loading. Please reload this page.