git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitb6c986abe9e48cc5f1cded64a7805c9fd198d547
parent979e8da177 37627adcd6
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-30 12:44
Merge main into claude/param-descriptions

Takes main's Relax and Remesh templates (the Remesh is a subnet now) and
describes their new and changed parameters, and the Repeat, Seed, Split
Edges, Collapse Edges, Flip Edges and Project templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md                 | 111 ++++++++++++-
 nodes/collapse_edges.json |  23 +++
 nodes/flip_edges.json     |  23 +++
 nodes/project.json        |  20 +++
 nodes/relax.json          |  16 +-
 nodes/remesh.json         | 290 +++++++++++++++++++++++++++++----
 nodes/repeat.json         |  64 ++++++++
 nodes/seed.json           |   7 +
 nodes/split_edges.json    |  23 +++
 src/app.rs                |  42 ++++-
 src/detail.rs             |  25 ++-
 src/geometry.rs           | 396 ++++++++++++++++++++++++++++++++++++++--------
 src/main.rs               | 229 ++++++++++++++++++++++++++-
 src/remesh.rs             | 128 ++++++++++++++-
 14 files changed, 1291 insertions(+), 106 deletions(-)

diff --cc nodes/collapse_edges.json
index 0000000,d8d3d12..3461baf
mode 000000,100644..100644
--- a/nodes/collapse_edges.json
+++ b/nodes/collapse_edges.json
@@@ -1,0 -1,21 +1,23 @@@
+ {
+  "name": "Collapse Edges",
+  "type": "collapse_edges",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+   {
+    "name": "Input",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose mesh is coarsened. The surviving point of each collapsed edge keeps its values and joins the other end's groups."
+   },
+   {
+    "name": "Target Length",
+    "type": "slider",
+    "default": "0.10",
+    "min": 0.001,
+    "max": 1.0,
 -   "step": 0.005
++   "step": 0.005,
++   "description": "Edge length, in world units, the pass steers toward: edges shorter than 4/5 of it are collapsed, unless that would make an edge longer than 4/3 of it."
+   }
+  ]
+ }
diff --cc nodes/flip_edges.json
index 0000000,3318a65..55d7575
mode 000000,100644..100644
--- a/nodes/flip_edges.json
+++ b/nodes/flip_edges.json
@@@ -1,0 -1,21 +1,23 @@@
+ {
+  "name": "Flip Edges",
+  "type": "flip_edges",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+   {
+    "name": "Input",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose mesh has its edges flipped to even out the triangles."
+   },
+   {
+    "name": "Target Length",
+    "type": "slider",
+    "default": "0.10",
+    "min": 0.001,
+    "max": 1.0,
 -   "step": 0.005
++   "step": 0.005,
++   "description": "Edge length, in world units, of the remesh this pass belongs to: a flip is refused if its new edge is longer than 4/3 of it, which the next split would cut."
+   }
+  ]
+ }
diff --cc nodes/project.json
index 0000000,fe418a1..915b96a
mode 000000,100644..100644
--- a/nodes/project.json
+++ b/nodes/project.json
@@@ -1,0 -1,18 +1,20 @@@
+ {
+  "name": "Project",
+  "type": "project",
+  "inputs": 2,
+  "outputs": 1,
+  "params": [
+   {
+    "name": "Input",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose points are moved onto the Surface."
+   },
+   {
+    "name": "Surface",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose surface each point is moved to, at the nearest place on it. Unconnected, the input passes through unchanged."
+   }
+  ]
+ }
diff --cc nodes/relax.json
index 3209b35,5702451..def34bb
--- a/nodes/relax.json
+++ b/nodes/relax.json
@@@ -12,9 -11,8 +12,9 @@@
      },
      {
        "name": "Mode",
-       "type": "choice:Springs,Repel",
+       "type": "choice:Springs,Repel,Tangential",
 -      "default": "Springs"
 +      "default": "Springs",
-       "description": "Springs pulls edge lengths back toward those of the Rest geometry. Repel pushes points apart until spheres of Radius around them stop overlapping."
++      "description": "Springs pulls edge lengths back toward those of the Rest geometry. Repel pushes points apart until spheres of Radius stop overlapping. Tangential evens out triangles along the surface."
      },
      {
        "name": "Rest",
@@@ -46,8 -41,7 +46,8 @@@
        "default": "8",
        "min": 0.0,
        "max": 50.0,
 -      "step": 1.0
 +      "step": 1.0,
-       "description": "Number of relaxation passes. More passes settle further. In Repel mode 0 turns the node off; Springs always runs at least one."
++      "description": "Number of relaxation passes. More passes settle further. In Repel and Tangential modes 0 turns the node off; Springs always runs at least one."
      },
      {
        "name": "Radius",
@@@ -63,8 -56,16 +63,18 @@@
        "name": "In 3D Space",
        "type": "toggle",
        "default": "false",
 -      "show_when": "Mode == Repel"
 +      "show_when": "Mode == Repel",
 +      "description": "When off, points slide only along the surface (in their tangent plane) as they repel. When on, they may move freely in 3D and leave the surface."
+     },
+     {
+       "name": "Amount",
+       "type": "slider",
+       "default": "0.50",
+       "min": 0.0,
+       "max": 1.0,
+       "step": 0.01,
 -      "show_when": "Mode == Tangential"
++      "show_when": "Mode == Tangential",
++      "description": "How far each point moves toward the centroid of its neighbours per pass, 0 to 1, less the part along its normal, so triangles even out while the shape stays. 0 turns it off."
      }
    ]
  }
diff --cc nodes/remesh.json
index 5e0c0b3,6bea3ea..6fda7cf
--- a/nodes/remesh.json
+++ b/nodes/remesh.json
@@@ -25,8 -23,7 +25,8 @@@
     "default": "3",
     "min": 1.0,
     "max": 20.0,
 -   "step": 1.0
 +   "step": 1.0,
-    "description": "How many times the split, collapse, flip and relax passes repeat. More rounds settle the mesh closer to the target; it stops early once nothing changes."
++   "description": "How many times the split, collapse, flip, relax and project passes repeat. More rounds settle the mesh closer to the target; it stops early once a round changes nothing."
    },
    {
     "name": "Relax",
@@@ -34,32 -31,7 +34,8 @@@
     "default": "0.50",
     "min": 0.0,
     "max": 1.0,
 -   "step": 0.01
 +   "step": 0.01,
-    "description": "Strength of the tangential smoothing that slides points to even out triangles without moving the surface. 0 turns it off; 1 is full strength."
-   },
-   {
-    "name": "Split",
-    "type": "choice:true,false",
-    "default": "true",
-    "description": "Whether edges longer than 4/3 of the Target Length are split at their midpoint, adding points where the mesh is too coarse."
-   },
-   {
-    "name": "Collapse",
-    "type": "choice:true,false",
-    "default": "true",
-    "description": "Whether edges shorter than 4/5 of the Target Length are collapsed, removing points where the mesh is too dense. The surviving point keeps its values."
-   },
-   {
-    "name": "Flip",
-    "type": "choice:true,false",
-    "default": "true",
-    "description": "Whether edges are flipped to bring points closer to six neighbours each, which evens out the shape of the triangles."
-   },
-   {
-    "name": "Project",
-    "type": "choice:true,false",
-    "default": "true",
-    "description": "Whether relaxed points are pulled back onto the input surface after each round, so repeated remeshing does not shrink or drift the shape."
++   "description": "How far each point slides toward its neighbours' centroid along the surface in every round, evening out the triangles. 0 turns the relaxation off; 1 is full strength."
    },
    {
     "name": "Transfer",
@@@ -71,8 -42,7 +47,8 @@@
     "name": "From",
     "type": "node",
     "default": "",
 -   "show_when": "Transfer == true"
 +   "show_when": "Transfer == true",
-    "description": "The node whose point attributes and groups are copied onto the remeshed points. Empty uses this node's own input as it was before the remesh."
++   "description": "The node whose point attributes and groups are copied onto the remeshed points. Empty uses the remesh's own input, as it was before remeshing."
    },
    {
     "name": "Attributes",
@@@ -102,8 -69,265 +78,266 @@@
     "min": 0.0,
     "max": 10.0,
     "step": 0.01,
 -   "show_when": "Transfer == true"
 +   "show_when": "Transfer == true",
 +   "description": "Only points within this many world units of a source point receive values; farther points keep their own. 0 means no limit."
    }
+  ],
+  "children": [
+   {
+    "name": "input1",
+    "type": "input",
+    "params": [],
+    "position": [
+     0.0,
+     0.0
+    ],
+    "geometry_visible": false
+   },
+   {
+    "name": "repeat1",
+    "type": "repeat",
+    "position": [
+     0.0,
+     1.0
+    ],
+    "params": [
+     {
+      "name": "Input",
+      "default": "input1"
+     },
+     {
+      "name": "Iterations",
+      "default": "chi(\"../Iterations\")",
+      "expr": true
+     },
+     {
+      "name": "Stop When Unchanged",
+      "default": "true"
+     }
+    ],
+    "children": [
+     {
+      "name": "input1",
+      "type": "input",
+      "params": [],
+      "position": [
+       0.0,
+       0.0
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "seed1",
+      "type": "seed",
+      "params": [],
+      "position": [
+       2.0,
+       0.0
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "split1",
+      "type": "split_edges",
+      "position": [
+       0.0,
+       1.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "input1"
+       },
+       {
+        "name": "Target Length",
+        "default": "chf(\"../../Target Length\")",
+        "expr": true
+       }
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "collapse1",
+      "type": "collapse_edges",
+      "position": [
+       0.0,
+       2.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "split1"
+       },
+       {
+        "name": "Target Length",
+        "default": "chf(\"../../Target Length\")",
+        "expr": true
+       }
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "flip1",
+      "type": "flip_edges",
+      "position": [
+       0.0,
+       3.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "collapse1"
+       },
+       {
+        "name": "Target Length",
+        "default": "chf(\"../../Target Length\")",
+        "expr": true
+       }
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "relax1",
+      "type": "relax",
+      "position": [
+       0.0,
+       4.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "flip1"
+       },
+       {
+        "name": "Mode",
+        "default": "Tangential"
+       },
+       {
+        "name": "Iterations",
+        "default": "1"
+       },
+       {
+        "name": "Amount",
+        "default": "chf(\"../../Relax\")",
+        "expr": true
+       }
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "project1",
+      "type": "project",
+      "position": [
+       1.0,
+       5.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "relax1"
+       },
+       {
+        "name": "Surface",
+        "default": "seed1"
+       }
+      ],
+      "geometry_visible": false
+     },
+     {
+      "name": "output1",
+      "type": "output",
+      "position": [
+       1.0,
+       6.0
+      ],
+      "params": [
+       {
+        "name": "Input",
+        "default": "project1"
+       }
+      ],
+      "geometry_visible": false
+     }
+    ],
+    "geometry_visible": false
+   },
+   {
+    "name": "transfer1",
+    "type": "transfer",
+    "position": [
+     1.0,
+     2.0
+    ],
+    "params": [
+     {
+      "name": "Input",
+      "default": "repeat1"
+     },
+     {
+      "name": "From",
+      "default": "if(chs(\"../From\"), chs(\"../From\"), \"input1\")",
+      "expr": true
+     },
+     {
+      "name": "Attributes",
+      "default": "chs(\"../Attributes\")",
+      "expr": true
+     },
+     {
+      "name": "Transfer Groups",
+      "default": "chb(\"../Transfer Groups\")",
+      "expr": true
+     },
+     {
+      "name": "Groups",
+      "default": "chs(\"../Groups\")",
+      "expr": true
+     },
+     {
+      "name": "Maximum Distance",
+      "default": "chf(\"../Maximum Distance\")",
+      "expr": true
+     }
+    ],
+    "geometry_visible": false
+   },
+   {
+    "name": "result1",
+    "type": "switch",
+    "position": [
+     0.0,
+     3.0
+    ],
+    "params": [
+     {
+      "name": "Input",
+      "default": "repeat1"
+     },
+     {
+      "name": "Input 2",
+      "default": "transfer1"
+     },
+     {
+      "name": "Index",
+      "default": "chi(\"../Transfer\")",
+      "expr": true
+     }
+    ]
+   },
+   {
+    "name": "output1",
+    "type": "output",
+    "position": [
+     0.0,
+     4.0
+    ],
+    "params": [
+     {
+      "name": "Input",
+      "default": "result1"
+     }
+    ],
+    "geometry_visible": false
+   }
   ]
  }
diff --cc nodes/repeat.json
index 0000000,2309790..31d03cb
mode 000000,100644..100644
--- a/nodes/repeat.json
+++ b/nodes/repeat.json
@@@ -1,0 -1,61 +1,64 @@@
+ {
+  "name": "Repeat",
+  "type": "repeat",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+   {
+    "name": "Input",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose geometry the first pass of the loop reads, through the input node inside."
+   },
+   {
+    "name": "Iterations",
+    "type": "spinbox",
+    "default": "4",
+    "min": 0.0,
+    "max": 100.0,
 -   "step": 1.0
++   "step": 1.0,
++   "description": "How many times the chain inside runs, each pass reading the last one's result. 0 passes the input through; at most 1000."
+   },
+   {
+    "name": "Stop When Unchanged",
+    "type": "toggle",
 -   "default": "false"
++   "default": "false",
++   "description": "Ends the loop early at a pass that changes nothing, since every later pass would find the same."
+   }
+  ],
+  "children": [
+   {
+    "name": "input1",
+    "type": "input",
+    "params": [],
+    "position": [
+     0.0,
+     0.0
+    ]
+   },
+   {
+    "name": "seed1",
+    "type": "seed",
+    "params": [],
+    "position": [
+     2.0,
+     0.0
+    ],
+    "geometry_visible": false
+   },
+   {
+    "name": "output1",
+    "type": "output",
+    "params": [
+     {
+      "name": "Input",
+      "default": "input1"
+     }
+    ],
+    "position": [
+     0.0,
+     2.0
+    ]
+   }
+  ]
+ }
diff --cc nodes/split_edges.json
index 0000000,2db7322..f5e11cb
mode 000000,100644..100644
--- a/nodes/split_edges.json
+++ b/nodes/split_edges.json
@@@ -1,0 -1,21 +1,23 @@@
+ {
+  "name": "Split Edges",
+  "type": "split_edges",
+  "inputs": 1,
+  "outputs": 1,
+  "params": [
+   {
+    "name": "Input",
+    "type": "node",
 -   "default": ""
++   "default": "",
++   "description": "The node whose mesh is refined. Point values ride to the new midpoints by interpolation."
+   },
+   {
+    "name": "Target Length",
+    "type": "slider",
+    "default": "0.10",
+    "min": 0.001,
+    "max": 1.0,
 -   "step": 0.005
++   "step": 0.005,
++   "description": "Edge length, in world units, the pass steers toward: every edge longer than 4/3 of it is split at its midpoint. Smaller means denser."
+   }
+  ]
+ }