@@ -34,8 +34,15 @@ CIRCLE_SEGMENTS = 12
3434# Chord length = 2 * radius * CIRCLE_CHORD_SIN_FACTOR.
3535CIRCLE_CHORD_SIN_FACTOR = Math .sin (Math .PI / CIRCLE_SEGMENTS)
3636
37+ # Number of layers by which each twig overlaps its parent branch.
38+ # Twigs start this many layers below the branch endpoint so that branch and twig
39+ # material are printed at the same Z level, physically bonding the joint.
40+ TWIG_OVERLAP_LAYERS = 1
41+
3742module .exports =
3843
44+ TWIG_OVERLAP_LAYERS : TWIG_OVERLAP_LAYERS
45+
3946 # Return the interpolated face Z at (x, y) by searching all region faces.
4047 # Returns null if the point lies outside every face's 2D XY projection.
4148 getFaceZAtPoint : (x , y , faces ) ->
@@ -146,18 +153,35 @@ module.exports =
146153
147154 cx = 0
148155 cy = 0
149- maxZ = - Infinity
150156
151157 for tip in clusterTips
152158
153159 cx += tip .x
154160 cy += tip .y
155- maxZ = Math .max (maxZ, tip .z )
156161
157162 cx /= clusterTips .length
158163 cy /= clusterTips .length
159164
160- branchNodes .push ({ x : cx, y : cy, z : maxZ, tips : clusterTips })
165+ # Compute the branch node Z from the 45-degree constraint applied to each tip.
166+ # nodeZ = min(tip.z - tdist) guarantees every twig rises at ≤ 45 degrees from
167+ # the branch endpoint to its contact tip, eliminating orphaned twig segments.
168+ nodeZ = Infinity
169+
170+ for tip in clusterTips
171+
172+ tdx = tip .x - cx
173+ tdy = tip .y - cy
174+ tdist = Math .sqrt (tdx * tdx + tdy * tdy)
175+ nodeZ = Math .min (nodeZ, tip .z - tdist)
176+
177+ nodeZ = Math .max (nodeZ, buildPlateZ + layerHeight)
178+
179+ # Round to 0.1 µm to avoid floating-point edge cases in the twig-emission
180+ # condition (node.z < tip.z - layerHeight) when large absolute coordinates
181+ # accumulate tiny rounding errors that differ from small-coordinate equivalents.
182+ nodeZ = Math .round (nodeZ * 10000 ) / 10000
183+
184+ branchNodes .push ({ x : cx, y : cy, z : nodeZ, tips : clusterTips })
161185
162186 # Compute branch root heights: where each branch diverges from the shared trunk.
163187 # The ideal root height is determined by the 45° angle constraint
@@ -195,6 +219,13 @@ module.exports =
195219 node = branchNodes[nodeIdx]
196220 branchRootZ = branchRootZs[nodeIdx]
197221
222+ # Guarantee the branch spans at least one printable layer.
223+ # When nodeZ was clamped to buildPlateZ + layerHeight and branchRootZ was also
224+ # clamped to the same value, the branch segment collapses to zero height and
225+ # never intersects any layer Z plane. Nudging node.z up by layerHeight ensures
226+ # a non-zero segment without changing the already-computed branchRootZ.
227+ node .z = Math .max (node .z , Math .round ((branchRootZ + layerHeight) * 10000 ) / 10000 )
228+
198229 # Branch segment: angled from trunk toward the branch node.
199230 segments .push ({
200231 x1 : trunkX, y1 : trunkY, z1 : branchRootZ
@@ -203,19 +234,18 @@ module.exports =
203234 })
204235
205236 # Twig segments: fine sub-branches from cluster node to individual contact tips.
206- for tip in node .tips
237+ # Twigs start TWIG_OVERLAP_LAYERS below the branch endpoint so that both the
238+ # branch (nearing its end) and the twig (at its root) are printed at the same Z,
239+ # creating a physical bond that strengthens the twig/branch joint.
240+ # Floor is branchRootZ (not buildPlateZ) so twigs never start below the branch.
241+ twigStartZ = Math .max (node .z - TWIG_OVERLAP_LAYERS * layerHeight, branchRootZ)
207242
208- tdx = tip .x - node .x
209- tdy = tip .y - node .y
210- tdist = Math .sqrt (tdx * tdx + tdy * tdy)
211-
212- twigRootZ = tip .z - tdist
213- twigRootZ = Math .max (twigRootZ, branchRootZ + layerHeight)
243+ for tip in node .tips
214244
215- if twigRootZ < tip .z - layerHeight
245+ if twigStartZ < tip .z - layerHeight
216246
217247 segments .push ({
218- x1 : node .x , y1 : node .y , z1 : twigRootZ
248+ x1 : node .x , y1 : node .y , z1 : twigStartZ
219249 x2 : tip .x , y2 : tip .y , z2 : tip .z
220250 type : ' twig'
221251 })
0 commit comments