Fill Adaptive disconnecting from walls at low density #1281

Closed
opened 2026-04-05 17:11:02 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @igiannakas on 12/24/2025

A different approach to fixing the same issue as described in PR #11725

I believe that adaptive infill (eg adaptive cubic) can degrade at very low sparse densities when the computed line spacing becomes comparable to (or larger than) the model’s bounding box. In this scenario the octree can be built with a single level, which prevents triangle insertion and effectively disables the “adaptive” portion of the algorithm. This PR ensures the octree always has at least one refinement step so triangle insertion can proceed and infill generation remains geometry-aware without changing the finest cell resolution (and therefore avoiding non-monotonic “weight” vs density effects).

Root Cause Hypothesis:

Adaptive Cubic constructs octree levels in make_cubes_properties() starting from edge_length = 2 * line_spacing, doubling until edge_length > max_cube_edge_length.

At low densities, line_spacing can become very large. When 2 * line_spacing > max_cube_edge_length, the loop terminates after the first iteration and cubes_properties.size() becomes 1.

However, the downstream build_octree() only inserts triangles when there is more than one level:

if (cubes_properties.size() > 1) {
    ... insert_triangle() ...
}

When cubes_properties.size() == 1, no triangles are inserted, no children cubes are created, and infill line generation effectively runs against a single root cube.

This produces coarse line placement and clipping artefacts, illustrated here as sparse infill segments ending short of perimeters, especially visible in preview at low infill widths.

Proposed fix

Amend the code to guarantee at least two cube-property levels by adding a coarser parent/root level when only one is generated. After building cubes_properties we insert the below code segment:

// Orca: Ensure at least 2 levels so build_octree() will insert triangles.
// Fixes scenario where adaptive fill is disconnected from walls on low densities
// Adds a coarser root level (instead of a finer leaf) to preserve effective density behavior.
if (cubes_properties.size() == 1) {
    CubeProperties p = cubes_properties.back();
    p.edge_length      *= 2.0;
    p.height           = p.edge_length * sqrt(3);
    p.diagonal_length  = p.edge_length * sqrt(2);
    p.line_z_distance  = p.edge_length / sqrt(3);
    p.line_xy_distance = p.edge_length / sqrt(6);
    cubes_properties.push_back(p);
}

This creates:

  • Level 0: edge_length = 2 * line_spacing (the original single level / finest level)
  • Level 1: edge_length = 4 * line_spacing (new coarser root level)

How it works:

The code first checks whether the octree configuration ended up with only one cube size (cubes_properties.size() == 1).

  • This happens at very low infill densities where the computed line spacing is so large that only one “big cube” level is generated.
  • If there’s only one level, the algorithm later can’t build a real octree (it won’t subdivide and it won’t insert mesh triangles), so the adaptive infill becomes coarse and can clip badly near walls.

To prevent that, this block creates a second, larger (coarser) cube level:

  • It copies the existing (only) cube properties (p = cubes_properties.back()).
  • It doubles the cube edge length (p.edge_length *= 2.0), meaning the new level is a coarser parent/root cube size.

Because cube geometry values depend on edge length, it recomputes:

  • the cube “height” in the rotated octree orientation,
  • face diagonal length,
  • the Z and XY distances used for where to place infill lines within the cube.

Finally, it appends this new coarser level to the end of the list.

That means the octree now has at least two levels: a finer one and the original coarse one. The end result is even at very low densities, there’s always at least one refinement step available, so build_octree() can subdivide and insert triangles into child cells instead of falling back to a single giant cube level that produces poorly-behaved infill near perimeters.

Importantly, this also avoids introducing a finer-than-original leaf level, which can otherwise increase effective material at very low densities (e.g. making 3% heavier than 5%).

As a result:

  • cubes_properties.size() > 1 holds,
  • triangle insertion is executed,
  • the octree is populated where geometry exists,
  • infill line generation remains adaptive instead of collapsing to a root-only pattern.

Alternative:

The alternative approach in the PR: https://github.com/OrcaSlicer/OrcaSlicer/pull/11725 changes the break condition to the below:
if (edge_length > 1.5 * max_cube_edge_length) break;

This can produce a second level for some models; however:

  • I think it may be less reliable across different model sizes/densities,
  • It increases the root cube size rather than adding refinement,
  • does not guarantee cubes_properties.size() > 1 in the lowest-density regimes.

I think this proposed PR may be more "robust", as the additional parent-level insertion directly guarantees triangle insertion while keeping the original finest cell size intact, which also helps preserve expected density/material trends.

Note:
This PR includes the changes from @RF47 PR on disabling rotation of sparse infill for adaptive fills.

Screenshots:

Small cube:
Before:
image

This PR:
image

Larger cube
Before:
image
This PR:
image

Multiline test with low infill density:
image

*Originally created by @igiannakas on 12/24/2025* A different approach to fixing the same issue as described in PR #11725 I believe that adaptive infill (eg adaptive cubic) can degrade at very low sparse densities when the computed line spacing becomes comparable to (or larger than) the model’s bounding box. In this scenario the octree can be built with a single level, which prevents triangle insertion and effectively disables the “adaptive” portion of the algorithm. This PR ensures the octree always has at least one refinement step so triangle insertion can proceed and infill generation remains geometry-aware without changing the finest cell resolution (and therefore avoiding non-monotonic “weight” vs density effects). ### Root Cause Hypothesis: Adaptive Cubic constructs octree levels in make_cubes_properties() starting from **edge_length = 2 * line_spacing**, doubling until **edge_length > max_cube_edge_length**. At low densities, line_spacing can become very large. When **2 * line_spacing > max_cube_edge_length**, the loop terminates after the first iteration and **cubes_properties.size() becomes 1**. However, the downstream build_octree() only inserts triangles when there is more than one level: ``` if (cubes_properties.size() > 1) { ... insert_triangle() ... } ``` **When cubes_properties.size() == 1, no triangles are inserted, no children cubes are created, and infill line generation effectively runs against a single root cube**. This produces coarse line placement and clipping artefacts, illustrated here as sparse infill segments ending short of perimeters, especially visible in preview at low infill widths. ### Proposed fix Amend the code to guarantee at least two cube-property levels by adding a coarser parent/root level when only one is generated. After building cubes_properties we insert the below code segment: ``` // Orca: Ensure at least 2 levels so build_octree() will insert triangles. // Fixes scenario where adaptive fill is disconnected from walls on low densities // Adds a coarser root level (instead of a finer leaf) to preserve effective density behavior. if (cubes_properties.size() == 1) { CubeProperties p = cubes_properties.back(); p.edge_length *= 2.0; p.height = p.edge_length * sqrt(3); p.diagonal_length = p.edge_length * sqrt(2); p.line_z_distance = p.edge_length / sqrt(3); p.line_xy_distance = p.edge_length / sqrt(6); cubes_properties.push_back(p); } ``` This creates: - Level 0: edge_length = 2 * line_spacing (the original single level / finest level) - Level 1: edge_length = 4 * line_spacing (new coarser root level) ### How it works: The code first checks whether the octree configuration ended up with only one cube size (cubes_properties.size() == 1). - This happens at very low infill densities where the computed line spacing is so large that only one “big cube” level is generated. - If there’s only one level, the algorithm later can’t build a real octree (it won’t subdivide and it won’t insert mesh triangles), so the adaptive infill becomes coarse and can clip badly near walls. To prevent that, this block creates a second, larger (coarser) cube level: - It copies the existing (only) cube properties (p = cubes_properties.back()). - It doubles the cube edge length (p.edge_length *= 2.0), meaning the new level is a coarser parent/root cube size. Because cube geometry values depend on edge length, it recomputes: - the cube “height” in the rotated octree orientation, - face diagonal length, - the Z and XY distances used for where to place infill lines within the cube. Finally, it appends this new coarser level to the end of the list. That means the octree now has at least two levels: a finer one and the original coarse one. The end result is even at very low densities, there’s always at least one refinement step available, so build_octree() can subdivide and insert triangles into child cells instead of falling back to a single giant cube level that produces poorly-behaved infill near perimeters. Importantly, this also avoids introducing a finer-than-original leaf level, which can otherwise increase effective material at very low densities (e.g. making 3% heavier than 5%). ### As a result: - cubes_properties.size() > 1 holds, - triangle insertion is executed, - the octree is populated where geometry exists, - infill line generation remains adaptive instead of collapsing to a root-only pattern. ### Alternative: The alternative approach in the PR: https://github.com/OrcaSlicer/OrcaSlicer/pull/11725 changes the break condition to the below: ```if (edge_length > 1.5 * max_cube_edge_length) break;``` This **can produce a second level** for some models; however: - I think it may be less reliable across different model sizes/densities, - It increases the root cube size rather than adding refinement, - does not guarantee cubes_properties.size() > 1 in the lowest-density regimes. I think this proposed PR may be more "robust", as the additional parent-level insertion directly guarantees triangle insertion while keeping the original finest cell size intact, which also helps preserve expected density/material trends. **Note:** This PR includes the changes from @RF47 PR on disabling rotation of sparse infill for adaptive fills. ### Screenshots: **Small cube:** Before: ![image](https://github.com/user-attachments/assets/1e66530d-39be-4941-a8d1-8f02403d83e0) This PR: ![image](https://github.com/user-attachments/assets/1c10f17c-fa20-4aae-9f12-9e8ebfc669de) **Larger cube** Before: ![image](https://github.com/user-attachments/assets/c99e54b6-ba4b-44a9-bb57-489b84ac3b63) This PR: ![image](https://github.com/user-attachments/assets/95fe4f97-12f9-4245-aa97-8c4d8f6a9f22) **Multiline test with low infill density:** ![image](https://github.com/user-attachments/assets/320e3fcd-602e-43ae-b5a3-f9fee97bd161)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/OrcaSlicer#1281