Skip to content

Add per-object display modes to the Objects panel - #594

Open
markomarkovic wants to merge 12 commits into
CadQuery:masterfrom
markomarkovic:per-object-visibility
Open

markomarkovic wants to merge 12 commits into
CadQuery:masterfrom
markomarkovic:per-object-visibility

Conversation

@markomarkovic

Copy link
Copy Markdown
Contributor

Adds per-object display modes to the Objects panel. Each object can be shown as shaded, transparent, or wireframe, or hidden, chosen from a row of radios in the tree. An All row applies a single mode to every object at once as a global override, while each object keeps its own mode underneath.

Per-object display modes

Testing

New tests/test_display.py covers the state model; tests/test_app.py adds pytest-qt coverage for the tree state, viewer application, and the panel radios. The existing test_inspect was updated for the new "All" row and the radio-based visibility control. pytest tests/ — full suite passes.

Depends on #593 — this branch is stacked on it, so until #593 merges the diff also shows the main_window.py LogViewer fix.


Claude AI assisted with the implementation under my direction; design, testing, and review are mine.

@markomarkovic
markomarkovic force-pushed the per-object-visibility branch from aa64b4b to aa56474 Compare July 23, 2026 13:37
@markomarkovic

Copy link
Copy Markdown
Contributor Author

Rebased onto master to clear the conflicts with #590.

The per-object display modes now work with the nested assembly tree that #590 introduced:

  • Every item under CQ models gets its own row of mode radios, nested assembly parts included, and each part holds its own mode.
  • Setting the mode on an assembly row cascades to its parts. An assembly root often has no shape of its own, so on its own it would have nothing to show — this takes over the role the ItemIsAutoTristate check state propagation played in Better Assembly Handling #590, which no longer applies now that these rows carry radios instead of a checkbox.
  • The branch indent is drawn in the name column (setTreePosition), so the nesting stays visible while the mode columns still line up with their header icons.

Both entry points to a mode — the radios and the properties editor's dropdown — go through the same path, so either one cascades.

New tests cover the nested case: parts get their own radios and hold independent modes, a parent's mode cascades to its subtree, a hidden part stays hidden across a re-run with "Preserve properties on reload", and re-running a script that shows an assembly does not leak radio widgets.

Introduces the display module: a per-object DisplayMode (Hidden, Wireframe,
Transparent, Shaded), a panel-wide GlobalMode override, and effective_mode()
resolving the two. HIDDEN always wins so an override never unhides an object.
Adds a per-object Display mode property (Hidden/Wireframe/Transparent/Shaded)
to ObjectTreeItem and a panel-wide global mode on ObjectTree, emitting
sigDisplayModesChanged/sigGlobalModeChanged with the resolved effective modes
so the viewer can apply them.
Wires the object tree's display-mode signals to the viewer: apply_display_modes
erases hidden objects and sets wireframe/shaded/transparency per object, and the
Wireframe/Shaded/Transparent toolbar actions drive and reflect the global mode.
Replaces the name-column visibility checkbox with a row of mutually exclusive
radios per object (Hidden/Wireframe/Transparent/Shaded) and an 'All' row that
sets the global override. Radios are centered under centered header icons, and
are torn down with the item so the joining QButtonGroup does not leak it.
Attaching the four setItemWidget radios inside the insert loop made every
later addChild/expandItem walk the view's persistent editor table, so
addObjects was quadratic in the object count: the 400-shape render went from
0.23s to 1.28s locally and past the 2s test bound on CI. Inserting and
expanding every item first and attaching the radios afterwards brings it back
to 0.36s. The finally keeps items already added before a failing object (the
partial-add case test_render_colors checks) with their radios.
@jmwright

jmwright commented Oct 2, 2026

Copy link
Copy Markdown
Member

Thanks @markomarkovic

I noticed two issues while experimenting with this feature.

  1. Subassembly colors are lost.
lost_subassembly_color subassembly_color
  1. Some control colors do not work well with the dark theme
Screenshot from 2026-10-02 16-16-06

@markomarkovic

Copy link
Copy Markdown
Contributor Author

Thanks, I can reproduce the dark theme issue, I'll take care of it. Could you share a project that has colored sub-assemblies so I can reproduce and fix that too? Doesn't need to be that exact one, just a simple reproduction example would be helpful.

@jmwright

jmwright commented Oct 7, 2026

Copy link
Copy Markdown
Member

It looks like I may have been wrong about the cause. It's the alpha in the rgba that determines whether or not the color setting is respected. The assembly I was working on was in a state that made it look like it was the subassembly's fault. Here's a minimal example to reproduce. If I change any of the a settings from 0.5 to 1.0, I lose the color selection from cq.Color.

import cadquery as cq

# If you change the a in the rgba to 1.0, the cq.Color becomes ignored
box = cq.Workplane().box(10, 10, 10)
box_assy = cq.Assembly()
box_assy.add(box, color=cq.Color(1.0, 0.0, 0.0, 0.5))

# Same problem with the alpha here
main_assy = cq.Assembly()
main_assy.add(cq.Workplane().box(10.0, 10.0, 10.0),
             color=cq.Color(0.0, 1.0, 0.0, 0.5),
             loc=cq.Location((20.0, 20.0, 0.0)))

# I am nesting assemblies here to make sure the fix reaches depth
# Same problem with the alpha here though
sub_assy = cq.Assembly()
sub_assy.add(cq.Workplane().box(10.0, 10.0, 10.0),
             color=cq.Color(0.0, 0.0, 1.0, 0.5),
             loc=cq.Location((20.0, 20.0, 20.0)))
main_assy.add(sub_assy)

show_object(box_assy)
show_object(main_assy)

Draw the header icons in the palette's text color instead of qtawesome's
default near-black, and lighten the tree's Window color under the dark
theme so Fusion's radio and checkbox outlines stand out from the dark
Base.
The color lives on the object's own shading aspect, which
UnsetTransparency drops. It was called directly for a transparency of 0,
and AIS_InteractiveContext::SetTransparency calls it too when leaving the
transparent mode, so set the transparency on the object instead.
@markomarkovic

markomarkovic commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

@jmwright Thanks for the repro, both issues are fixed:

  • Dark theme (88178fc): the header icons now follow the palette's text color, and the radio outlines in the Objects panel are visible against the dark background.
  • Colors with alpha 1.0 (7ba817e): the part's color is stored on its own shading aspect, and OCCT's UnsetTransparency drops that aspect. It was being called for every fully opaque part, and also when leaving Transparent mode, so opaque colors fell back to the default. Transparency is now set on the object directly, which keeps the color. Your example now shows red, green and blue, and they survive switching to Transparent and back. A regression test covers it.

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.

2 participants