Kit frames: mates are lp_at placements that track the Ship Root frame; zone OFFs unchanged

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-22 15:53:20 +02:00
commit bd343b2a4e
2 changed files with 33 additions and 7 deletions

View file

@ -12,3 +12,28 @@ module cbox(s, c=0.2, center=true) {
module tube(r, l, fn=8) { rotate([-90,0,0]) cylinder(r=r, h=l, $fn=fn); }
// a straight strut between two points
module strut(a, b, r=0.08, fn=6) { hull() { translate(a) sphere(r, $fn=fn); translate(b) sphere(r, $fn=fn); } }
// ==== Frames and schematic lines =====================================================================================
// A kit part that draws schematic lines is placed with lp_at, never with raw translate/rotate/mirror: lp_at moves the
// geometry (multmatrix) and tracks the same frame in $lp_m, so lp_line, called anywhere beneath it, emits its points in
// the Ship Root frame. Placement has this one owner. A line is echoed (part "lines") only when the frame's local +Z
// points up, so fittings on the belly, the walls and the belts, which a top view cannot see, emit nothing.
$lp_m = [[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]];
function lp_T(t) = [[1,0,0,t[0]], [0,1,0,t[1]], [0,0,1,t[2]], [0,0,0,1]];
function lp_R(r) = // Rz * Ry * Rx, as rotate() composes
[[cos(r[2]), -sin(r[2]), 0, 0], [sin(r[2]), cos(r[2]), 0, 0], [0,0,1,0], [0,0,0,1]]
* [[cos(r[1]), 0, sin(r[1]), 0], [0,1,0,0], [-sin(r[1]), 0, cos(r[1]), 0], [0,0,0,1]]
* [[1,0,0,0], [0, cos(r[0]), -sin(r[0]), 0], [0, sin(r[0]), cos(r[0]), 0], [0,0,0,1]];
lp_MX = [[-1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]]; // mirror across x = 0
function lp_ap(m, p) = let(v = m * [p[0], p[1], p[2], 1]) [v[0], v[1], v[2]];
// Mirror (when mx), then translate by t, then rotate by r (as nested mirror, translate, rotate would).
module lp_at(t=[0,0,0], r=[0,0,0], mx=false) {
M = (mx ? lp_MX : [[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]]) * lp_T(t) * lp_R(r);
$lp_m = $lp_m * M;
multmatrix(M) children();
}
// One schematic line: layer (outline, step, seam or fitting), points in the local frame, cyclic when it closes.
module lp_line(layer, pts, cyclic=false) {
if (!is_undef(part) && part == "lines" && ($lp_m * [0, 0, 1, 0])[2] > 0.5)
echo(line = [layer, cyclic, [for (p = pts) lp_ap($lp_m, p)]]);
}

View file

@ -22,6 +22,7 @@
// zh_on_stern, zh_on_nose. In the mate frame local +Z is the outward normal and the part is sunk ZH_SINK into
// the skin, so nothing floats. Top/belly frames keep local +Y aft; wall/belt frames keep +Y aft with local +X
// pointing down the wall on either side (starboard mates are mirrors of port); stern/nose frames have local +Y down.
// Mates are lp_at frames: a part under a mate that draws lines (lp_line) emits them in the Ship Root frame.
// 3. Zone routing: every zh_ module draws only the zone the global `part` names, so a hull writes its assembly once.
// A hull on a Zhestokost donor (HULL_DONOR == "zhestokost") names a donor zone 'zhestokost:<zone>'.
// Anchor meshes (zh_rad_vanes, zh_emitter) are not routed: call them inside the hull's rad:/emit: parts under
@ -65,17 +66,17 @@ function zh_wall_tilt(secs, y) = let(s = zh_sec(secs, y)) atan2(s[4], s[2]*(1-ZH
function zh_stern_pt(secs, x=0, v=0.5) = let(s = secs[len(secs)-1]) [x, s[0], s[3] + s[2]*v];
function zh_nose_pt(secs, x=0, v=0.5) = let(s = secs[0]) [x, s[0], s[3] + s[2]*v];
module zh_on_top(secs, y, u=0, sink=ZH_SINK) translate(zh_top(secs, y, u) - [0, 0, sink]) children();
module zh_on_belly(secs, y, u=0, sink=ZH_SINK) translate(zh_belly(secs, y, u) + [0, 0, sink]) rotate([0, 180, 0]) children();
module _zh_port(side) { if (side < 0) mirror([1, 0, 0]) children(); else children(); }
module zh_on_top(secs, y, u=0, sink=ZH_SINK) lp_at(t=zh_top(secs, y, u) - [0, 0, sink]) children();
module zh_on_belly(secs, y, u=0, sink=ZH_SINK) lp_at(t=zh_belly(secs, y, u) + [0, 0, sink], r=[0, 180, 0]) children();
module zh_on_belt(secs, y, side=1, v=0.5, sink=ZH_SINK)
_zh_port(side) translate(zh_belt(secs, y, 1, v) - [sink, 0, 0]) rotate([0, 90, 0]) children();
lp_at(t=zh_belt(secs, y, 1, v) - [sink, 0, 0], r=[0, 90, 0], mx=side < 0) children();
module zh_on_wall(secs, y, side=1, v=0.5, sink=ZH_SINK) {
t = zh_wall_tilt(secs, y);
_zh_port(side) translate(zh_wall(secs, y, 1, v) - [cos(t), 0, sin(t)]*sink) rotate([0, 90 - t, 0]) children();
lp_at(t=zh_wall(secs, y, 1, v) - [cos(t), 0, sin(t)]*sink, r=[0, 90 - t, 0], mx=side < 0) children();
}
module zh_on_stern(secs, x=0, v=0.5, sink=ZH_SINK) translate(zh_stern_pt(secs, x, v) - [0, sink, 0]) rotate([-90, 0, 0]) children();
module zh_on_nose(secs, x=0, v=0.5, sink=ZH_SINK) translate(zh_nose_pt(secs, x, v) + [0, sink, 0]) rotate([90, 0, 0]) rotate([0, 0, 180]) children();
module zh_on_stern(secs, x=0, v=0.5, sink=ZH_SINK) lp_at(t=zh_stern_pt(secs, x, v) - [0, sink, 0], r=[-90, 0, 0]) children();
module zh_on_nose(secs, x=0, v=0.5, sink=ZH_SINK)
lp_at(t=zh_nose_pt(secs, x, v) + [0, sink, 0], r=[90, 0, 0]) lp_at(r=[0, 0, 180]) children();
// world point of a local offset under a mate (for anchors): top, belly, stern frames
function zh_top_at(secs, y, u, o, sink=ZH_SINK) = zh_top(secs, y, u) - [0, 0, sink] + o;
function zh_belly_at(secs, y, u, o, sink=ZH_SINK) = zh_belly(secs, y, u) + [0, 0, sink] + [-o[0], o[1], -o[2]];