-
Notifications
You must be signed in to change notification settings - Fork 15
Add the Axiom Relay windows group (10 models) and a shared axiom-window-kit #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
reposlayer
wants to merge
5
commits into
vibe-stack:main
Choose a base branch
from
reposlayer:feat/architecture-windows-batch-1
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
38c34b9
Add the Axiom Relay windows group and its shared window kit
vmlabss 34a2422
Land tiled window runs on the 1 m grid, and publish the corner's real…
vmlabss a702d9d
Merge branch 'main' into feat/architecture-windows-batch-1
vmlabss 1b75188
Discover support kits instead of listing them by hand
vmlabss c458820
Require a support kit to carry sources
vmlabss File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| --- | ||
| "@scifi-kit/registry": patch | ||
| --- | ||
|
|
||
| Add the Axiom Relay windows group: ten procedural architecture modules covering | ||
| the shared bay, the rated and sealed ports, the tiled ribbon and curtain-wall | ||
| runs, a building corner, a residential variant, a damage variant, and an | ||
| external shading device. | ||
|
|
||
| All ten are built on a new `axiom-window-kit` support item. It treats the | ||
| brief's 1.5 m envelope as a *bay pitch* rather than a fixed prop size, which is | ||
| what makes the long and curtain variants exact multiples of one bay instead of | ||
| one bay stretched — a stretched bay keeps its 50 mm reveal across a 3 m opening | ||
| and reads as a slot cut in a panel. The kit carries the shared plate, reveal | ||
| ring, glazing seat, cill and drip, aperture lamps, plate strips, control paddle, | ||
| and actuator ram, plus lit signal lamps at a lower emissive tier than the cargo | ||
| wave's, because a window lights the full head and cill of its aperture where a | ||
| crate lights a 70 mm lens. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /** | ||
| * The datums every Axiom Relay window module is built on. | ||
| * | ||
| * The windows brief gives the whole group one envelope — 1.5 m wide, 0.20 m | ||
| * deep, 1.4 m tall — and says long and curtain variants tile it on the 1 m | ||
| * grid. That last clause is the important one: it means the module is not a | ||
| * fixed prop but a *bay*, and anything wider has to be an exact number of bays | ||
| * rather than a stretched copy. | ||
| * | ||
| * The two figures do not agree on their own. The bay is 1.5 m because the brief | ||
| * fixes the envelope, but `production-rules.md` requires modular architecture to | ||
| * *terminate* on the 1 m grid, and a run of 1.5 m bays only lands there on an | ||
| * even bay count. Rather than pick one rule and break the other, the pitch stays | ||
| * at the brief's 1.5 m and tiled runs are constrained to even counts — which is | ||
| * why `long-horizontal-window` is four bays at 6 m and not three at 4.5 m. Use | ||
| * {@link tiledWidth} rather than multiplying the pitch by hand. | ||
| * | ||
| * The aperture is centred on the plate for the same reason the doors' opening | ||
| * is: a symmetric plate can be cut as one prism with one centred octagonal hole, | ||
| * and the reference sheets show a symmetric plate. Sill asymmetry is restored by | ||
| * the drip and the cill board, which are the parts that actually read. | ||
| */ | ||
| export const WINDOW_KIT = Object.freeze({ | ||
| width: 1.5, | ||
| height: 1.4, | ||
| depth: 0.2, | ||
| /** Clear glazed aperture. */ | ||
| clearWidth: 0.98, | ||
| clearHeight: 0.86, | ||
| /** Aperture centre; also the plate's centre. */ | ||
| centreY: 0.7, | ||
| clip: 0.16, | ||
| outerClip: 0.2, | ||
| /** Shell frame plate depth; the graphite reveal runs deeper behind it. */ | ||
| platePitch: 0.16, | ||
| /** | ||
| * Tiling pitch for long and curtain variants. Equal to the module width: a | ||
| * bay is the module, not a subdivision of it. | ||
| */ | ||
| bayPitch: 1.5, | ||
| front: '+Z', | ||
| pivot: 'sill-centre', | ||
| } as const) | ||
|
|
||
| /** | ||
| * Width of a tiled run, and the guard that keeps it on the 1 m grid. | ||
| * | ||
| * Throws rather than rounding. A run that silently became 4.5 m wide would place | ||
| * every module downstream of it a half metre off the grid, and the failure would | ||
| * surface as a seam in a level rather than as an error here. | ||
| */ | ||
| export function tiledWidth(bays: number): number { | ||
| if (!Number.isInteger(bays) || bays < 1) { | ||
| throw new Error(`Bay count must be a positive integer, received ${bays}`) | ||
| } | ||
| if (bays > 1 && bays % 2 !== 0) { | ||
| throw new Error( | ||
| `A ${bays}-bay run at the ${WINDOW_KIT.bayPitch} m pitch is ${bays * WINDOW_KIT.bayPitch} m wide, ` | ||
| + 'which does not terminate on the 1 m grid. Tiled runs take an even bay count.', | ||
| ) | ||
| } | ||
| return bays * WINDOW_KIT.bayPitch | ||
| } | ||
|
|
||
| export const APERTURE_HALF: readonly [number, number] = [ | ||
| WINDOW_KIT.clearWidth * 0.5, | ||
| WINDOW_KIT.clearHeight * 0.5, | ||
| ] | ||
|
|
||
| /** Frame plate front face; glazing and hardware are placed against it. */ | ||
| export const PLATE_FRONT = WINDOW_KIT.platePitch * 0.5 | ||
|
|
||
| export interface WindowEnvelope { | ||
| readonly width: number | ||
| readonly depth: number | ||
| readonly height: number | ||
| } | ||
|
|
||
| export interface WindowMetadata { | ||
| readonly version: 1 | ||
| readonly moduleId: string | ||
| readonly pivot: typeof WINDOW_KIT.pivot | ||
| readonly front: typeof WINDOW_KIT.front | ||
| readonly envelope: WindowEnvelope | ||
| readonly clear: { readonly width: number; readonly height: number } | ||
| readonly bays: number | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| import { Group, type MeshPhysicalMaterial } from 'three/webgpu' | ||
|
|
||
| import { cylinder, extrudeProfile, prism } from '../../../src/asset-forge/generator/index.ts' | ||
| import { | ||
| AXIS_Y, | ||
| boltRun, | ||
| box, | ||
| slot, | ||
| statusLens, | ||
| type CargoMaterials, | ||
| } from '../axiom-cargo-kit/index.ts' | ||
| import { APERTURE_HALF, PLATE_FRONT, WINDOW_KIT } from './contract.ts' | ||
|
|
||
| /** | ||
| * The shared window bay: shell plate, graphite reveal ring, glazing seat, and | ||
| * the jamb hardware every module in the group hangs off. | ||
| * | ||
| * Same construction as the doors group, turned landscape: nested octagonal cuts | ||
| * rather than four mitred members, because the reference sheets' aperture has | ||
| * clipped corners at two radii and four members cannot produce them. | ||
| * | ||
| * Every lit part stands proud of the plate. Housed inside the plate's own | ||
| * thickness an emissive strip is buried in solid shell and renders as a black | ||
| * slot — emissive material here is a visible surface, not a light source. | ||
| */ | ||
|
|
||
| const HOUSING_Z = PLATE_FRONT + 0.012 | ||
| const LAMP_Z = PLATE_FRONT + 0.03 | ||
|
|
||
| export interface BayOptions { | ||
| /** Horizontal centre, for multi-bay modules. */ | ||
| readonly centreX?: number | ||
| /** Aperture half-extents, where a module narrows or deepens the opening. */ | ||
| readonly half?: readonly [number, number] | ||
| /** Overall plate width, for the outer bays of a tiled module. */ | ||
| readonly width?: number | ||
| readonly height?: number | ||
| /** Omit the outer plate on interior bays of a tiled run. */ | ||
| readonly plate?: boolean | ||
| /** | ||
| * Omit the stepped picture-frame border. | ||
| * | ||
| * A tiled run draws one border around the whole run instead. Per-bay borders | ||
| * make three bays read as three windows bolted edge to edge — the border is | ||
| * what says "this is one fabrication", so a run that repeats it is saying the | ||
| * opposite of what it means. | ||
| */ | ||
| readonly border?: boolean | ||
| } | ||
|
|
||
| /** The stepped outer border, drawn once per fabrication rather than per bay. */ | ||
| export function plateBorder(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const width = options.width ?? WINDOW_KIT.width | ||
| const height = options.height ?? WINDOW_KIT.height | ||
| parent.add(extrudeProfile(m.shellLight, slot(width * 0.5, height * 0.5, WINDOW_KIT.outerClip), 0.028, [ | ||
| cx, WINDOW_KIT.centreY, PLATE_FRONT + 0.013, | ||
| ], { | ||
| fillet: 0.01, | ||
| bevel: 0.013, | ||
| holes: [slot(width * 0.5 - 0.1, height * 0.5 - 0.1, WINDOW_KIT.outerClip - 0.04)], | ||
| })) | ||
| } | ||
|
|
||
| /** Plate and reveal for one bay. */ | ||
| export function bayPlate(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const [hx, hy] = options.half ?? APERTURE_HALF | ||
| const width = options.width ?? WINDOW_KIT.width | ||
| const height = options.height ?? WINDOW_KIT.height | ||
|
|
||
| if (options.plate ?? true) { | ||
| parent.add(prism(m.shell, [width, height, WINDOW_KIT.platePitch], [cx, WINDOW_KIT.centreY, 0], { | ||
| chamfer: WINDOW_KIT.outerClip, | ||
| fillet: 0.016, | ||
| bevel: 0.014, | ||
| capChamfer: [0.04, 0.024], | ||
| holes: [slot(hx + 0.05, hy + 0.05, WINDOW_KIT.clip + 0.015)], | ||
| })) | ||
| if (options.border ?? true) plateBorder(parent, m, options) | ||
| } | ||
|
|
||
| // Reveal ring: a chunky dark octagon standing proud and running back through | ||
| // the plate. The group's strongest value break, with real walls rather than a | ||
| // dark quad, so it catches the key on one flank and goes black on the other. | ||
| parent.add(extrudeProfile(m.graphite, slot(hx + 0.052, hy + 0.052, WINDOW_KIT.clip + 0.015), 0.15, [ | ||
| cx, WINDOW_KIT.centreY, PLATE_FRONT - 0.058, | ||
| ], { | ||
| fillet: 0.009, | ||
| bevel: 0.012, | ||
| holes: [slot(hx, hy, WINDOW_KIT.clip)], | ||
| })) | ||
| // Inner liner, the seat the glazing beds into. | ||
| parent.add(extrudeProfile(m.ink, slot(hx + 0.022, hy + 0.022, WINDOW_KIT.clip - 0.01), 0.09, [ | ||
| cx, WINDOW_KIT.centreY, PLATE_FRONT - 0.17, | ||
| ], { | ||
| fillet: 0.006, | ||
| bevel: 0.007, | ||
| holes: [slot(hx - 0.012, hy - 0.012, WINDOW_KIT.clip - 0.02)], | ||
| })) | ||
| } | ||
|
|
||
| /** | ||
| * The aperture's edge lighting: a lit line inset just inside the reveal on the | ||
| * head and cill. It is what makes a glazed hole read as a fitted window rather | ||
| * than as a hole, and it is the group's dominant signal surface. | ||
| */ | ||
| export function apertureLamps( | ||
| parent: Group, | ||
| m: CargoMaterials, | ||
| signal: MeshPhysicalMaterial, | ||
| options: BayOptions = {}, | ||
| ): void { | ||
| const cx = options.centreX ?? 0 | ||
| const [hx, hy] = options.half ?? APERTURE_HALF | ||
| for (const sy of [-1, 1]) { | ||
| const y = WINDOW_KIT.centreY + sy * (hy - 0.028) | ||
| box(parent, m.ink, [hx * 1.5, 0.05, 0.04], [cx, y, PLATE_FRONT - 0.055], { | ||
| chamfer: 0.014, fillet: 0.006, bevel: 0.005, | ||
| }) | ||
| box(parent, signal, [hx * 1.42, 0.026, 0.028], [cx, y, PLATE_FRONT - 0.038], { | ||
| chamfer: 0.009, fillet: 0.004, bevel: 0.003, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| /** Cyan status strips on the head and cill of the outer plate. */ | ||
| export function plateStrips(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const height = options.height ?? WINDOW_KIT.height | ||
| for (const sy of [-1, 1]) { | ||
| const y = WINDOW_KIT.centreY + sy * (height * 0.5 - 0.075) | ||
| box(parent, m.ink, [0.34, 0.05, 0.045], [cx, y, HOUSING_Z], { | ||
| chamfer: 0.016, fillet: 0.006, bevel: 0.005, | ||
| }) | ||
| box(parent, m.cyan, [0.28, 0.026, 0.03], [cx, y, LAMP_Z], { | ||
| chamfer: 0.009, fillet: 0.004, bevel: 0.003, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * The jamb control paddle: a raised bezel carrying one large paddle switch and | ||
| * a small state read. Sits on the jamb opposite the actuator. | ||
| */ | ||
| export function controlPaddle( | ||
| parent: Group, | ||
| m: CargoMaterials, | ||
| signal: MeshPhysicalMaterial, | ||
| options: BayOptions = {}, | ||
| ): void { | ||
| const width = options.width ?? WINDOW_KIT.width | ||
| const x = (options.centreX ?? 0) + width * 0.5 - 0.135 | ||
| box(parent, m.graphite, [0.13, 0.36, 0.05], [x, WINDOW_KIT.centreY, HOUSING_Z + 0.008], { | ||
| chamfer: 0.04, fillet: 0.011, bevel: 0.009, capChamfer: 0.018, | ||
| }) | ||
| statusLens(parent, m, [0.07, 0.155], [x, WINDOW_KIT.centreY - 0.02, LAMP_Z + 0.008], signal, 'front') | ||
| statusLens(parent, m, [0.055, 0.022], [x, WINDOW_KIT.centreY + 0.13, LAMP_Z + 0.008], m.cyan, 'front') | ||
| box(parent, m.steel, [0.055, 0.022, 0.018], [x, WINDOW_KIT.centreY - 0.135, LAMP_Z + 0.01], { | ||
| chamfer: 0.006, fillet: 0.003, bevel: 0.003, | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Shutter actuator: a ram on the aperture's left jamb. | ||
| * | ||
| * It is the physical reason the group's shutter and blast variants can close. | ||
| * On the modules that never close it is still present, because the bay is one | ||
| * fabrication and a window that omits the ram is a different casting. | ||
| */ | ||
| export function actuatorRam(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const [hx, hy] = options.half ?? APERTURE_HALF | ||
| // Inside the aperture, not on the jamb behind it. Set one ram-radius outboard | ||
| // of the clear opening the ram sits *inside* the reveal ring's own material | ||
| // and is invisible from every angle the bay is ever seen from. | ||
| const x = cx - hx + 0.055 | ||
| const z = PLATE_FRONT - 0.105 | ||
| parent.add(cylinder(m.steel, 0.024, hy * 1.55, [x, WINDOW_KIT.centreY, z], AXIS_Y, 12)) | ||
| for (const sy of [-1, 1]) { | ||
| parent.add(cylinder(m.graphiteEdge, 0.046, 0.12, [x, WINDOW_KIT.centreY + sy * hy * 0.62, z], AXIS_Y, 12)) | ||
| box(parent, m.graphite, [0.1, 0.12, 0.06], [x - 0.045, WINDOW_KIT.centreY + sy * hy * 0.62, z - 0.03], { | ||
| chamfer: 0.02, fillet: 0.008, bevel: 0.007, | ||
| }) | ||
| } | ||
| parent.add(cylinder(m.ironOxide, 0.034, 0.2, [x, WINDOW_KIT.centreY, z], AXIS_Y, 12)) | ||
| } | ||
|
|
||
| /** | ||
| * Cill: a drip board that throws water clear of the plate below, plus the | ||
| * ribbed board itself. This is where the module stops being symmetric. | ||
| */ | ||
| export function cill(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const width = options.width ?? WINDOW_KIT.width | ||
| const y = WINDOW_KIT.centreY - (options.half ?? APERTURE_HALF)[1] - 0.075 | ||
| // Reaches 50 mm past the plate, not 90. A cill drawn as far forward as the | ||
| // whole module is deep stops reading as part of the bay and starts reading as | ||
| // a rail bolted across it. | ||
| const reach = WINDOW_KIT.depth + 0.05 | ||
| box(parent, m.graphiteEdge, [width - 0.3, 0.05, reach], [cx, y, 0.022], { | ||
| chamfer: 0.014, fillet: 0.006, bevel: 0.005, capChamfer: [0.026, 0.012], | ||
| }) | ||
| for (let index = 0; index < 6; index += 1) { | ||
| box(parent, m.ink, [0.02, 0.009, reach - 0.05], [ | ||
| cx + (index - 2.5) * (width - 0.48) / 5, y + 0.029, 0.022, | ||
| ], { chamfer: 0.004, fillet: 0.002, bevel: 0.002 }) | ||
| } | ||
| box(parent, m.amberPaint, [width - 0.38, 0.009, 0.024], [cx, y + 0.029, reach * 0.5 - 0.02], { | ||
| chamfer: 0.004, fillet: 0.002, bevel: 0.002, | ||
| }) | ||
| } | ||
|
|
||
| /** Corner fixings on the diagonal returns the outer clip creates. */ | ||
| export function plateFixings(parent: Group, m: CargoMaterials, options: BayOptions = {}): void { | ||
| const cx = options.centreX ?? 0 | ||
| const width = options.width ?? WINDOW_KIT.width | ||
| const height = options.height ?? WINDOW_KIT.height | ||
| for (const sx of [-1, 1]) { | ||
| for (const sy of [-1, 1]) { | ||
| boltRun( | ||
| parent, | ||
| m.steel, | ||
| [cx + sx * (width * 0.5 - 0.052), WINDOW_KIT.centreY + sy * (height * 0.5 - 0.19), PLATE_FRONT + 0.028], | ||
| [cx + sx * (width * 0.5 - 0.052), WINDOW_KIT.centreY + sy * (height * 0.5 - 0.42), PLATE_FRONT + 0.028], | ||
| 2, | ||
| 0.013, | ||
| 'front', | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** Everything a standard single-bay window shares. */ | ||
| export function buildBay( | ||
| parent: Group, | ||
| m: CargoMaterials, | ||
| signal: MeshPhysicalMaterial, | ||
| options: BayOptions = {}, | ||
| ): void { | ||
| bayPlate(parent, m, options) | ||
| plateFixings(parent, m, options) | ||
| cill(parent, m, options) | ||
| apertureLamps(parent, m, signal, options) | ||
| plateStrips(parent, m, options) | ||
| controlPaddle(parent, m, signal, options) | ||
| actuatorRam(parent, m, options) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.