Skip to content

Add roots component to tree support for improved base stability#178

Merged
jgphilpott merged 11 commits intomainfrom
copilot/add-roots-to-tree-support
Mar 5, 2026
Merged

Add roots component to tree support for improved base stability#178
jgphilpott merged 11 commits intomainfrom
copilot/add-roots-to-tree-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 5, 2026

  • Explore codebase, understand tree support and canGenerateSupportAt
  • Fix 45° angle + hanging root suppression (commit 7d5c6fb)
  • Fix partial-overlap filtering (commit 6c12883)
    • Rework canGenerateSupportAt everywhere mode: per-layer clip behaviour, Z_EPSILON
    • Broaden _validRootIndices ray-cast: ROOT_RAY_SAMPLE_FRACTIONS = [0.5, 1.0, 1.5, 2.0]
    • Tests: 3 new canGenerateSupportAt everywhere-mode tests; hanging-root + broadened-ray tests
  • Apply automated reviewer suggestions (commit this one)
    • Export ROOT_RAY_SAMPLE_FRACTIONS from module.exports
    • Pre-filter layerSolidRegions to relevantLayers (within rootHeight window) before root ray-cast loop
    • Fix 'base layer should have more extrusion moves than a layer above the root zone' test to actually compare against a generated above-root-zone layer
    • All 836 tests pass

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: jgphilpott <4128208+jgphilpott@users.noreply.github.com>
Copilot AI changed the title [WIP] Add roots component to tree support algorithm Add roots component to tree support for improved base stability Mar 5, 2026
@jgphilpott
Copy link
Owner

This is a great start @copilot! Here are a few notes for improvement:

  • The roots connect to the trunk at a higher point than I was expecting, do you think you could reduce it by about half? There should be almost no gap (or none at all) between the root base and trunk base on the bottom layer.
  • I noticed that in the arch sideways everywhere example (resources/gcode/support/tree/arch/sideways-everywhere.gcode) that no roots are present. I think this is because they would be located inside the bottom part of the arch and are thus suppressed due to this blocking geometry. In this scenario where the bottom of a tree is not on the build plate but rather on top of part of the print then we need to treat the first trunk layer as the bottom of the tree and adjust the roots position accordingly.

More notes will come but this is a good start for now :)

…ight

Co-authored-by: jgphilpott <4128208+jgphilpott@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

This is a great start @copilot! Here are a few notes for improvement:

  • The roots connect to the trunk at a higher point than I was expecting, do you think you could reduce it by ...

Both issues addressed in commit ebf8507:

Root connection height: rootHeight = rootSpread * 0.5 — roots now converge to the trunk over half the horizontal spread distance, keeping them close to the base.

Floating trunk case: Roots are now generated dynamically in generateTreePattern rather than pre-computed in buildTreeStructure. Each call tracks region._effectiveTrunkBaseZ — the first layer Z at which the trunk actually prints. Roots are then placed in the range [effectiveTrunkBaseZ, effectiveTrunkBaseZ + rootHeight], so for an arch in 'everywhere' mode the roots correctly appear just above the arch body (the first printable trunk layer) instead of sitting inside the arch where they'd be suppressed.

@jgphilpott
Copy link
Owner

Nice progress @copilot, here are some more notes:

  • The roots (like branches) should still be at a 45 degree angle, so if the root height is reduced the distance from the trunk needs to be updated to match as well.
  • The floating trunk case is working nicely now except for 1 issue, sometimes it creates hanging roots. In the sideways dome example for instance (resources/gcode/support/tree/dome/sideways-everywhere.gcode) 3 of the 4 roots make contact with the dome interior proving additional support for the trunk but 1 is left hanging in mid air. It might be hard to fix this but if you think you can manage it that would be great! The hanging root looks silly and provides no support so is thus useless.

More notes will come but this is a enough for now :)

…e mode

Co-authored-by: jgphilpott <4128208+jgphilpott@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

Nice progress @copilot, here are some more notes:

  • The roots (like branches) should still be at a 45 degree angle, so if the root height is reduced the distance from the trunk ne...

Both fixed in commit 7d5c6fb:

45° angle: The base spread was rootSpread but should have been rootHeight (rootSpread * 0.5). Corrected to rootEndX = trunkX + rootHeight * cos(angle) so horizontal spread equals vertical height — exactly 45°.

Hanging roots: Added region._validRootIndices pre-computed on the first call at effectiveTrunkBaseZ. For everywhere mode, each root's base endpoint is checked with isPointInsideSolidGeometry on the layer just below (effectiveBaseZ - layerData.z <= layerHeight * ROOT_SUPPORT_LAYER_SEARCH_DEPTH). Only roots that land on solid geometry make it into _validRootIndices — any root pointing off the edge of the surface is silently dropped. buildPlate mode is unaffected.

@jgphilpott
Copy link
Owner

Great progress @copilot but I think we need to address the issue of partial support/object overlap. Right now it looks like if a support structure overlaps even slightly with part of the printed object then it is removed/filtered out entirely. Instead what we should do is to clip the support structure, until full overlap is reached. This will allow the supports to get closer to the object and look less like they are hanging in the air when on curved surfaces.

I also noticed that the sideways everywhere dome example resources/gcode/support/tree/dome/sideways-everywhere.gcode now has just one root when I was expecting three. I think this may be related to the issue above and the two side roots are getting filtered out early because of partial overlap ... but i'm not sure?

Do you think you can investigate and resolve this issue for me? This could potential effect not only roots but the other components as well, like twigs.

Thanks!

… for curved surfaces

- canGenerateSupportAt (everywhere mode): replace "solid ended in N layers" check
  with a per-layer embedded check (current layer only). Support is now clipped at
  each layer independently, so roots/twigs on curved surfaces are kept until they
  actually enter the object.
- _validRootIndices: sample 0.5×/1.0×/1.5×/2.0× rootHeight along each root ray
  (ROOT_RAY_SAMPLE_FRACTIONS) to detect dome walls further from the trunk than
  the base endpoint alone. Removes ROOT_SUPPORT_LAYER_SEARCH_DEPTH constant.
- Add Z_EPSILON (1e-9) to minimumSupportZ comparison to absorb FP accumulation
  in layer heights.
- Tests: 2 new canGenerateSupportAt everywhere-mode tests, updated hanging-root
  test (tinyRadius derived from rootHeight), new broadened-ray-cast test.

Co-authored-by: jgphilpott <4128208+jgphilpott@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

Great progress @copilot but I think we need to address the issue of partial support/object overlap. Right now it looks like if a support structure overlaps even slightly with part of...

Fixed in commit 6c12883 with two changes:

Clip instead of filter — canGenerateSupportAt everywhere mode (normal.coffee):
The old "solid ended in last 3 layers" check was replaced with a per-layer embedded check: each layer is tested independently against the current-layer solid geometry. Support is now clipped — it generates at every layer where the node is not embedded inside the object, so it naturally terminates when it enters a curved surface rather than being blanket-rejected. A Z_EPSILON = 1e-9 constant guards against floating-point accumulation in layer height sums.

Broadened ray-cast — _validRootIndices (tree.coffee):
The old check only tested whether the exact root base endpoint (1 × rootHeight from trunk) was inside solid geometry 2 layers below. For a dome whose hollow interior is wider than rootHeight the base sits in empty space, so all dome-surface roots were wrongly treated as hanging. Replaced with a multi-distance ray-cast using ROOT_RAY_SAMPLE_FRACTIONS = [0.5, 1.0, 1.5, 2.0]: each direction is probed at 0.5× through 2× rootHeight, so dome walls further from the trunk are detected. The ROOT_SUPPORT_LAYER_SEARCH_DEPTH constant is no longer needed and has been removed.

@jgphilpott jgphilpott marked this pull request as ready for review March 5, 2026 13:58
Copilot AI review requested due to automatic review settings March 5, 2026 13:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a “roots” component to tree supports to improve base stability and updates everywhere-mode collision logic to support clip-until-embedded behavior on curved surfaces.

Changes:

  • Add dynamic root cross-sections in generateTreePattern, anchored to an effective trunk base layer and suppressed in everywhere mode when no solid is detected along sampled rays.
  • Update canGenerateSupportAt (everywhere mode) to compute highest solid strictly below the current layer and block only when embedded at the current layer, with a small Z epsilon.
  • Extend/adjust support regression fixtures and expand unit tests for both tree roots and everywhere-mode support placement.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/slicer/support/tree/tree.coffee Implements dynamic root generation, root sizing, and broadened ray-cast support checks in everywhere mode.
src/slicer/support/tree/tree.test.coffee Adds tests for dynamic roots, effective base tracking, radius ordering, spread/angle constraints, and hanging-root suppression.
src/slicer/support/normal/normal.coffee Adjusts everywhere-mode collision logic to “clip until embedded” and adds a Z epsilon for comparisons.
src/slicer/support/normal/normal.test.coffee Adds/updates tests for everywhere-mode allow/block behavior (embedded, just-above-solid).
resources/gcode/support/tree/dome/upright.gcode Updates LFS pointer for regenerated tree-support fixture output.
resources/gcode/support/tree/dome/sideways-everywhere.gcode Updates LFS pointer for regenerated tree-support fixture output.
resources/gcode/support/tree/arch/upright.gcode Updates LFS pointer for regenerated tree-support fixture output.
resources/gcode/support/tree/arch/sideways-everywhere.gcode Updates LFS pointer for regenerated tree-support fixture output.
resources/gcode/support/normal/dome/sideways-everywhere.gcode Updates LFS pointer for regenerated normal-support fixture output.
resources/gcode/support/normal/arch/sideways-everywhere.gcode Updates LFS pointer for regenerated normal-support fixture output.

@jgphilpott
Copy link
Owner

@copilot apply changes based on the comments in this thread

…NS, pre-filter layers, fix test comparison

- Export ROOT_RAY_SAMPLE_FRACTIONS from module.exports so downstream code and
  tests can reference the constant (as documented in the PR description).
- Pre-filter layerSolidRegions to relevantLayers (within rootHeight window of
  effectiveBaseZ) before the root × sampleFraction nested loop.  Removes the
  per-iteration Z-range check and avoids iterating all layers on tall prints.
- Fix 'base layer should have more extrusion moves than a layer above the root
  zone' test: actually generate a layer above the root zone (z=2.0) and compare
  the per-call extrusion counts, instead of comparing against the constant
  singleNodeExtrusions.

Co-authored-by: jgphilpott <4128208+jgphilpott@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot apply changes based on the comments in this thread

Applied all three reviewer suggestions in commit 8ec4746:

  1. Export ROOT_RAY_SAMPLE_FRACTIONS — added to module.exports alongside the other constants so downstream code and tests can reference it.

  2. Pre-filter layerSolidRegionsrelevantLayers is computed once before the root loop by filtering to layers within the rootHeight window of effectiveBaseZ. The per-iteration Z-range guard inside the loop is removed, so each root × sampleFraction only iterates the small relevant subset rather than all layers.

  3. Fixed 'base layer should have more extrusion moves than a layer above the root zone' test — the test now actually generates a second layer at z = 2.0 (above rootTopZ ≈ 1.9) using a fresh slicer/region pair, records the delta G-code from that second call, and asserts baseExtrusionCount > aboveLayerExtrusionCount, as the reviewer's suggestion described.

@jgphilpott jgphilpott merged commit 163089d into main Mar 5, 2026
1 check passed
@jgphilpott jgphilpott deleted the copilot/add-roots-to-tree-support branch March 5, 2026 14:22
Copilot AI mentioned this pull request Mar 8, 2026
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