Zhestokost kit and Nakovalnya emit schematic lines from their own frames (outline, step, seam, fitting)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-22 15:54:27 +02:00
commit 28ed24b28e
2 changed files with 54 additions and 19 deletions

View file

@ -1,5 +1,5 @@
// Zhestokost fortress (boss) "Nakovalnya" (anvil). Built from the Zhestokost kit.
// part = lang | brief | anchors | primary | secondary | accent | recess | marking | emit:<id> | rad:<id>
// part = lang | brief | anchors | lines | primary | secondary | accent | recess | marking | emit:<id> | rad:<id>
// Frame: Ship Root, metres, nose -Y, starboard -X, up +Z. Anchor yaw: 0 nose, 90 port, 180 aft, 270 starboard.
include <zhestokost.scad>
part = "primary";
@ -46,10 +46,10 @@ zh_mass(M);
zh_mass(T1);
zh_mass(T2);
zh_mass(APRON);
for (x = CX) translate([x, 0, 0]) zh_mass(CAS);
for (x = CX) lp_at(t=[x, 0, 0]) zh_mass(CAS);
zh_mass(SPINE);
zh_mass(KEEL);
for (s = [-1, 1]) translate([s*DX, 0, 0]) zh_mass(DRV, "secondary");
zh_mass(KEEL, lines=false); // under the hull: no top view sees it
for (s = [-1, 1]) lp_at(t=[s*DX, 0, 0]) zh_mass(DRV, "secondary");
// ---- the casemate guns: lighter cast-steel barrels out of charcoal mantlets -----------------------------------------
module casemate_gun() {
@ -60,9 +60,11 @@ module casemate_gun() {
}
zh_z("primary") translate([0, GY - 0.7, GZ]) rotate([0, 0, 180]) tube(BR, BL, 8);
zh_z("recess") translate([0, GY - 0.7 - BL - 0.81, GZ]) cube([BR*1.2, 0.05, BR*1.2], center=true);
lp_line("fitting", [for (p = zh_rect(4.0, 1.2, GZ + 1.7)) p + [0, GY - 0.3, 0]], true);
lp_line("fitting", [[0, GY - 0.7, GZ + BR], [0, GY - 0.7 - BL, GZ + BR]]);
}
for (x = CX) translate([x, 0, 0]) casemate_gun();
for (x = CX) translate([x, 0, 0]) zh_on_top(CAS, -21.9, 0) zh_stripes(3.6, 0.6, 4);
for (x = CX) lp_at(t=[x, 0, 0]) casemate_gun();
for (x = CX) lp_at(t=[x, 0, 0]) zh_on_top(CAS, -21.9, 0) zh_stripes(3.6, 0.6, 4);
// black reveals between the casemates, on the apron
for (u = [-0.33, 0.33]) zh_on_top(APRON, -19.5, u) zh_reveal(2.3, 7.0);
@ -76,9 +78,10 @@ module flak(yaw) {
zh_z("secondary") cylinder(r=FR, h=FH + ZH_SINK, $fn=8);
zh_z("accent") translate([0, 0, FH*0.35]) cylinder(r=FR + 0.08, h=0.35, $fn=8); // oxide band round the drum
zh_z("recess") translate([0, 0, FH - 0.06]) cylinder(r=FR*0.84, h=0.1, $fn=8);
translate([0, 0, FH - 0.02]) rotate([0, 0, yaw]) zh_gun(FG[0], FG[1], FG[2], FG[3], "primary");
lp_line("fitting", zh_oct(FR, FH), true);
lp_at(t=[0, 0, FH - 0.02], r=[0, 0, yaw]) zh_gun(FG[0], FG[1], FG[2], FG[3], "primary");
}
for (f = FLAK) translate([f[0], f[1], DECK - ZH_SINK]) flak(f[2]);
for (f = FLAK) lp_at(t=[f[0], f[1], DECK - ZH_SINK]) flak(f[2]);
function rz(a, p) = [p[0]*cos(a) - p[1]*sin(a), p[0]*sin(a) + p[1]*cos(a), p[2]];
// ---- bow: cab with the slit high on the spine's front step ---------------------------------------------------------
@ -87,7 +90,10 @@ zh_on_top(T2, -3.6, 0) zh_serial(0.7, 4);
// ---- dorsal magazine hatches: two rows of four on the mid-deck, clear of the rim -----------------------------------
for (y = [-8.0, -3.4, 1.2, 5.6], u = [-0.78, 0.78]) zh_on_top(T1, y, u) zh_hatch(2.8, 3.0, dogs=false);
for (y = [0.5, 4.5]) zh_on_top(T2, y, 0) zh_z("secondary") translate([0, 0, 0.06]) cube([8.4, 0.5, 0.2], center=true); // plate seams
for (y = [0.5, 4.5]) zh_on_top(T2, y, 0) { // plate seams
zh_z("secondary") translate([0, 0, 0.06]) cube([8.4, 0.5, 0.2], center=true);
lp_line("seam", [[-4.2, 0, 0.16], [4.2, 0, 0.16]]);
}
// Feed collar for an Oboz: a lighter cast-steel flange ring standing proud of the flank round the kit collar.
module feed_collar() {
@ -97,7 +103,7 @@ module feed_collar() {
}
// ---- flanks: feed collar, ammunition door, unit serial -------------------------------------------------------------
for (s = [-1, 1]) {
zh_on_belt(M, 1.5, s, 0.5) rotate([0, 0, 22.5]) feed_collar();
zh_on_belt(M, 1.5, s, 0.5) lp_at(r=[0, 0, 22.5]) feed_collar();
zh_on_wall(M, 7.0, s, 0.45) zh_hatch(2.6, 3.0);
zh_on_wall(M, -9.0, s, 0.5) zh_serial(0.8, 5);
}

View file

@ -84,7 +84,22 @@ function zh_stern_at(secs, x, v, o, sink=ZH_SINK) = zh_stern_pt(secs, x, v) - [0
// ==== PRIMARY: masses that make the silhouette ========================================================================
// Any section list as one faceted poured mass.
module zh_mass(secs, zone="primary") zh_z(zone) for (i = [0:len(secs)-2]) hull() { _zh_slice(secs[i]); _zh_slice(secs[i+1]); }
// Lines: the plan at the cast belt (outline), the deck edge (step) and, on a primary mass, a seam across the deck at each
// interior station. lines=false for a mass no top view can see (a keel under the hull).
module zh_mass(secs, zone="primary", lines=true) {
zh_z(zone) for (i = [0:len(secs)-2]) hull() { _zh_slice(secs[i]); _zh_slice(secs[i+1]); }
if (lines) {
n = len(secs);
lp_line("outline", concat([for (s = secs) [-s[1]/2, s[0], s[3] + s[2]*ZH_WU]],
[for (i = [n-1:-1:0]) [secs[i][1]/2, secs[i][0], secs[i][3] + secs[i][2]*ZH_WU]]), true);
lp_line("step", concat([for (s = secs) [-(s[1]/2 - s[4]), s[0], s[3] + s[2]]],
[for (i = [n-1:-1:0]) [secs[i][1]/2 - secs[i][4], secs[i][0], secs[i][3] + secs[i][2]]]), true);
if (zone == "primary") for (i = [1:n-2]) let(s = secs[i])
lp_line("seam", [[-(s[1]/2 - s[4]), s[0], s[3] + s[2]], [s[1]/2 - s[4], s[0], s[3] + s[2]]]);
}
}
function zh_rect(w, l, z) = [[-w/2, -l/2, z], [w/2, -l/2, z], [w/2, l/2, z], [-w/2, l/2, z]];
function zh_oct(r, z) = [for (k = [0:7]) [r*cos(45*k), r*sin(45*k), z]];
// A core or whole hull: bow chamfered in plan and dropped (bow, as a fraction of L), stern a flat face with a cast lip.
// bowW / bowH: nose width and height as fractions of W and H.
@ -128,6 +143,7 @@ module zh_collar(r=ZH_COLLAR_R, proud=0.55, lugs=4) {
zh_z("secondary") difference() {
cylinder(r=r, h=proud + ZH_SINK, $fn=8); translate([0, 0, -0.1]) cylinder(r=r*0.6, h=proud + 1, $fn=8); }
zh_z("recess") cylinder(r=r*0.62, h=0.08 + ZH_SINK, $fn=8);
lp_line("fitting", zh_oct(r, proud), true);
zh_z("accent") for (i = [0:lugs-1]) rotate(i*360/lugs) translate([r + 0.16, 0, (proud + 0.25)/2])
cube([0.5, 0.6, proud + 0.25 + ZH_SINK], center=true);
}
@ -140,13 +156,17 @@ module zh_hatch(w=ZH_HATCH, l=ZH_HATCH, t=0.22, dogs=true) {
for (x = [-1, 1]) translate([x*w*0.27, 0, t + 0.12]) cbox([0.5, l + 0.6, 0.34], 0.07);
if (dogs) for (sx = [-1, 1], sy = [-1, 1]) translate([sx*(w/2 + 0.12), sy*l*0.3, t*0.6]) cube([0.36, 0.44, 0.4], center=true);
}
lp_line("fitting", zh_rect(w, l, t), true);
translate([0, l/2 - 0.42, t]) zh_stripes(w*0.36, 0.5, dogs ? 3 : 2);
}
// Armour plate (any frame): replaceable slab with four bolt bosses; leaves a seam to its neighbour.
module zh_plate(w, l, t=0.3, zone="secondary") zh_z(zone) {
translate([0, 0, t/2]) cbox([w, l, t + ZH_SINK], 0.07);
for (sx = [-1, 1], sy = [-1, 1]) translate([sx*(w/2 - 0.25), sy*(l/2 - 0.25), t + 0.05]) cube([0.22, 0.22, 0.12], center=true);
module zh_plate(w, l, t=0.3, zone="secondary") {
zh_z(zone) {
translate([0, 0, t/2]) cbox([w, l, t + ZH_SINK], 0.07);
for (sx = [-1, 1], sy = [-1, 1]) translate([sx*(w/2 - 0.25), sy*(l/2 - 0.25), t + 0.05]) cube([0.22, 0.22, 0.12], center=true);
}
lp_line("fitting", zh_rect(w, l, t), true);
}
// Radiator trough (top frame): cast frame round a black floor, split by a spine wall; vanes stand in it (zh_rad_vanes).
@ -155,6 +175,8 @@ module zh_rad_trough(w=3.2, l=2.4, d=0.45, zone="secondary") {
cbox([w + 0.7, l + 0.7, d + ZH_SINK], 0.1); cube([w, l, d*3], center=true); }
zh_z(zone) translate([0, 0, d*0.6]) cube([0.3, l, d*1.2 + ZH_SINK], center=true);
zh_z("recess") translate([0, 0, 0.04]) cube([w, l, 0.08 + ZH_SINK], center=true);
lp_line("fitting", zh_rect(w + 0.7, l + 0.7, d), true);
lp_line("fitting", zh_rect(w, l, d), true);
}
// radiator-mesh for one half of a trough (side 1 port, -1 starboard): transverse vanes rising out of it
module zh_rad_vanes(w=3.2, l=2.4, side=1, n=5, h=1.0) {
@ -173,17 +195,24 @@ module zh_tow_lug(s=ZH_LUG, zone="secondary") {
// Ballistic mount (top frame): octagonal ring, battered mantlet block, n thick barrels with box brakes, pointing -Y.
module zh_gun(n=2, cal=0.22, len=3.0, base=1.8, zone="secondary") {
zh_z(zone) cylinder(r=base*0.56, h=0.35 + ZH_SINK, $fn=8);
zh_mass([for (s = zh_block(0, base*0.95, base, base*0.5, 0.33, base*0.14, 0, 0.22)) s], zone);
zh_z(zone) for (i = [0:n-1]) translate([(i - (n-1)/2)*cal*3.2, -base*0.4, 0.33 + base*0.25]) {
rotate([0, 0, 180]) tube(cal, len, 8);
translate([0, -len, 0]) cbox([cal*3, cal*3.5, cal*3], 0.04);
lp_line("fitting", zh_oct(base*0.56, 0.35), true);
zh_mass([for (s = zh_block(0, base*0.95, base, base*0.5, 0.33, base*0.14, 0, 0.22)) s], zone, lines=false);
for (i = [0:n-1]) lp_at(t=[(i - (n-1)/2)*cal*3.2, -base*0.4, 0.33 + base*0.25]) {
zh_z(zone) {
rotate([0, 0, 180]) tube(cal, len, 8);
translate([0, -len, 0]) cbox([cal*3, cal*3.5, cal*3], 0.04);
}
lp_line("fitting", [[0, 0, 0], [0, -len, 0]]);
}
}
function zh_gun_muzzle(n=2, cal=0.22, len=3.0, base=1.8) = [0, -base*0.4 - len - cal*1.75, 0.33 + base*0.25];
// ==== TERTIARY: detail ===============================================================================================
// Shadow-gap floor (top frame): the black bottom of a reveal between modules.
module zh_reveal(w, l) zh_z("recess") translate([0, 0, 0.03]) cube([w, l, 0.06 + ZH_SINK], center=true);
module zh_reveal(w, l) {
zh_z("recess") translate([0, 0, 0.03]) cube([w, l, 0.06 + ZH_SINK], center=true);
lp_line("seam", zh_rect(w, l, 0.06), true);
}
// Cream safety chevrons (any frame, +Z out): n diagonal bars clipped to w x l.
module zh_stripes(w, l, n=3) zh_z("marking") intersection() {
translate([0, 0, 0.04]) cube([w, l, 0.1], center=true);