Files
solvecraft/examples/chebyshev-motion-trace.json
takuto-NA 63d81cc096 Motion Trace: regenerate kept curves only when inputs change (#5248) (#5270)
* claim: #5248

* feat(motion): regenerate kept traces when source inputs change

* feat(motion): add a parametric Chebyshev trace demo

* perf(joints): reuse face measurements across motion samples

Face-centred joint origins called Body::faces on every solve, bypassing the
reference-mesh reuse used for rigid placements. Share face measurements with
unchanged model geometry at the existing tolerance, while guarding replaced
bodies and applying offsets after the cached measurements.

Cover repeated named/nearest-face solves, shape edits, retained snapshots and
direct replacement with deterministic work counts. The 145-point Chebyshev
dimension and range updates retain identical sampled and interpolated curves.

* perf(naming): skip preparation for cached face names

Both face lookup paths spawned fresh workers over every body even when their name cells were ready. Prepare only cold cells, with direct handling of zero or one cold body. Preserve the existing parallel naming path and all face selection semantics.

Deterministic regressions cover repeated picked and unique-name lookups, misses, partial warmup, renames, snapshots and moved copies. Release Chebyshev medians fall from 859 to 50 ms for a dimension edit and 871 to 34 ms for a sweep edit; all 145 samples and interpolated points remain identical. Full cargo xtask ci passes with RUST_TEST_THREADS=4 and the existing local Windows validation overlays.
2026-10-23 13:42:36 +00:00

57 lines
6.2 KiB
JSON

{
"commands": [
{"command":"file.new","params":{"name":"Chebyshev lambda motion trace"}},
{"command":"parameters.add","params":{"name":"unit_length","expression":"20 mm","favorite":true,"comment":"Chebyshev scale: crank 1, ground 2, rocker 2.5, coupler 2.5 + 2.5"}},
{"command":"parameters.add","params":{"name":"crank_length","expression":"unit_length"}},
{"command":"parameters.add","params":{"name":"ground_length","expression":"2 * unit_length"}},
{"command":"parameters.add","params":{"name":"coupler_length","expression":"2.5 * unit_length","favorite":true,"comment":"Distance between the coupler pins; try 55 mm to reshape the trajectory"}},
{"command":"parameters.add","params":{"name":"rocker_length","expression":"2.5 * unit_length"}},
{"command":"parameters.add","params":{"name":"trace_sweep","expression":"360 deg","favorite":true,"comment":"Retained trace range; try 180 deg"}},
{"command":"parameters.add","params":{"name":"pivot_span","expression":"sqrt(ground_length^2 + crank_length^2)"}},
{"command":"parameters.add","params":{"name":"circle_along","expression":"(coupler_length^2 - rocker_length^2 + pivot_span^2) / (2 * pivot_span)"}},
{"command":"parameters.add","params":{"name":"circle_height","expression":"sqrt(coupler_length^2 - circle_along^2)"}},
{"command":"parameters.add","params":{"name":"coupler_q_x","expression":"(circle_along * ground_length + circle_height * crank_length) / pivot_span"}},
{"command":"parameters.add","params":{"name":"coupler_q_y","expression":"crank_length + (circle_height * ground_length - circle_along * crank_length) / pivot_span"}},
{"command":"parameters.add","params":{"name":"coupler_angle","expression":"atan2(coupler_q_y - crank_length, coupler_q_x)","unit":"deg"}},
{"command":"parameters.add","params":{"name":"rocker_angle","expression":"atan2(coupler_q_y, coupler_q_x - ground_length)","unit":"deg"}},
{"command":"component.create","params":{"name":"Ground"}},
{"command":"solid.box","params":{"corner":[-7,-8,-6],"length":"ground_length + 14 mm","width":16,"height":4,"body_name":"Base"}},
{"command":"solid.cylinder","params":{"base":[0,0,-2],"radius":4,"height":12,"body_name":"Fixed crank pivot"}},
{"command":"solid.cylinder","params":{"base":["ground_length",0,-2],"radius":4,"height":12,"body_name":"Fixed rocker pivot"}},
{"command":"appearance.assign","params":{"components":["Ground"],"color":"#526477"}},
{"command":"component.activate","params":{"component":"root"}},
{"command":"component.create","params":{"name":"Crank"}},
{"command":"solid.box","params":{"corner":[0,-3,0],"length":"crank_length","width":6,"height":3,"body_name":"Crank bar"}},
{"command":"solid.move","params":{"bodies":["Crank bar"],"axis":[0,0,1],"angle":"90 deg"}},
{"command":"solid.cylinder","params":{"base":[0,0,3],"radius":3,"height":7,"body_name":"Crank bearing"}},
{"command":"solid.cylinder","params":{"base":[0,"crank_length",3],"radius":3,"height":7,"body_name":"Crank pin"}},
{"command":"appearance.assign","params":{"components":["Crank"],"color":"#328BD3"}},
{"command":"component.activate","params":{"component":"root"}},
{"command":"component.create","params":{"name":"Coupler"}},
{"command":"solid.box","params":{"corner":[0,-2.5,3],"length":"2 * coupler_length","width":5,"height":3,"body_name":"Extended coupler"}},
{"command":"solid.move","params":{"bodies":["Extended coupler"],"axis":[0,0,1],"angle":"coupler_angle","translate":[0,"crank_length",0]}},
{"command":"solid.cylinder","params":{"base":[0,"crank_length",6],"radius":2.5,"height":4,"body_name":"Coupler crank pin"}},
{"command":"solid.cylinder","params":{"base":["coupler_q_x","coupler_q_y",6],"radius":2.5,"height":4,"body_name":"Coupler rocker pin"}},
{"command":"solid.cylinder","params":{"base":["2 * coupler_q_x","2 * coupler_q_y - crank_length",6],"radius":2,"height":4,"body_name":"Tracer tip"}},
{"command":"appearance.assign","params":{"components":["Coupler"],"color":"#E3AB4B"}},
{"command":"appearance.assign","params":{"bodies":["Tracer tip"],"color":"#D95360"}},
{"command":"component.activate","params":{"component":"root"}},
{"command":"component.create","params":{"name":"Rocker"}},
{"command":"solid.box","params":{"corner":[0,-3,0],"length":"rocker_length","width":6,"height":3,"body_name":"Rocker bar"}},
{"command":"solid.move","params":{"bodies":["Rocker bar"],"axis":[0,0,1],"angle":"rocker_angle","translate":["ground_length",0,0]}},
{"command":"solid.cylinder","params":{"base":["ground_length",0,3],"radius":3,"height":7,"body_name":"Rocker bearing"}},
{"command":"solid.cylinder","params":{"base":["coupler_q_x","coupler_q_y",3],"radius":3,"height":7,"body_name":"Rocker pin"}},
{"command":"appearance.assign","params":{"components":["Rocker"],"color":"#3EA69B"}},
{"command":"component.activate","params":{"component":"root"}},
{"command":"occurrence.ground","params":{"occurrence":"Ground:1","grounded":true}},
{"command":"joint.create","params":{"name":"Crank drive","type":"revolute","flip":true,"a":{"occurrence":"Ground","face":[0,0,10],"x":[1,0,0]},"b":{"occurrence":"Crank","face":[0,0,10],"x":[1,0,0]}}},
{"command":"joint.create","params":{"name":"Crank to coupler","type":"revolute","flip":true,"a":{"occurrence":"Crank","face":[0,20,10],"x":[1,0,0]},"b":{"occurrence":"Coupler","face":[0,20,10],"x":[1,0,0]}}},
{"command":"joint.create","params":{"name":"Coupler to rocker","type":"revolute","flip":true,"a":{"occurrence":"Coupler","face":[40,50,10],"x":[1,0,0]},"b":{"occurrence":"Rocker","face":[40,50,10],"x":[1,0,0]}}},
{"command":"joint.create","params":{"name":"Rocker pivot","type":"revolute","flip":true,"a":{"occurrence":"Ground","face":[40,0,10],"x":[1,0,0]},"b":{"occurrence":"Rocker","face":[40,0,10],"x":[1,0,0]}}},
{"command":"motion.study","params":{"name":"Chebyshev cycle","steps":72,"keys":[{"joint":"Crank drive","points":[[0,0],[72,360]]}]}},
{"command":"motion.trace.keep","params":{"name":"Chebyshev trajectory","occurrence":"Coupler","point":[80,80,10],"point_ref":{"occurrence":"Coupler","face":[80,80,10]},"joint":"Crank drive","from":0,"to":"trace_sweep","samples":145}},
{"command":"component.activate","params":{"component":"root"}}
]
}