graphic design tool
git clone https://git.lucas.co/cce-designer.git
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."
+ }
+ ]
+ }