Kernel rig: assembly cases for mass properties, align, find limits and moving items between parts

- assembly.mass_properties: two boxes (2 g/cm3) in a rigid group: mass
  0.006 kg, centre (18.333,5,5), inertia diag(0.1, 1.0333, 1.0333) kg mm2
  by the parallel-axis theorem.
- occurrence.align: a block's end face onto a base's top face: a quarter
  turn about y, centre (70,20,20), box (65,15,10)-(75,25,30).
- joint.find_limits: a slider between two stops: min -20, max 10 mm.
- component.move_features, browser.reparent and
  component.move_sketches.to_parts: items moved into a turned and moved
  part keep their world place (volumes, centres and boxes by hand).

The mass case assigns a material it creates, so material.create and
material.assign leave EXEMPT. PATHS names the new command modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
jyviko
2026-11-02 17:34:24 -05:00
parent 21db090bab
commit c1f45cae2e
14 changed files with 832 additions and 2 deletions
-2
View File
@@ -94,8 +94,6 @@ inspect.zebra display analysis: shading
library.apply material and appearance libraries
library.export material and appearance libraries
library.inspect material and appearance libraries
material.assign materials and their properties
material.create materials and their properties
material.delete materials and their properties
material.edit materials and their properties
material.inspect materials and their properties
+6
View File
@@ -13,17 +13,23 @@ crates/engine/src/cmd/motion_trace.rs
crates/engine/src/cmd/contact_sweep.rs
# Components and occurrences (their placement in the world is crates/doc/src/eval.rs: shared core).
crates/engine/src/cmd/component.rs
crates/engine/src/cmd/component_align.rs
crates/engine/src/cmd/component_clone.rs
crates/engine/src/cmd/component_clone_assembly.rs
crates/engine/src/cmd/component_drag.rs
crates/engine/src/cmd/component_extract.rs
crates/engine/src/cmd/component_link.rs
crates/engine/src/cmd/component_move_features.rs
crates/engine/src/cmd/component_placing.rs
crates/engine/src/cmd/component_properties.rs
crates/engine/src/cmd/component_reparent.rs
crates/engine/src/cmd/component_reparent_items.rs
crates/engine/src/cmd/component_sketches_to_parts.rs
crates/engine/src/cmd/component_transfer.rs
# Where occurrences are placed, shown and reported (the exploded view is shown only).
crates/engine/src/occurrences.rs
# Mass, centre of mass and inertia per occurrence and rigid group.
crates/engine/src/cmd/mass_properties.rs
# Interference and clearance between placed bodies.
crates/engine/src/cmd/clearance.rs
crates/engine/src/cmd/interference.rs
@@ -0,0 +1,52 @@
{
"sources": [],
"what": "browser.reparent of a part's placement and a loose body into a component placed turned and moved",
"source": "analytic",
"method": "Arm: box 40x10x10 from (0,-5,0), centre (20,0,5), V 4000, placed as occurrence 3 (ids count every component, occurrence and feature made); Cube: box 10x10x10 from (0,20,0) in the design, centre (5,25,5), V 1000; Holder placed a quarter turn about z and 100 mm along x. Both move into Holder and keep their world place: Holder's total is V 5000, centre ((4000*20 + 1000*5)/5000, (1000*25)/5000, 5) = (17,5,5), box (0,-5,0)-(40,30,10)",
"body_count": 2,
"volume_mm3": 5000.0,
"center_of_mass": [
17.0,
5.0,
5.0
],
"bbox": {
"min": [
0.0,
-5.0,
0.0
],
"max": [
40.0,
30.0,
10.0
]
},
"results": [
{
"step": 8,
"json": {
"total": {
"volume_mm3": 5000.0,
"center_of_mass": [
17.0,
5.0,
5.0
],
"bbox": {
"min": [
0.0,
-5.0,
0.0
],
"max": [
40.0,
30.0,
10.0
]
}
}
}
}
]
}
@@ -0,0 +1,105 @@
{
"commands": [
{
"command": "solid.box",
"params": {
"length": 40,
"width": 10,
"height": 10,
"corner": [
0,
-5,
0
],
"operation": "new",
"body_name": "ArmBox"
}
},
{
"command": "component.create",
"params": {
"name": "Arm",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"ArmBox"
],
"component": "Arm"
}
},
{
"command": "component.create",
"params": {
"name": "Holder",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "occurrence.move",
"params": {
"occurrence": "Holder:1",
"matrix": [
[
0,
-1,
0,
100
],
[
1,
0,
0,
0
],
[
0,
0,
1,
0
]
]
}
},
{
"command": "solid.box",
"params": {
"length": 10,
"width": 10,
"height": 10,
"corner": [
0,
20,
0
],
"operation": "new",
"body_name": "Cube"
}
},
{
"command": "browser.reparent",
"params": {
"occurrences": [
3
],
"bodies": [
"Cube"
],
"component": "Holder"
}
},
{
"command": "occurrence.properties",
"params": {
"occurrence": "Holder:1"
}
}
]
}
@@ -0,0 +1,52 @@
{
"sources": [],
"what": "a sketch and its extrude moved from the design into a component placed turned and moved",
"source": "analytic",
"method": "Holder placed a quarter turn about z and 100 mm along x; the rectangle (10,20)-(30,30) on XY extruded 5 mm: 20x10x5 = 1000. Moved into Holder, it keeps its world place (re-expressed in Holder's frame): Holder's total is the block at centre (20,25,2.5), box (10,20,0)-(30,30,5)",
"body_count": 1,
"volume_mm3": 1000.0,
"center_of_mass": [
20.0,
25.0,
2.5
],
"bbox": {
"min": [
10.0,
20.0,
0.0
],
"max": [
30.0,
30.0,
5.0
]
},
"results": [
{
"step": 8,
"json": {
"total": {
"volume_mm3": 1000.0,
"center_of_mass": [
20.0,
25.0,
2.5
],
"bbox": {
"min": [
10.0,
20.0,
0.0
],
"max": [
30.0,
30.0,
5.0
]
}
}
}
}
]
}
@@ -0,0 +1,85 @@
{
"commands": [
{
"command": "component.create",
"params": {
"name": "Holder",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "occurrence.move",
"params": {
"occurrence": "Holder:1",
"matrix": [
[
0,
-1,
0,
100
],
[
1,
0,
0,
0
],
[
0,
0,
1,
0
]
]
}
},
{
"command": "sketch.create",
"params": {
"plane": "XY"
}
},
{
"command": "sketch.rectangle.two_point",
"params": {
"p0": [
10,
20
],
"p1": [
30,
30
]
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"distance": 5,
"operation": "new"
}
},
{
"command": "component.move_features",
"params": {
"features": [
"Sketch1",
"Extrude1"
],
"component": "Holder"
}
},
{
"command": "occurrence.properties",
"params": {
"occurrence": "Holder:1"
}
}
]
}
@@ -0,0 +1,17 @@
{
"sources": [],
"what": "component.move_sketches.to_parts moves a sketch drawn on a part's top face into that part, and the pin built from it stays",
"source": "analytic",
"method": "Bracket: plate 40x30x10 placed 100 mm along x, top face z = 10; a circle r 5 sketched on that face from the design, extruded 8 mm up as a new body: pi*25*8 = 628.3185. The sketch is on an exactly attached face of Bracket's body, so it moves to Bracket (moved 1, stayed 0) and stays where it is on the face: the plate and the pin keep their volumes, 12000 + 628.3185",
"body_count": 2,
"volume_mm3": 12628.318531,
"results": [
{
"step": 9,
"json": {
"moved": 1,
"stayed": 0
}
}
]
}
@@ -0,0 +1,85 @@
{
"commands": [
{
"command": "solid.box",
"params": {
"length": 40,
"width": 30,
"height": 10,
"corner": [
0,
0,
0
],
"operation": "new",
"body_name": "Plate"
}
},
{
"command": "component.create",
"params": {
"name": "Bracket",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"Plate"
],
"component": "Bracket"
}
},
{
"command": "occurrence.move",
"params": {
"occurrence": "Bracket:1",
"translate": [
100,
0,
0
]
}
},
{
"command": "sketch.create",
"params": {
"plane": {
"face": [
120,
15,
10
]
}
}
},
{
"command": "sketch.circle.center",
"params": {
"center": [
0,
0
],
"radius": 5
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"distance": 8,
"operation": "new"
}
},
{
"command": "component.move_sketches.to_parts",
"params": {}
}
]
}
@@ -0,0 +1,26 @@
{
"sources": [],
"what": "joint.find_limits of a slider whose block runs into a stop at each end",
"source": "analytic",
"method": "Frame (grounded): stops 10x10x10 at x -30..-20 and 50..60; Carriage: box 40x10x10 at x 0..40, same y and z; a slider along +x at value 0. Up, its end x = 40 meets the right stop at x = 50: max 10; down, its end x = 0 meets the left stop at x = -20: min -20. Each side stops at the last value clear of contact, bisected to refine 0.01 mm: within 0.02 mm. Every face is flat, so the mesh gap is exact (gap_tolerance_mm 0)",
"body_count": 3,
"volume_mm3": 6000.0,
"results": [
{
"step": 9,
"abs_mm": 0.02,
"json": {
"values": [
{
"index": 0,
"unit": "mm",
"value": 0.0,
"min": -20.0,
"max": 10.0
}
],
"gap_tolerance_mm": 0.0
}
}
]
}
@@ -0,0 +1,127 @@
{
"commands": [
{
"command": "solid.box",
"params": {
"length": 10,
"width": 10,
"height": 10,
"corner": [
-30,
-5,
0
],
"operation": "new",
"body_name": "StopL"
}
},
{
"command": "solid.box",
"params": {
"length": 10,
"width": 10,
"height": 10,
"corner": [
50,
-5,
0
],
"operation": "new",
"body_name": "StopR"
}
},
{
"command": "solid.box",
"params": {
"length": 40,
"width": 10,
"height": 10,
"corner": [
0,
-5,
0
],
"operation": "new",
"body_name": "SlideBox"
}
},
{
"command": "component.create",
"params": {
"name": "Frame",
"parent": "root",
"activate": false,
"grounded": true
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"StopL",
"StopR"
],
"component": "Frame"
}
},
{
"command": "component.create",
"params": {
"name": "Carriage",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"SlideBox"
],
"component": "Carriage"
}
},
{
"command": "joint.create",
"params": {
"type": "slider",
"name": "Slide",
"flip": true,
"a": {
"occurrence": "Frame",
"point": [
0,
0,
5
],
"z": [
1,
0,
0
]
},
"b": {
"occurrence": "Carriage",
"point": [
0,
0,
5
],
"z": [
1,
0,
0
]
},
"quiet": true
}
},
{
"command": "joint.find_limits",
"params": {
"joint": "Slide"
}
}
]
}
@@ -0,0 +1,53 @@
{
"sources": [],
"what": "assembly.mass_properties of two boxes in one rigid group",
"source": "analytic",
"method": "material 2 g/cm3; A: box 20x10x10 from the origin, V 2000, m 0.004 kg, centre (10,5,5); B: box 10x10x10 from (30,0,0), V 1000, m 0.002 kg, centre (35,5,5); the rigid group: m = 0.006 kg, centre x = (0.004*10 + 0.002*35)/0.006 = 18.333333, y = z = 5; about it, each box's own m/12 (b^2 + c^2) plus m d^2 along x (parallel axes): Ixx = 0.004/12*200 + 0.002/12*200 = 0.100000, Iyy = Izz = 0.004/12*500 + 0.002/12*200 + 0.004*(10 - x)^2 + 0.002*(35 - x)^2 = 1.033333 kg mm^2, no products of inertia (both boxes symmetric about y = 5 and z = 5). Groups: the design's own (no bodies), then A with B",
"body_count": 2,
"volume_mm3": 3000.0,
"results": [
{
"step": 10,
"pointer": "/occurrences/2/mass/center_of_mass_mm",
"json": [
35.0,
5.0,
5.0
]
},
{
"step": 10,
"pointer": "/groups/1/mass",
"json": {
"mass_kg": 0.006,
"center_of_mass_mm": [
18.333333,
5.0,
5.0
]
}
},
{
"step": 10,
"pointer": "/groups/1/mass/inertia_world_kg_mm2",
"abs_mm": 1e-06,
"json": [
[
0.1,
0.0,
0.0
],
[
0.0,
1.033333,
0.0
],
[
0.0,
0.0,
1.033333
]
]
}
]
}
@@ -0,0 +1,98 @@
{
"commands": [
{
"command": "material.create",
"params": {
"name": "Rig Two",
"density_g_cm3": 2.0
}
},
{
"command": "solid.box",
"params": {
"length": 20,
"width": 10,
"height": 10,
"corner": [
0,
0,
0
],
"operation": "new",
"body_name": "BlockA"
}
},
{
"command": "solid.box",
"params": {
"length": 10,
"width": 10,
"height": 10,
"corner": [
30,
0,
0
],
"operation": "new",
"body_name": "BlockB"
}
},
{
"command": "component.create",
"params": {
"name": "A",
"parent": "root",
"activate": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"BlockA"
],
"component": "A"
}
},
{
"command": "component.create",
"params": {
"name": "B",
"parent": "root",
"activate": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"BlockB"
],
"component": "B"
}
},
{
"command": "material.assign",
"params": {
"bodies": [
"BlockA",
"BlockB"
],
"material": "Rig Two"
}
},
{
"command": "joint.rigid_group",
"params": {
"occurrences": [
"A:1",
"B:1"
]
}
},
{
"command": "assembly.mass_properties",
"params": {}
}
]
}
@@ -0,0 +1,34 @@
{
"sources": [],
"what": "occurrence.align of a block's end face onto another part's top face",
"source": "analytic",
"method": "Base: box 40x40x10 from (50,0,0), grounded, top face z = 10; Peg: box 20x10x10 from the origin, end face x = 20 (normal +x) picked at (20,5,5), aligned to the top face at (70,20,10) (normal +z): faces meet facing each other, so +x turns to -z, a quarter turn about +y through (20,5,5), then (20,5,5) moves to (70,20,10). The centre (10,5,5) is (-10,0,0) from the pick, turned (0,0,10): (70,20,20); the 20 mm length stands along z: box (65,15,10)-(75,25,30)",
"body_count": 2,
"volume_mm3": 18000.0,
"results": [
{
"step": 8,
"json": {
"total": {
"center_of_mass": [
70.0,
20.0,
20.0
],
"bbox": {
"min": [
65.0,
15.0,
10.0
],
"max": [
75.0,
25.0,
30.0
]
}
}
}
}
]
}
@@ -0,0 +1,92 @@
{
"commands": [
{
"command": "solid.box",
"params": {
"length": 40,
"width": 40,
"height": 10,
"corner": [
50,
0,
0
],
"operation": "new",
"body_name": "BaseBox"
}
},
{
"command": "solid.box",
"params": {
"length": 20,
"width": 10,
"height": 10,
"corner": [
0,
0,
0
],
"operation": "new",
"body_name": "PegBox"
}
},
{
"command": "component.create",
"params": {
"name": "Base",
"parent": "root",
"activate": false,
"grounded": true
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"BaseBox"
],
"component": "Base"
}
},
{
"command": "component.create",
"params": {
"name": "Peg",
"parent": "root",
"activate": false,
"grounded": false
}
},
{
"command": "component.move_bodies",
"params": {
"bodies": [
"PegBox"
],
"component": "Peg"
}
},
{
"command": "occurrence.align",
"params": {
"occurrence": "Peg:1",
"from_face": [
20,
5,
5
],
"to_face": [
70,
20,
10
]
}
},
{
"command": "occurrence.properties",
"params": {
"occurrence": "Peg:1"
}
}
]
}