@@ -10,6 +10,7 @@ public class Fillet extends Primitive {
1010
1111 double w , h ;
1212 private static final double numArcPoints = 12 ;
13+ private static double filletOfset =0.001 ;
1314 /** The properties. */
1415 private final PropertyStorage properties = new PropertyStorage ();
1516
@@ -30,46 +31,46 @@ public Fillet(double w, double h) {
3031 }
3132
3233 public static CSG corner (double rad , double angle ) throws ColinearPointsException {
33- // --- Build the concave quarter-circle fillet profile ---
34- //
35- // Shape (in XY plane, swept around Z):
36- //
37- // (0,rad) ──arc─── (rad,rad)
38- // │ center │
39- // │ at (rad,rad) │
40- // (0,0) ────────── (rad,0)
41- //
42- // The arc is concave (bites inward), matching what the
43- // old Fillet+Sphere difference was carving out.
44-
45-
46- List < Vector3d > pts = new ArrayList <>();
47-
48- // Corner at origin
49- pts . add ( new Vector3d ( 0 , 0 , 0 ));
50-
51- // Straight edge along +X
52- pts . add ( new Vector3d ( rad , 0 , 0 ));
53-
54- // Concave quarter-circle arc: center= (rad,rad), radius= rad
55- // sweeps from 270° → 180° (i.e. (rad,0) → (0,rad))
56- for ( int i = 0 ; i <= numArcPoints ; i ++) {
57- double a = Math . toRadians ( 270.0 - 90.0 * i / numArcPoints );
58- double x = rad + rad * Math .cos (a );
59- double y = rad + rad * Math . sin ( a );
60- pts . add ( new Vector3d ( x , y , 0 ));
61- }
62-
63- // Straight edge back down to origin closes the polygon
64- // (fromPoints auto-closes, so no need to re-add (0,0,0))
65-
66- Polygon profile = Polygon . fromPoints ( pts );
67-
68- // --- Sweep the profile around the Z axis ---
69- // radius =0 → profile is already positioned relative to the axis
70- // z=0 → no axial offset
71- // steps=32 → match arc resolution for a smooth result
72- return Extrude . sweep ( profile , angle / numArcPoints , 0 , 0 , ( int ) numArcPoints ) .roty (90 );
34+ // --- Build the concave quarter-circle fillet profile ---
35+ //
36+ // Shape (in XY plane, swept around Z):
37+ //
38+ // (0,rad) ──arc─── (rad,rad)
39+ // │ center │
40+ // │ at (rad,rad) │
41+ // (0,0) ────────── (rad,0)
42+ //
43+ // The arc is concave (bites inward), matching what the
44+ // old Fillet+Sphere difference was carving out.
45+
46+ List < Vector3d > pts = new ArrayList <>();
47+
48+ // Corner at origin
49+ pts . add ( new Vector3d ( 0 , - filletOfset , 0 ));
50+
51+ // Straight edge along +X
52+ pts . add ( new Vector3d ( rad , - filletOfset , 0 ));
53+
54+ // Concave quarter-circle arc: center=(rad,rad), radius=rad
55+ // sweeps from 270° → 180° (i.e. (rad,0) → (0, rad))
56+ for ( int i = 0 ; i <= numArcPoints ; i ++) {
57+ double a = Math . toRadians ( 270.0 - 90.0 * i / numArcPoints );
58+ double x = rad + rad * Math . cos ( a );
59+ double y = rad + rad * Math .sin (a );
60+ pts . add ( new Vector3d ( x , y , 0 ) );
61+ }
62+
63+ // Straight edge back down to origin closes the polygon
64+ // (fromPoints auto-closes, so no need to re-add (0,0,0))
65+
66+ Polygon profile = Polygon . fromPoints ( pts );
67+
68+ // --- Sweep the profile around the Z axis ---
69+ // radius=0 → profile is already positioned relative to the axis
70+ // z =0 → no axial offset
71+ // steps=32 → match arc resolution for a smooth result
72+ return Extrude . sweep ( profile , angle / numArcPoints , 0 , 0 , ( int ) numArcPoints )
73+ .roty (90 );
7374 }
7475
7576 public static CSG outerFillet (CSG base , double rad ) throws ColinearPointsException {
@@ -79,85 +80,106 @@ public static CSG outerFillet(CSG base, double rad) throws ColinearPointsExcepti
7980
8081 public static CSG outerFillet (List <Polygon > polys , double rad ) {
8182
82- ArrayList <CSG > parts = new ArrayList <>();
83-
84- for (Polygon p : polys ) {
85- boolean isHole = false ;
86- try {
87- isHole = !Extrude .isCCW (p );
88- } catch (ColinearPointsException e ) {
89- e .printStackTrace ();
90- }
91- System .err .println ("Polygon filler " + (isHole ? "hole" : "outside" ));
92-
93- int size = p .getVertices ().size ();
94- for (int i = 0 ; i < size ; i ++) {
95- int next = (i + 1 ) % size ;
96- int nextNext = (next + 1 ) % size ;
97-
98- Vector3d position0 = p .getVertices ().get (i ).pos ;
99- Vector3d position1 = p .getVertices ().get (next ).pos ; // corner vertex
100- Vector3d position2 = p .getVertices ().get (nextNext ).pos ;
101-
102- Vector3d seg1 = position0 .minus (position1 ); // incoming edge
103- Vector3d seg2 = position2 .minus (position1 ); // outgoing edge
104-
105- double len = seg1 .magnitude ();
106- double theta = Math .toDegrees (seg1 .angle (seg2 ));
107- double crossZ = seg1 .cross (seg2 ).z ;
108-
109- boolean isConvex = isHole ? (crossZ > 0 ) : (crossZ < 0 );
110- boolean isReflex = !isConvex ;
111-
112- double filletAngle = 180.0 - theta ;
113-
114- // --- Absolute rotation for the corner (perpendicular to seg1) ---
115- double cornerAngleAbs = Math .toDegrees (seg1 .angle (Vector3d .Y_ONE ));
116- if (seg1 .x < 0 )
117- cornerAngleAbs = 360 - cornerAngleAbs ;
118- if (isHole ) cornerAngleAbs += 180 ;
119- if (isReflex ) cornerAngleAbs += 180 ;
120-
121- // --- Absolute rotation for the edge fillet (parallel to seg1) ---
122- double edgeAngleAbs = Math .toDegrees (seg1 .angle (Vector3d .Y_ONE ));
123- if (seg1 .x < 0 )
124- edgeAngleAbs = 360 - edgeAngleAbs ;
125- if (isHole ) {
126- edgeAngleAbs += 180 ;
127-
128- }
129- System .err .println (
130- " vertex=" + next +
131- " theta=" + String .format ("%.2f" , theta ) +
132- " filletAngle=" + String .format ("%.2f" , filletAngle ) +
133- " cornerAbs=" + String .format ("%.2f" , cornerAngleAbs ) +
134- " edgeAbs=" + String .format ("%.2f" , edgeAngleAbs ) +
135- " convex=" + isConvex +
136- " reflex=" + isReflex
137- );
138-
139- // --- Straight edge fillet ---
140- // Runs from position1 toward position0 (along seg1 direction)
141- // toYMax() aligns the fillet profile to sit flush on the face
142- CSG edgeFillet = new Fillet (rad , len ).toCSG ().toYMax ()
143- .rotz (edgeAngleAbs )
144- .move (position1 );
145- //parts.add(edgeFillet);
146-
147- // --- Corner fillet ---
148- if (filletAngle > 0.01 && filletAngle < 359.99 ) {
149- try {
150- parts .add (corner (rad , filletAngle )
151- .rotz (cornerAngleAbs )
152- .move (position1 ));
153- } catch (ColinearPointsException e ) {
154- e .printStackTrace ();
155- }
156- }
157- }
158- }
159- return CSG .unionAll (parts );
83+ boolean outer = true ;
84+
85+ ArrayList <CSG > parts = new ArrayList <>();
86+
87+ for (Polygon p : polys ) {
88+ boolean isHole = false ;
89+ try {
90+ isHole = !Extrude .isCCW (p );
91+ } catch (ColinearPointsException e ) {
92+ e .printStackTrace ();
93+ }
94+ if (!outer )
95+ isHole =!isHole ;
96+ System .err .println ("Polygon filler " + (isHole ? "hole" : "outside" ));
97+
98+ int size = p .getVertices ().size ();
99+ for (int i = 0 ; i < size ; i ++) {
100+ int next = (i + 1 ) % size ;
101+ int nextNext = (next + 1 ) % size ;
102+
103+ Vector3d position0 = p .getVertices ().get (i ).pos ;
104+ Vector3d position1 = p .getVertices ().get (next ).pos ; // corner vertex
105+ Vector3d position2 = p .getVertices ().get (nextNext ).pos ;
106+
107+ Vector3d seg1 = position0 .minus (position1 ); // incoming, pointing away from corner
108+ Vector3d seg2 = position2 .minus (position1 ); // outgoing, pointing away from corner
109+
110+ double len = seg1 .magnitude ();
111+ double theta = Math .toDegrees (seg1 .angle (seg2 )); // always 0–180
112+ double crossZ = seg1 .cross (seg2 ).z ;
113+
114+ // Convex vs reflex meaning depends on winding:
115+ // Outside (CCW): convex corners have crossZ < 0
116+ // Hole (CW): convex corners have crossZ > 0
117+ boolean isConvex = isHole ? (crossZ > 0 ) : (crossZ < 0 );
118+ boolean isReflex = !isConvex ;
119+
120+ // filletAngle = exterior sweep angle of the corner piece
121+ double filletAngle = 180.0 - theta ;
122+
123+
124+ // Base rotation: align to seg1 direction relative to Y axis
125+ double baseAngle = Math .toDegrees (seg1 .angle (Vector3d .Y_ONE ));
126+ if (seg1 .x < 0 )
127+ baseAngle = 360 - baseAngle ;
128+ double edgeAngleAbs = baseAngle ;
129+ if (isHole )
130+ edgeAngleAbs += 180 ;
131+ if (isReflex ) {
132+ filletAngle =180 -filletAngle ;
133+ if (filletAngle <90 )
134+ filletAngle =0 ;
135+ baseAngle +=90 ;
136+ }
137+ // -------------------------------------------------------
138+ // Edge fillet: always placed, runs along seg1 from position1
139+ // Holes face inward so flip 180°
140+ // -------------------------------------------------------
141+
142+
143+ CSG edgeFillet = new Fillet (rad , len ).toCSG ();
144+ if (isHole )
145+ edgeFillet = edgeFillet .toYMax ();
146+ else
147+ edgeFillet = edgeFillet .toYMin ().mirrorx ();
148+ if (!outer )
149+ edgeFillet = edgeFillet .mirrorx ();
150+ edgeFillet = edgeFillet .rotz (edgeAngleAbs ).move (position1 );
151+ parts .add (edgeFillet );
152+ double cornerAngleAbs = baseAngle ;
153+ if (!isHole && outer ) {
154+ cornerAngleAbs =cornerAngleAbs -filletAngle -90 ;
155+ }
156+ // -------------------------------------------------------
157+ // Corner fillet: only placed on REFLEX corners
158+ //
159+ // Outside reflex: edge fillets don't reach into the notch,
160+ // corner piece fills it, no extra rotation.
161+ //
162+ // Hole reflex: edge fillets each consume 90° at the corner,
163+ // so the corner piece must start 90° further
164+ // around to align with where they leave off.
165+ // -------------------------------------------------------
166+ if (filletAngle > 0.01 && filletAngle < 359.99 ) {
167+
168+ // Outside: no extra offset — logic is reversed (edge fillets
169+ // run away from the corner, so no 90° consumption occurs)
170+
171+
172+ try {
173+ parts .add (corner (rad , filletAngle ).rotz (cornerAngleAbs ).move (position1 ));
174+ } catch (ColinearPointsException e ) {
175+ e .printStackTrace ();
176+ }
177+ }
178+ }
179+ }
180+ return CSG .unionAll (parts );
160181 }
182+
161183 @ Override
162184 public CSG toCSG () {
163185 // --- 1. Build the fillet cross-section polygon in the XZ plane (Y = 0) ---
@@ -173,15 +195,17 @@ public CSG toCSG() {
173195 List <Vector3d > profilePoints = new ArrayList <>();
174196
175197 // Inner corner
176- profilePoints .add (new Vector3d (0 , 0 , 0 ));
198+ profilePoints .add (new Vector3d (-filletOfset , 0 , 0 ));
199+ //profilePoints.add(new Vector3d(0, 0, 0));
177200
178201 // Concave quarter-circle arc
179202 for (int i = 0 ; i <= numArcPoints ; i ++) {
180203 double angle = Math .toRadians (270.0 - 90.0 * i / numArcPoints );
181204 double x = w + w * Math .cos (angle );
182205 double z = w + w * Math .sin (angle );
183- profilePoints .add (0 ,new Vector3d (x , 0 , z ));
206+ profilePoints .add (0 , new Vector3d (x , 0 , z ));
184207 }
208+ profilePoints .add (new Vector3d (-filletOfset , 0 , w ));
185209 // Last arc point lands exactly at (0, 0, w); polygon closes to (0,0,0)
186210 try {
187211 Polygon profile = Polygon .fromPoints (profilePoints );
0 commit comments