Skip to content

Better imprint for assy - #2069

Merged
jmwright merged 8 commits into
masterfrom
adam-imprint-fix
Jul 26, 2026
Merged

Better imprint for assy#2069
jmwright merged 8 commits into
masterfrom
adam-imprint-fix

Conversation

@adam-urbanczyk

@adam-urbanczyk adam-urbanczyk commented Jul 16, 2026

Copy link
Copy Markdown
Member

Low level fix enabling faster imprints for well behaved models.

NB: this PR also contains a typing fix related to Numba version update.


Tangentially related to this PR (I added explicit test for this use case) - it is possible to imprint subassemblies separately and on per subassy basis use different imprint glue settings in order to optimize performance:

res1, origins1 = cq.occ_impl.assembly.imprint(complex_assy.sub1, glue='partial')
res2, origins2 = cq.occ_impl.assembly.imprint(complex_assy.sub2, glue='full')

# combine the results
res: Compound = res1 | res2
origins: dict[Shape, Shape] = origins1 | origins2

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.70%. Comparing base (b47f7b7) to head (2da3b69).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2069   +/-   ##
=======================================
  Coverage   95.70%   95.70%           
=======================================
  Files          30       30           
  Lines        9479     9479           
  Branches     1410     1410           
=======================================
  Hits         9072     9072           
  Misses        253      253           
  Partials      154      154           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lorenzncode lorenzncode left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @adam-urbanczyk !

Comment thread cadquery/occ_impl/assembly.py Outdated
Comment thread cadquery/occ_impl/assembly.py Outdated
@adam-urbanczyk

Copy link
Copy Markdown
Member Author

@jmwright would you be able to take a look?

@jmwright jmwright left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MRE:

import cadquery as cq
from cadquery.occ_impl.assembly import imprint
imprint(cq.Assembly().add(cq.Workplane().box(1, 1, 1)))

gives this error:

Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    imprint(cq.Assembly().add(cq.Workplane().box(1, 1, 1)))
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/assembly.py", line 885, in imprint
    res = Shape(builder.Shape())
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 445, in __init__
    self.wrapped = downcast(obj)
                   ~~~~~~~~^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 419, in downcast
    f_downcast: Any = downcast_LUT[shapetype(obj)]
                                   ~~~~~~~~~^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 409, in shapetype
    raise ValueError("Null TopoDS_Shape object")

Comment thread cadquery/occ_impl/assembly.py Outdated
Comment thread cadquery/occ_impl/shapes.py Outdated
Co-authored-by: Jeremy Wright <wrightjmf@gmail.com>
@adam-urbanczyk

Copy link
Copy Markdown
Member Author

This MRE:

import cadquery as cq
from cadquery.occ_impl.assembly import imprint
imprint(cq.Assembly().add(cq.Workplane().box(1, 1, 1)))

gives this error:

Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    imprint(cq.Assembly().add(cq.Workplane().box(1, 1, 1)))
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/assembly.py", line 885, in imprint
    res = Shape(builder.Shape())
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 445, in __init__
    self.wrapped = downcast(obj)
                   ~~~~~~~~^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 419, in downcast
    f_downcast: Any = downcast_LUT[shapetype(obj)]
                                   ~~~~~~~~~^^^^^
  File "/home/jwright/repos/cadquery/cadquery/occ_impl/shapes.py", line 409, in shapetype
    raise ValueError("Null TopoDS_Shape object")

Imprint needs at least 2 shapes to do something meaningful . The previous version does not indeed throw, I'll add an explicit check for this.

@adam-urbanczyk
adam-urbanczyk requested a review from jmwright July 26, 2026 08:02
@adam-urbanczyk

adam-urbanczyk commented Jul 26, 2026

Copy link
Copy Markdown
Member Author

Done, I also added a check for no solids at all.

@jmwright jmwright left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmwright
jmwright merged commit 6ab9565 into master Jul 26, 2026
7 checks passed
shimwell added a commit to fusion-energy/model_benchmark_zoo that referenced this pull request Jul 26, 2026
The zoo had no model where two solids meet over part of a curved surface.
Every existing multi-solid model either shares an exactly coincident face,
because the enclosing body was cut by the same primitive, or does not touch at
all. Partial contact on a curved surface is the case that separates OCCT's
gluing options, so add two models that cover it.

TwoAnnularSectors puts a shorter sector directly outside a taller one, both
spanning the same 90 degrees. The contact patch is a band that reaches the
angular edges of the first sector's outer face, so the imprint edges run clean
across that face. Every gluing option handles this, which makes it the control
case.

TwoAnnularSectorsPartialArc makes the second sector narrower as well as
shorter, so the patch is strictly interior to the first sector's outer face and
reaches its boundary in neither the axial nor the angular direction. Imprinting
then has to split the larger curved face around an island:

    glue          solids  shared faces  shared area
    full               2             0            0
    partial            2             1      23.5619
    None               2             1      23.5619

The shared area matches the analytic 6 * radians(45) * 5. Full gluing returns
the two solids with no shared face at all, so the DAGMC model would not be
watertight at that interface. This is the geometry that shows why partial is
the right default, following CadQuery/cadquery#2069.

Solid volumes are analytically exact, 212.0575 and 176.7146 for the band model
and 212.0575 and 88.3573 for the partial arc model, and the CSG regions agree.
Both models reproduce their CSG reference under the gmsh mesher, to 0.01
percent on material 1 and 0.10 percent on material 2.

Adds the models, tests for both suites, examples, preview images and README
entries. Both fail under the tolerance and angular_tolerance meshing path with
about 19 percent error on material 2, which is the same cad_to_dagmc defect
described in the parent pull request and not a property of these models.
@shimwell

shimwell commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

I am now using this feature and it is super useful.

I have a use case where I have some CQ geometry and I don't know if I can get away with 'full' imprinting (which is fast but can be wrong) or if I need 'partial' imprinting (which is slower but always correct).

The CQ models are large and prepared upstream so without a lot of inspection I'm never going to know if I can go for full imprint in some surfaces.

Is there any possibility of having another mode 'auto' which know when 'full' can be used and 'partial' is necessary. For example can we use check the faces/edges and somehow conservatively identify fully coincident contacts or partial face overlap

@lorenzncode

Copy link
Copy Markdown
Member

I am now using this feature and it is super useful.

I have a use case where I have some CQ geometry and I don't know if I can get away with 'full' imprinting (which is fast but can be wrong) or if I need 'partial' imprinting (which is slower but always correct).

The CQ models are large and prepared upstream so without a lot of inspection I'm never going to know if I can go for full imprint in some surfaces.

Is there any possibility of having another mode 'auto' which know when 'full' can be used and 'partial' is necessary. For example can we use check the faces/edges and somehow conservatively identify fully coincident contacts or partial face overlap

I'm not an OCCT expert but according to the docs isn't glue faster because it skips computations based on assumptions up front?

https://dev.opencascade.org/doc/overview/html/specification__boolean_operations.html#specification__boolean_7

The performance improvement in gluing mode is achieved by excluding the most time consuming computations and in some case can go up to 90%:

Exclude computation of FACE/FACE intersections for partial coincidence;
Exclude computation of VERTEX/FACE, EDGE/FACE and FACE/FACE intersections for full coincidence.
By setting the Gluing option for the operation user should guarantee that the arguments are really coinciding. The algorithm does not check this itself. Setting inappropriate option for the operation is likely to lead to incorrect result.

If you run these computation in 'auto' mode then doesn't that move a time consuming part to a different function?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants