Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
c4d6fb7
Added code for simplified and fused assembly export to STEP
jmwright Feb 3, 2023
1dc0ff6
Changed toFused to use OCCT history API instead of the cut method
jmwright Feb 6, 2023
84b789f
Black fix
jmwright Feb 7, 2023
5894dc7
Handle a null label due to a deleted face from the fuse
jmwright Feb 7, 2023
d635b7d
Handle generated faces
jmwright Feb 8, 2023
7107aed
Allow a face to be null because of something other than IsDeleted
jmwright Feb 8, 2023
247ff59
Added assembly STEP export doucmentation
jmwright Feb 8, 2023
6b88570
Handle assemblies that have only one shape in them
jmwright Feb 9, 2023
33453a3
Better check before getting a null label and commented out generated …
jmwright Feb 11, 2023
a450414
Appended CAF to new toSimplified and toFused method names for export
jmwright Feb 11, 2023
3e79caa
Removed unneeded __iter__ call
jmwright Feb 17, 2023
1708dd9
Trying to figure out mypy Literal for STEP export mode
jmwright Feb 17, 2023
bd2d539
Switched to using literals for the export mode
jmwright Feb 21, 2023
c9bb231
Switching to built-in CQ method for creating a compound
jmwright Feb 21, 2023
db4f667
Added glue and fuzzy tolerance settings for fused assembly
jmwright Feb 24, 2023
5d64464
Use the built-in assembly name, otherwise a UUID
jmwright Feb 28, 2023
4c97cbf
Merge branch 'master' of github.com:CadQuery/cadquery into step
jmwright Mar 10, 2023
cca863b
Pulled latest master and fixed black errors
jmwright Mar 10, 2023
65dd174
Started using location of assembly parent part object for compound
jmwright Mar 10, 2023
0d2e4b2
Handle nested assemblies
jmwright Mar 10, 2023
bd5469f
Added nested assembly export tests
jmwright Mar 10, 2023
da19c9a
mypy apparently could not handle the one-liner inside toShapeList
jmwright Mar 13, 2023
8e4a67f
Attempting to fix remaining mypy errors
jmwright Mar 13, 2023
e1eb403
Added single part fused export, fuzzy_tol and glue setting tests
jmwright Mar 14, 2023
7b571fe
Removed simplified STEP export method and addressed some comments on …
jmwright Mar 14, 2023
dd783c5
Fixed export mode literal typing omission
jmwright Mar 14, 2023
ce166a6
Fix black error
jmwright Mar 14, 2023
70275cb
Fixing mypy error
jmwright Mar 14, 2023
4f5c4f2
Resolving merge conflicts
jmwright Mar 17, 2023
3b3aebe
Added ability to handle a top level shape passed during Assembly init…
jmwright Mar 20, 2023
0412bf3
Simplified assembly handling while still keeping top level assembly o…
jmwright Mar 20, 2023
18d9364
Attempting to round out tests
jmwright Mar 20, 2023
50ac03e
Update cadquery/assembly.py
jmwright Mar 21, 2023
d888941
Update cadquery/occ_impl/assembly.py
jmwright Mar 21, 2023
7937f4d
Simplification for latest OCP
jmwright Mar 21, 2023
f5d8daf
Fixed tests for typing change
jmwright Mar 21, 2023
b152b95
Use f string in exception
jmwright Mar 21, 2023
6d46bf8
Capitalization and f string suggestions
jmwright Mar 21, 2023
e26b570
Resolved merge conflict
jmwright Mar 21, 2023
a899334
Fix black check
jmwright Mar 21, 2023
9f672a3
Simplified code by using traverse to walk the assembly structure
jmwright Mar 21, 2023
50396a6
Removed toShapeList
jmwright Mar 21, 2023
8063dfa
Trying to avoid an error that only seems to be happening in CI
jmwright Mar 21, 2023
1ef5208
Needed to put the check if a face was deleted on the fuse op
jmwright Mar 21, 2023
d329428
Investigate the failures on appveyor
adam-urbanczyk Mar 23, 2023
96b2ecf
Reworked and simplified adding of modified and generated faces
jmwright Mar 23, 2023
b98b2bc
Updating appveyor
jmwright Mar 23, 2023
106819d
Added null checks for labels after trying to add a subshape
jmwright Mar 23, 2023
a24006f
Fixed color handling for some faces during fuse
jmwright Mar 23, 2023
47225b5
Comment out on_finish
adam-urbanczyk Mar 23, 2023
bce2b78
Apply locations to shapes
adam-urbanczyk Mar 25, 2023
d92c658
Fix failing tests
adam-urbanczyk Mar 26, 2023
a41af3e
Better testcase
adam-urbanczyk Mar 26, 2023
70ef18e
Copy explicitly
adam-urbanczyk Mar 28, 2023
734a9ee
Fix single shape export
adam-urbanczyk Apr 1, 2023
b9693c0
Mypy fix
adam-urbanczyk Apr 1, 2023
52689ca
Add (initial) test of fused STEP export mode colors
lorenzncode Apr 2, 2023
65c5431
Merge branch 'master' into step
lorenzncode Apr 9, 2023
86c9c66
toFusedCAF - handle single compound special case, use existing assemb…
lorenzncode Apr 10, 2023
0285250
Removed performance statement in assembly exporter docstring
jmwright Apr 19, 2023
e6b1499
Updated docstring for glue
adam-urbanczyk Apr 20, 2023
cc1ed92
Doc tweaks
adam-urbanczyk Apr 23, 2023
071fd65
More assembly export mode doc tweaks
lorenzncode Apr 23, 2023
1620705
Changed the name exportMode to mode
jmwright Apr 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ nested.stl
out1.3mf
out2.3mf
out3.3mf
orig.dxf
5 changes: 5 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ test_script:

on_success:
- codecov

#on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# - sh: export APPVEYOR_SSH_BLOCK=true
# - sh: curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
22 changes: 20 additions & 2 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from functools import reduce
from typing import Union, Optional, List, Dict, Any, overload, Tuple, Iterator, cast
from typing import (
Union,
Optional,
List,
Dict,
Any,
overload,
Tuple,
Iterator,
cast,
get_args,
)
from typing_extensions import Literal
from typish import instance_of
from uuid import uuid1 as uuid
Expand All @@ -21,6 +32,8 @@
exportVTKJS,
exportVRML,
exportGLTF,
STEPExportModeLiterals,
ExportModes,
)

from .selectors import _expression_grammar as _selector_grammar
Expand Down Expand Up @@ -436,6 +449,7 @@ def save(
self,
path: str,
exportType: Optional[ExportLiterals] = None,
mode: STEPExportModeLiterals = "default",
tolerance: float = 0.1,
angularTolerance: float = 0.1,
**kwargs,
Expand All @@ -451,6 +465,10 @@ def save(
See :meth:`~cadquery.occ_impl.exporters.assembly.exportAssembly`.
"""

# Make sure the export mode setting is correct
if mode not in get_args(STEPExportModeLiterals):
raise ValueError(f"Unknown assembly export mode {mode} for STEP")

if exportType is None:
t = path.split(".")[-1].upper()
if t in ("STEP", "XML", "VRML", "VTKJS", "GLTF", "STL"):
Expand All @@ -459,7 +477,7 @@ def save(
raise ValueError("Unknown extension, specify export type explicitly")

if exportType == "STEP":
exportAssembly(self, path, **kwargs)
exportAssembly(self, path, mode, **kwargs)
elif exportType == "XML":
exportCAF(self, path)
elif exportType == "VRML":
Expand Down
126 changes: 124 additions & 2 deletions cadquery/occ_impl/assembly.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from typing import Union, Iterable, Tuple, Dict, overload, Optional, Any, List
from typing import Union, Iterable, Tuple, Dict, overload, Optional, Any, List, cast
from typing_extensions import Protocol
from math import degrees

from OCP.TDocStd import TDocStd_Document
from OCP.TCollection import TCollection_ExtendedString
from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType
from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType, XCAFDoc_ColorGen
from OCP.XCAFApp import XCAFApp_Application
from OCP.TDataStd import TDataStd_Name
from OCP.TDF import TDF_Label
from OCP.TopLoc import TopLoc_Location
from OCP.Quantity import Quantity_ColorRGBA
from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse
from OCP.TopTools import TopTools_ListOfShape
from OCP.BOPAlgo import BOPAlgo_GlueEnum
from OCP.TopoDS import TopoDS_Shape

from vtkmodules.vtkRenderingCore import (
vtkActor,
Expand Down Expand Up @@ -112,6 +116,10 @@ def parent(self) -> Optional["AssemblyProtocol"]:
def color(self) -> Optional[Color]:
...

@property
def obj(self) -> AssemblyObjects:
...

@property
def shapes(self) -> Iterable[Shape]:
...
Expand Down Expand Up @@ -289,3 +297,117 @@ def toJSON(
rv.extend(toJSON(child, loc, color, tolerance))

return rv


def toFusedCAF(
assy: AssemblyProtocol, glue: bool = False, tol: Optional[float] = None,
) -> Tuple[TDF_Label, TDocStd_Document]:
"""
Converts the assembly to a fused compound and saves that within the document
to be exported in a way that preserves the face colors. Because of the use of
boolean operations in this method, performance may be slow in some cases.

:param assy: Assembly that is being converted to a fused compound for the document.
"""

# Prepare the document
app = XCAFApp_Application.GetApplication_s()
doc = TDocStd_Document(TCollection_ExtendedString("XmlOcaf"))
app.InitDocument(doc)

# Shape and color tools
shape_tool = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main())
color_tool = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())

# To fuse the parts of the assembly together
fuse_op = BRepAlgoAPI_Fuse()
args = TopTools_ListOfShape()
tools = TopTools_ListOfShape()

# If there is only one solid, there is no reason to fuse, and it will likely cause problems anyway
top_level_shape = None

# Walk the entire assembly, collecting the located shapes and colors
shapes: List[Shape] = []
colors = []

def extract_shapes(assy, parent_loc=None, parent_color=None):

loc = parent_loc * assy.loc if parent_loc else assy.loc
color = assy.color if assy.color else parent_color

for shape in assy.shapes:
shapes.append(shape.moved(loc).copy())
colors.append(color)

for ch in assy.children:
extract_shapes(ch, loc, color)

extract_shapes(assy)

# Initialize with a dummy value for mypy
top_level_shape = cast(TopoDS_Shape, None)

# If the tools are empty, it means we only had a single shape and do not need to fuse
if not shapes:
raise Exception(f"Error: Assembly {assy.name} has no shapes.")
elif len(shapes) == 1:
# There is only one shape and we only need to make sure it is a Compound
# This seems to be needed to be able to add subshapes (i.e. faces) correctly
sh = shapes[0]
if sh.ShapeType() != "Compound":
top_level_shape = Compound.makeCompound((sh,)).wrapped
elif sh.ShapeType() == "Compound":
sh = sh.fuse(glue=glue, tol=tol)
top_level_shape = Compound.makeCompound((sh,)).wrapped
shapes = [sh]
else:
# Set the shape lists up so that the fuse operation can be performed
args.Append(shapes[0].wrapped)

for shape in shapes[1:]:
tools.Append(shape.wrapped)

# Allow the caller to configure the fuzzy and glue settings
if tol:
fuse_op.SetFuzzyValue(tol)
if glue:
fuse_op.SetGlue(BOPAlgo_GlueEnum.BOPAlgo_GlueShift)

fuse_op.SetArguments(args)
fuse_op.SetTools(tools)
fuse_op.Build()

top_level_shape = fuse_op.Shape()

# Add the fused shape as the top level object in the document
top_level_lbl = shape_tool.AddShape(top_level_shape, False)
TDataStd_Name.Set_s(top_level_lbl, TCollection_ExtendedString(assy.name))

# Walk the assembly->part->shape->face hierarchy and add subshapes for all the faces
for color, shape in zip(colors, shapes):
for face in shape.Faces():
# See if the face can be treated as-is
cur_lbl = shape_tool.AddSubShape(top_level_lbl, face.wrapped)
if color and not cur_lbl.IsNull() and not fuse_op.IsDeleted(face.wrapped):
color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen)

# Handle any modified faces
modded_list = fuse_op.Modified(face.wrapped)

for mod in modded_list:
# Add the face as a subshape and set its color to match the parent assembly component
cur_lbl = shape_tool.AddSubShape(top_level_lbl, mod)
if color and not cur_lbl.IsNull() and not fuse_op.IsDeleted(mod):
color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen)

# Handle any generated faces
gen_list = fuse_op.Generated(face.wrapped)

for gen in gen_list:
# Add the face as a subshape and set its color to match the parent assembly component
cur_lbl = shape_tool.AddSubShape(top_level_lbl, gen)
if color and not cur_lbl.IsNull():
color_tool.SetColor(cur_lbl, color.wrapped, XCAFDoc_ColorGen)

return top_level_lbl, doc
40 changes: 36 additions & 4 deletions cadquery/occ_impl/exporters/assembly.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import os.path
import uuid

from tempfile import TemporaryDirectory
from shutil import make_archive
from itertools import chain
from typing_extensions import Literal

from vtkmodules.vtkIOExport import vtkJSONSceneExporter, vtkVRMLExporter
from vtkmodules.vtkRenderingCore import vtkRenderer, vtkRenderWindow
Expand All @@ -23,20 +25,41 @@
from OCP.Message import Message_ProgressRange
from OCP.Interface import Interface_Static

from ..assembly import AssemblyProtocol, toCAF, toVTK
from ..assembly import AssemblyProtocol, toCAF, toVTK, toFusedCAF
from ..geom import Location


def exportAssembly(assy: AssemblyProtocol, path: str, **kwargs) -> bool:
class ExportModes:
DEFAULT = "default"
FUSED = "fused"


STEPExportModeLiterals = Literal["default", "fused"]


def exportAssembly(
assy: AssemblyProtocol,
path: str,
mode: STEPExportModeLiterals = "default",
**kwargs
) -> bool:
"""
Export an assembly to a STEP file.

kwargs is used to provide optional keyword arguments to configure the exporter.

:param assy: assembly
:param path: Path and filename for writing
:param mode: STEP export mode. The options are "default", and "fused" (a single fused compound).
It is possible that fused mode may exhibit low performance.
:param fuzzy_tol: OCCT fuse operation tolerance setting used only for fused assembly export.
:type fuzzy_tol: float
:param glue: Enable gluing mode for improved performance during fused assembly export.
This option should only be used for non-intersecting shapes or those that are only touching or partially overlapping.
Note that when glue is enabled, the resulting fused shape may be invalid if shapes are intersecting in an incompatible way.
Defaults to False.
:type glue: bool
: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.
Expand All @@ -49,8 +72,17 @@ def exportAssembly(assy: AssemblyProtocol, path: str, **kwargs) -> bool:
if "write_pcurves" in kwargs and not kwargs["write_pcurves"]:
pcurves = 0
precision_mode = kwargs["precision_mode"] if "precision_mode" in kwargs else 0
fuzzy_tol = kwargs["fuzzy_tol"] if "fuzzy_tol" in kwargs else None
glue = kwargs["glue"] if "glue" in kwargs else False

# Use the assembly name if the user set it
assembly_name = assy.name if assy.name else str(uuid.uuid1())

_, doc = toCAF(assy, True)
# Handle the doc differently based on which mode we are using
if mode == "fused":
_, doc = toFusedCAF(assy, glue, fuzzy_tol)
else: # Includes "default"
_, doc = toCAF(assy, True)

session = XSControl_WorkSession()
writer = STEPCAFControl_Writer(session, False)
Expand Down
Loading