feat(stage): composable 3D stage for robot arms and CNC machines - #18
Merged
Merged
Conversation
Matrix3 and Transform3 (axis-angle, RPY, DH, AlignZ, row-major import), ClipPolygonNear/ClipSegmentNear, ViewFrame::ToView/ProjectView/ ProjectWithDepth, and OrbitCamera pan and Frame. The ground grid now clips at the near plane instead of smearing once the eye can hover over it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
A Mesh of shared vertices and tri/quad faces with per-face normals and hard-edge masks. Primitive builders for a box, cylinder, cone, sphere, capsule and plane, lathed from one profile helper. ParseStl reads binary and ASCII STL from a byte span with welding, scale and a triangle cap. Floats are parsed by hand because from_chars<float> is missing from older libc++. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
… model SceneGraph holds parent-before-child nodes with fixed, revolute and prismatic joints (clamped to limits, settable from a q vector) and settles world transforms in one lazy pass. Stage owns shared unit meshes, materials with presets, parts, fixed-size labels, ring-buffer trails and frame markers; StageBuilder adds boxes, cylinders, links and joint housings. Six scene colour roles join both palettes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
…picking StageRenderer transforms each visible part once into view space, culls back faces (kept for translucent and double-sided materials), clips at the near plane, flat-shades with a key light, eye fill and Blinn-Phong highlight, and sorts far to near before one DrawPolygon per face. Hard edges outline without facet seams, trails emit as depth-sorted 16-segment polylines, and frame markers and labels draw on top. Pick() reuses the last frame's sorted polygons. Scratch buffers grow only when the stage does; a dedicated suite counts operator new to prove steady-state paint is allocation-free. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
Owns the Stage, renderer and orbit camera. Left-drag orbits; right-, middle- or shift-drag pans; the wheel zooms; a click inside the slop picks and selects (onPick, onSelectionChanged); double-click frames the scene; Escape clears the selection and Home resets the camera. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
…jects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
doc/scene3d.md covers building a scene, interaction, the rendering pipeline, the allocation contract and the painter's-algorithm limits. ui/stage joins the Tier 1 and allocation-free lists. A Qt test renders a StageView offscreen and selects a part through the Qt adapter. examples/stage (UI_BUILD_EXAMPLES, on in the host-qt preset so CI keeps it compiling) shows a six-axis arm and a gantry mill with a toolpath. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
✅
|
| Descriptor | Linter | Files | Fixed | Errors | Max errors | Warnings | Elapsed time |
|---|---|---|---|---|---|---|---|
| ✅ ACTION | actionlint | 7 | 0 | 0 | 0.69s | ||
| ✅ CPP | clang-format | 158 | 0 | 0 | 0 | 2.03s | |
| ✅ CPP | cppcheck | 158 | 0 | 0 | 2.03s | ||
| ✅ DOCKERFILE | hadolint | 1 | 0 | 0 | 0.39s | ||
| ✅ JSON | jsonlint | 8 | 0 | 0 | 0.11s | ||
| ✅ JSON | prettier | 8 | 2 | 0 | 0 | 0.34s | |
| ✅ MARKDOWN | markdownlint | 14 | 0 | 0 | 0 | 0.91s | |
| ✅ MARKDOWN | markdown-table-formatter | 14 | 0 | 0 | 0 | 0.18s | |
| lychee | 60 | 1 | 0 | 1.63s | |||
| ✅ YAML | prettier | 11 | 0 | 0 | 0 | 0.48s | |
| ✅ YAML | yamllint | 11 | 0 | 0 | 0.59s |
Detailed Issues
⚠️ SPELL / lychee - 1 error
📝 Summary
---------------------
🔍 Total...........23
🔗 Unique...........9
✅ Successful......15
⏳ Timeouts.........0
🔀 Redirected.......1
👻 Excluded.........7
❓ Unknown..........0
🚫 Errors...........1
⛔ Unsupported......1
Errors in .github/workflows/static-analysis.yml
[403] https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$ (at 37:21) | Rejected status code: 403 Forbidden
Hint: Followed 1 redirect. You might want to consider replacing redirecting URLs with the resolved URLs. Use verbose mode (`-v`/`-vv`) to see redirection details.
Hint: You can configure accepted/rejected response codes with `-a` or `--accept`
Notices
REPOSITORY_GITLEAKS, REPOSITORY_KICS. See Removed linters to find their replacements.
See detailed reports in MegaLinter artifacts
Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)
- Documentation: Custom Flavors
- Command:
npx mega-linter-runner@10.0.0 --custom-flavor-setup --custom-flavor-linters ACTION_ACTIONLINT,CPP_CPPCHECK,CPP_CLANG_FORMAT,DOCKERFILE_HADOLINT,JSON_JSONLINT,JSON_PRETTIER,MARKDOWN_MARKDOWNLINT,MARKDOWN_MARKDOWN_TABLE_FORMATTER,SPELL_LYCHEE,YAML_PRETTIER,YAML_YAMLLINT

Show us your support by starring ⭐ the repository
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
Explicit has_value() on optionals, hidden-friend operators for Matrix3 and Transform3, std::byte for the hard-edge mask, emplace_back for scene nodes, a template instead of a function pointer for shared meshes, std::ranges::sort, std::size on generic containers, a const Refresh(), using enum for mouse buttons, and a plain while loop in the ASCII STL reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
StageView's pick and selection callbacks become private members set through SetOnPick and SetOnSelectionChanged, so the class no longer mixes public and private data. cpp:S6022 joins the project-wide Sonar ignores: viewing bytes as text and small bit masks don't need std::byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Adds
ui/stage, a Tier 1 3D view: a stage (camera, lights, grid) plus parts you hang off a joint tree. It draws through the existing 2DCanvasusing the painter's algorithm, so there is no Qt, no GPU and noCanvaschange, and it is tested withRecordingCanvas.What's in it
ui/scene(extended)Matrix3andTransform3: rigid transforms with axis-angle, RPY, DH,AlignZand a 3×4 row-major import.ClipPolygonNear/ClipSegmentNearclip geometry at the camera's near plane.ViewFramegainsToView,ProjectViewandProjectWithDepth.OrbitCameragains pan andFrame(centre, radius).ui/stage(new)Mesh, with hard-edge masks so outlines skip facet seams.Unit primitives: box, cylinder, cone, sphere, capsule, plane.
ParseStl/LoadStlFile: binary and ASCII, welding, scale and a triangle cap. Floats are parsed by hand becausefrom_chars<float>is missing from older libc++.SceneGraph: fixed, revolute and prismatic joints with limits, andSetJointValues(q)in joint-creation order.Materialwith presets (Themed,Matte,Plastic,Metal,Aluminium,Steel,Translucent,Wireframe) andLighting.Stageholds parts, fixed-size labels, ring-buffer trails and frame markers.StageBuilderaddsAddBox,AddCylinder,AddLink,AddJointHousingand friends.StageRendererrenders each face in turn:DrawPolygonper faceIt also emits trails as depth-sorted chunks, then frame markers and labels. Clicks are picked against the last painted frame.
StageView : PaintedView:SetOnPickandSetOnSelectionChanged.Other changes
SceneSurface,SceneJoint,SceneEdge,SceneSelection,SceneTrail,SceneLabel.doc/scene3d.mdcovers usage, the allocation rules and the limitations.ui/stageis added to the Tier 1 and allocation-free lists inAGENTS.mdanddoc/portability.md.examples/stageis a Qt demo of a six-axis arm and a gantry mill with a toolpath. It builds behind the newUI_BUILD_EXAMPLESoption, which is on in thehost-qtpreset so CI keeps it compiling.sonar-project.propertiesaddscpp:S6022("usestd::bytefor byte-oriented memory access") to the project-wide ignored rules. Viewing raw bytes as characters, as the ASCII STL reader does, is an ordinary use ofchar.Allocation contract
Adding nodes, parts, meshes or trails allocates, and belongs to setup. Moving joints, pushing trail points, relabelling, recolouring and resizing never allocate. The renderer's scratch buffers grow only on the first paint after the stage has grown.
ui.stage_allocation_testreplacesoperator newwith a counter, paints 200 frames that change all of those things, and requires zero allocations.Known limitations
These are documented in
doc/scene3d.md:OrbitLimitssuit a scene about a metre across.Testing
hostandhost-qtpresets build and all tests pass: 12 and 13 suites respectively. That includes 92 stage tests and a Qt test that renders aStageViewoffscreen and selects a part throughQtPaintedWidget.scripts/validate-docs.pypasses. The guardrail greps are clean: no Qt in Tier 1, noIprefix, nostd::formatinui/sceneorui/stage.clang-formatis clean.🤖 Generated with Claude Code
https://claude.ai/code/session_019pVVrF6XXfUvyzubDXtGVo