Skip to content

Conversation

jmwright
Copy link
Member

@jmwright jmwright commented Feb 26, 2025

The goal is to make it possible to round-trip assemblies to and from STEP without loss of data. This data can include:

  • Part location
  • Part color
  • Shape colors
  • Shape colors
  • Shape names (advanced face)

Copy link

codecov bot commented Feb 26, 2025

Codecov Report

❌ Patch coverage is 93.25153% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.98%. Comparing base (8431073) to head (d958c2b).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
cadquery/occ_impl/importers/assembly.py 90.35% 3 Missing and 8 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1779      +/-   ##
==========================================
+ Coverage   95.66%   95.98%   +0.31%     
==========================================
  Files          28       30       +2     
  Lines        7431     9233    +1802     
  Branches     1122     1499     +377     
==========================================
+ Hits         7109     8862    +1753     
- Misses        193      228      +35     
- Partials      129      143      +14     

☔ View full report in Codecov by Sentry.
📢 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.

@jmwright
Copy link
Member Author

@adam-urbanczyk In 4045c58 you set the cadquery.Assembly.importStep method up to be a class method, and you construct assy before calling the lower-level method. However, since the Assembly.name property is private and can only be set during instantiation, this creates an issue for me. I need to be able to set the top-level assembly name based on the name set in the STEP file, which requires me to set the name property after instantiation.

We need to decide on the proper way to fix this.

@adam-urbanczyk
Copy link
Member

Hey, I do not understand the issue. You can always do this:

assy = Assembly()
assy.name = '123'

Do you mean that you need to modify AssemblyProtocol to satisify mypy? That also does not sound like an issue to me.

@jmwright
Copy link
Member Author

Correct, mypy complains. I can make that change to make name a public property if you don't see an issue with doing so.

@lorenzncode
Copy link
Member

I am still getting a ValueError with a STEP from the tests.

(cqdev) lorenzn@fedora:~/devel/cadquery$ git status -u no
On branch assembly-import
Your branch is up to date with 'upstream/assembly-import'.

nothing to commit, working tree clean
(cqdev) lorenzn@fedora:~/devel/cadquery$ pytest tests/test_assembly.py --basetemp=./tmp2 -k test_colors_assy1 -q --no-summary
...........                                                                                                                                                        [100%]
11 passed, 96 deselected, 13 warnings in 0.58s
sys:1: DeprecationWarning: builtin type swigvarlink has no __module__ attribute
(cqdev) lorenzn@fedora:~/devel/cadquery$ cat test2.py 
from cadquery import Assembly

obj = Assembly.importStep("./tmp2/assembly10/chassis0_assy.step")
(cqdev) lorenzn@fedora:~/devel/cadquery$ python test2.py 
Traceback (most recent call last):
  File "/home/lorenzn/devel/cadquery/test2.py", line 3, in <module>
    obj = Assembly.importStep("./tmp2/assembly10/chassis0_assy.step")
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lorenzn/devel/cadquery/cadquery/assembly.py", line 622, in importStep
    _importStep(assy, path)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 194, in importStep
    process_label(labels.Value(1))
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 173, in process_label
    process_label(sub_label, loc, name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 149, in process_label
    process_label(ref_label, parent_location, parent_name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 173, in process_label
    process_label(sub_label, loc, name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 149, in process_label
    process_label(ref_label, parent_location, parent_name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 173, in process_label
    process_label(sub_label, loc, name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 149, in process_label
    process_label(ref_label, parent_location, parent_name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 173, in process_label
    process_label(sub_label, loc, name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 149, in process_label
    process_label(ref_label, parent_location, parent_name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 179, in process_label
    _process_simple_shape(label, parent_location, parent_name)
  File "/home/lorenzn/devel/cadquery/cadquery/occ_impl/importers/assembly.py", line 74, in _process_simple_shape
    assy.add(
  File "/home/lorenzn/devel/cadquery/cadquery/assembly.py", line 241, in add
    self.add(assy)
  File "/home/lorenzn/devel/cadquery/cadquery/assembly.py", line 222, in add
    raise ValueError("Unique name is required")
ValueError: Unique name is required

@adam-urbanczyk
Copy link
Member

@jmwright any updates on this PR?

@jmwright
Copy link
Member Author

@adam-urbanczyk Not yet. I got busy with some other things.

@jmwright
Copy link
Member Author

@adam-urbanczyk I'm getting mypy errors now that I did not before. Most of them are probably my fault, but there is this one about the OpenCASCADE code.

cadquery\occ_impl\importers\assembly.py:105: error: "TDF_Attribute" has no attribute "GetFather"  [attr-defined]

Does this have anything to do with the stubs being published as part of OCP 7.9.x?

@adam-urbanczyk
Copy link
Member

Nothing should be published, but let me check

@jmwright
Copy link
Member Author

jmwright commented Jul 29, 2025

I'm having trouble with mypy. It is giving me different errors locally than I get in CI even though I have the same version. I'll create a new environment from scratch tomorrow and try to match the CI environment to see if I can get rid of the discrepancies.

@jmwright
Copy link
Member Author

@adam-urbanczyk @lorenzncode Please have another look when you get a chance. This PR has been significantly reworked, and a test or two added.

@adam-urbanczyk
Copy link
Member

Something is off with top level location handling.

Before importing:
image

After importing:
image

Code:

#%% imports
from cadquery import Assembly, Location, Color, importers
from cadquery.func import box, rect, sphere
from cadquery.vis import show

#%% prepare the model
def make_model(name: str, COPY: bool):
    b = box(1,1,1)
    
    assy_top = Assembly(name='test_assy_top', loc=Location((0,1,1)))
    assy_top.add(sphere(1))
    
    assy = Assembly(name='test_assy', loc=Location((5,5,1)))
    assy.add(box(1,2,5), color=Color('green'))
    
    
    
    for v in rect(10,10).vertices():
        assy.add(
            b.copy() if COPY else b,
            loc=Location(v.Center()),
            color=Color('red')
        )
        
    # show(assy)
    assy_top.add(assy)
    assy_top.export(name)
    
    return assy_top

assy = make_model("test_assy.step", False)

show(assy, Location(),  scale=5)

#%% import the assy without copies - this throws

assy_i = Assembly.importStep("test_assy.step")
wp = importers.importStep("test_assy.step")

show(assy_i, Location(),scale=5)

@jmwright
Copy link
Member Author

jmwright commented Aug 4, 2025

@adam-urbanczyk Thanks, I fixed that bug and modified one of the tests so that I could add an assert to verify.

Copy link
Member

@lorenzncode lorenzncode left a comment

Choose a reason for hiding this comment

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

Thanks for the big effort @jmwright! The error I had before is resolved now when I try importing from the pytest generated step file.

I find import/export round trip changes the assembly as follows. Is this expected?

from cadquery import Assembly, Location
from cadquery.func import box


def round_trip_test(assy, n=3):
    for i in range(n):
        print(f"\n{i=}")
        for name in assy._flatten().keys():
            print(name)
        assy.export(f"out{i}.step")
        assy = Assembly.importStep(f"out{i}.step")


b = box(1, 1, 1)
assy = Assembly(name="assy")
assy.add(b, name="box0")
assy.add(b, name="box1", loc=Location((1, 0, 0)))

round_trip_test(assy, 5)
i=0
assy/box0
assy/box1
assy

i=1
assy/box0/box0_part
assy/box0
assy/box1/box0_part
assy/box1
assy

i=2
assy/box0/box0_part/box0_part_part
assy/box0/box0_part
assy/box0
assy/box1/box0_part/box0_part_part
assy/box1/box0_part
assy/box1
assy

i=3
assy/box0/box0_part/box0_part_part/box0_part_part_part
assy/box0/box0_part/box0_part_part
assy/box0/box0_part
assy/box0
assy/box1/box0_part/box0_part_part/box0_part_part_part
assy/box1/box0_part/box0_part_part
assy/box1/box0_part
assy/box1
assy

i=4
assy/box0/box0_part/box0_part_part/box0_part_part_part/box0_part_part_part_part
assy/box0/box0_part/box0_part_part/box0_part_part_part
assy/box0/box0_part/box0_part_part
assy/box0/box0_part
assy/box0
assy/box1/box0_part/box0_part_part/box0_part_part_part/box0_part_part_part_part
assy/box1/box0_part/box0_part_part/box0_part_part_part
assy/box1/box0_part/box0_part_part
assy/box1/box0_part
assy/box1
assy

assy = subshape_assy()

# Use a temporary directory
tmpdir = tmp_path_factory.mktemp("out")
Copy link
Member

Choose a reason for hiding this comment

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

FYI, you could use the tmpdir fixture (line 42) to reduce boilerplate.

tmpdir = tmp_path_factory.mktemp("out")
plain_step_path = os.path.join(tmpdir, "plain_assembly_step.step")

# Simple cubes
Copy link
Member

Choose a reason for hiding this comment

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

There are existing assembly fixtures that could be used here (and for other tests) instead. OK if you would rather explicitly redefine here.

@adam-urbanczyk
Copy link
Member

adam-urbanczyk commented Aug 9, 2025

Thanks for the big effort @jmwright! The error I had before is resolved now when I try importing from the pytest generated step file.

I find import/export round trip changes the assembly as follows. Is this expected?

That is definitely not OK.

else:
cq_color = None

new_assy.add(
Copy link
Member

Choose a reason for hiding this comment

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

So probably some logic should be added here to handle the name / name_part pairs. E.g. something like:

if ref_name.endswith('_part') and ref_name.startswith(parent_name):
    new_assy[parent_name].obj = cq_shape
else:
    ...

CC @lorenzncode

Copy link
Member

@adam-urbanczyk adam-urbanczyk Aug 20, 2025

Choose a reason for hiding this comment

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

@lorenzncode @jmwright I added this draft PR #1884, it needs some work still.

  • Added logic to handle _part shapes
  • Fixed _copy() to include metadata

NB: I think that at least one test is actually wrong. Subshapes should be a property of the (sub)assy that owns the shape. Not a top level one.

assert assy.children[0].name == "cube_1"
assert assy.children[1].children[0].name == "cylinder_1"


@pytest.mark.parametrize(
Copy link
Member

Choose a reason for hiding this comment

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

Probably a test that asserts idempotence of export/import is also needed.

@jmwright
Copy link
Member Author

@adam-urbanczyk I've pulled in your changes from #1884 and have things working except for a little bit of code coverage. You had a FIXME comment in the code that I did not remove because I was not sure what you meant beyond fixing the mypy error for that code. Feel free to commit directly to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants