wadi-mcp 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.mjs +255 -106
- package/dist/server.mjs +255 -106
- package/package.json +2 -1
package/dist/http.mjs
CHANGED
|
@@ -326144,7 +326144,7 @@ var require_detect_gpu_umd = __commonJS({
|
|
|
326144
326144
|
function e3(e4) {
|
|
326145
326145
|
var o2;
|
|
326146
326146
|
return n4(this, void 0, void 0, function() {
|
|
326147
|
-
var n5, a2, i5, c2, u2, l2, d3, v2, p2, g3, m2, b2, y2, x3, P2, A2,
|
|
326147
|
+
var n5, a2, i5, c2, u2, l2, d3, v2, p2, g3, m2, b2, y2, x3, P2, A2, C26, L2, M2, k2, j2, B2, R2, T2, U2, I2;
|
|
326148
326148
|
return t(this, function(t2) {
|
|
326149
326149
|
switch (t2.label) {
|
|
326150
326150
|
case 0:
|
|
@@ -326172,7 +326172,7 @@ var require_detect_gpu_umd = __commonJS({
|
|
|
326172
326172
|
return [e5, h(p2, e5[2])];
|
|
326173
326173
|
}).sort(function(e5, r4) {
|
|
326174
326174
|
return e5[1] - r4[1];
|
|
326175
|
-
})[0][0] : d3[0], m2 = g3[0], b2 = g3[4], y2 = Number.MAX_VALUE, P2 = window.devicePixelRatio, A2 = S.width * P2 * S.height * P2,
|
|
326175
|
+
})[0][0] : d3[0], m2 = g3[0], b2 = g3[4], y2 = Number.MAX_VALUE, P2 = window.devicePixelRatio, A2 = S.width * P2 * S.height * P2, C26 = 0, L2 = b2; C26 < L2.length; C26++) M2 = L2[C26], k2 = M2[0], j2 = M2[1], B2 = k2 * j2, (R2 = Math.abs(A2 - B2)) < y2 && (y2 = R2, x3 = M2);
|
|
326176
326176
|
return x3 ? (U2 = (T2 = x3)[2], I2 = T2[3], [2, [y2, U2, m2, I2]]) : [2];
|
|
326177
326177
|
}
|
|
326178
326178
|
});
|
|
@@ -337816,15 +337816,15 @@ var require_index_cjs3 = __commonJS({
|
|
|
337816
337816
|
}, [r2, z3]), S.createElement("primitive", M.default({ ref: d2, object: z3 }, u2));
|
|
337817
337817
|
});
|
|
337818
337818
|
var ft = S.forwardRef(({ children: e2, domElement: t2, onChange: r2, onMouseDown: n5, onMouseUp: o2, onObjectChange: i3, object: s2, makeDefault: l2, camera: u2, enabled: d2, axis: m2, mode: f3, translationSnap: p2, rotationSnap: h2, scaleSnap: x2, space: y2, size: v2, showX: g2, showY: w2, showZ: z3, ...b2 }, E2) => {
|
|
337819
|
-
const
|
|
337819
|
+
const C26 = a.useThree((e3) => e3.controls), P2 = a.useThree((e3) => e3.gl), R2 = a.useThree((e3) => e3.events), D2 = a.useThree((e3) => e3.camera), F2 = a.useThree((e3) => e3.invalidate), k2 = a.useThree((e3) => e3.get), _2 = a.useThree((e3) => e3.set), A2 = u2 || D2, L2 = t2 || R2.connected || P2.domElement, I2 = S.useMemo(() => new c.TransformControls(A2, L2), [A2, L2]), B2 = S.useRef(null);
|
|
337820
337820
|
S.useLayoutEffect(() => (s2 ? I2.attach(s2 instanceof T.Object3D ? s2 : s2.current) : B2.current instanceof T.Object3D && I2.attach(B2.current), () => {
|
|
337821
337821
|
I2.detach();
|
|
337822
337822
|
}), [s2, e2, I2]), S.useEffect(() => {
|
|
337823
|
-
if (
|
|
337824
|
-
const e3 = (e4) =>
|
|
337823
|
+
if (C26) {
|
|
337824
|
+
const e3 = (e4) => C26.enabled = !e4.value;
|
|
337825
337825
|
return I2.addEventListener("dragging-changed", e3), () => I2.removeEventListener("dragging-changed", e3);
|
|
337826
337826
|
}
|
|
337827
|
-
}, [I2,
|
|
337827
|
+
}, [I2, C26]);
|
|
337828
337828
|
const U2 = S.useRef(void 0), V3 = S.useRef(void 0), O2 = S.useRef(void 0), N2 = S.useRef(void 0);
|
|
337829
337829
|
return S.useLayoutEffect(() => {
|
|
337830
337830
|
U2.current = r2;
|
|
@@ -337894,7 +337894,7 @@ var require_index_cjs3 = __commonJS({
|
|
|
337894
337894
|
const e3 = { Box3: n4.Box3, MathUtils: { clamp: n4.MathUtils.clamp }, Matrix4: n4.Matrix4, Quaternion: n4.Quaternion, Raycaster: n4.Raycaster, Sphere: n4.Sphere, Spherical: n4.Spherical, Vector2: n4.Vector2, Vector3: n4.Vector3, Vector4: n4.Vector4 };
|
|
337895
337895
|
b2.install({ THREE: e3 }), a.extend({ CameraControlsImpl: b2 });
|
|
337896
337896
|
}, [b2]);
|
|
337897
|
-
const E2 = a.useThree((e3) => e3.camera),
|
|
337897
|
+
const E2 = a.useThree((e3) => e3.camera), C26 = a.useThree((e3) => e3.gl), T2 = a.useThree((e3) => e3.invalidate), R2 = a.useThree((e3) => e3.events), D2 = a.useThree((e3) => e3.setEvents), F2 = a.useThree((e3) => e3.set), k2 = a.useThree((e3) => e3.get), _2 = a.useThree((e3) => e3.performance), A2 = i3 || E2, L2 = s2 || R2.connected || C26.domElement, I2 = t.useMemo(() => new b2(A2), [b2, A2]);
|
|
337898
337898
|
return a.useFrame((e3, t2) => {
|
|
337899
337899
|
I2.update(t2);
|
|
337900
337900
|
}, -1), t.useEffect(() => (I2.connect(L2), () => {
|
|
@@ -338210,7 +338210,7 @@ var require_index_cjs3 = __commonJS({
|
|
|
338210
338210
|
for (const n5 in e3) r3[n5] = t2(e3[n5]);
|
|
338211
338211
|
return r3;
|
|
338212
338212
|
}
|
|
338213
|
-
}, []),
|
|
338213
|
+
}, []), C26 = S.useCallback(() => {
|
|
338214
338214
|
const e3 = {}, t2 = u2.current, r3 = p2.current;
|
|
338215
338215
|
if (t2) {
|
|
338216
338216
|
if (r3 && Array.isArray(t2.frames)) {
|
|
@@ -338261,12 +338261,12 @@ var require_index_cjs3 = __commonJS({
|
|
|
338261
338261
|
}
|
|
338262
338262
|
u2.current && u2.current.frames && (u2.current.frames = M2(u2.current.frames));
|
|
338263
338263
|
} else if (t2) {
|
|
338264
|
-
u2.current = e3, u2.current.frames =
|
|
338264
|
+
u2.current = e3, u2.current.frames = C26(), d2.current = Array.isArray(e3.frames) ? e3.frames.length : Object.keys(e3.frames).length;
|
|
338265
338265
|
const { w: t3, h: n5 } = or(e3.frames).sourceSize;
|
|
338266
338266
|
r3 = b2(t3, n5, 0.1);
|
|
338267
338267
|
}
|
|
338268
338268
|
x2(u2.current), "encoding" in t2 ? t2.encoding = 3001 : "colorSpace" in t2 && (t2.colorSpace = T.SRGBColorSpace), v2(t2), z3({ spriteTexture: t2, spriteData: u2.current, aspect: r3 });
|
|
338269
|
-
}, [E2, i3,
|
|
338269
|
+
}, [E2, i3, C26, b2, M2]), R2 = S.useCallback((e3, t2, r3) => {
|
|
338270
338270
|
const n5 = fetch(e3).then((e4) => e4.json()), a2 = new Promise((e4) => {
|
|
338271
338271
|
g2.load(t2, e4);
|
|
338272
338272
|
});
|
|
@@ -338539,7 +338539,7 @@ var require_index_cjs3 = __commonJS({
|
|
|
338539
338539
|
});
|
|
338540
338540
|
var Or = S.createContext(null);
|
|
338541
338541
|
var Nr = new T.PlaneGeometry(1, 1);
|
|
338542
|
-
var jr = S.forwardRef(({ startFrame: e2 = 0, endFrame: t2, fps: r2 = 30, frameName: n5 = "", textureDataURL: o2, textureImageURL: i3, loop: s2 = false, numberOfFrames: l2 = 1, autoPlay: c2 = true, animationNames: u2, onStart: d2, onEnd: m2, onLoopEnd: f3, onFrame: p2, play: h2, pause: x2 = false, flipX: y2 = false, alphaTest: v2 = 0, children: g2, asSprite: w2 = false, offset: z3, playBackwards: b2 = false, resetOnEnd: E2 = false, maxItems:
|
|
338542
|
+
var jr = S.forwardRef(({ startFrame: e2 = 0, endFrame: t2, fps: r2 = 30, frameName: n5 = "", textureDataURL: o2, textureImageURL: i3, loop: s2 = false, numberOfFrames: l2 = 1, autoPlay: c2 = true, animationNames: u2, onStart: d2, onEnd: m2, onLoopEnd: f3, onFrame: p2, play: h2, pause: x2 = false, flipX: y2 = false, alphaTest: v2 = 0, children: g2, asSprite: w2 = false, offset: z3, playBackwards: b2 = false, resetOnEnd: E2 = false, maxItems: C26 = 1, instanceItems: P2 = [[0, 0, 0]], spriteDataset: R2, canvasRenderingContext2DSettings: D2, roundFramePosition: F2 = false, meshProps: k2 = {}, ..._2 }, A2) => {
|
|
338543
338543
|
const L2 = S.useRef(new T.Group()), I2 = S.useRef(null), B2 = S.useRef(null), U2 = S.useRef(null), V3 = S.useRef(window.performance.now()), O2 = S.useRef(e2), N2 = S.useRef(n5), j2 = r2 > 0 ? 1e3 / r2 : 0, [W2, G2] = S.useState(new T.Texture()), H2 = S.useRef(0), [$2, q2] = S.useState(new T.Vector3(1, 1, 1)), X2 = y2 ? -1 : 1, Z2 = S.useRef(x2), Y2 = S.useRef(z3), Q2 = S.useRef(false), { spriteObj: K2, loadJsonAndTexture: J2 } = sr(null, null, u2, l2, void 0, D2), te2 = S.useRef(n5), re2 = S.useCallback((e3, t3) => {
|
|
338544
338544
|
if (null === t3) l2 && (H2.current = l2, b2 && (O2.current = l2 - 1), I2.current = t3);
|
|
338545
338545
|
else {
|
|
@@ -338615,7 +338615,7 @@ var require_index_cjs3 = __commonJS({
|
|
|
338615
338615
|
})(), null == p2 || p2({ currentFrameName: N2.current, currentFrame: O2.current })));
|
|
338616
338616
|
}), S.createElement("group", M.default({}, _2, { ref: L2, scale: function(e3 = new T.Vector3(1, 1, 1), t3 = 1) {
|
|
338617
338617
|
return "number" == typeof t3 ? e3.multiplyScalar(t3) : Array.isArray(t3) ? e3.multiply(new T.Vector3(...t3)) : t3 instanceof T.Vector3 ? e3.multiply(t3) : void 0;
|
|
338618
|
-
}($2, _2.scale) }), S.createElement(Or.Provider, { value: ae2 }, w2 && S.createElement(ee, null, S.createElement("mesh", M.default({ ref: U2, scale: 1, geometry: Nr }, k2), S.createElement("meshBasicMaterial", { premultipliedAlpha: false, toneMapped: false, side: T.DoubleSide, ref: B2, map: W2, transparent: true, alphaTest: null != v2 ? v2 : 0 }))), !w2 && S.createElement(Ir, M.default({ geometry: Nr, limit: null !=
|
|
338618
|
+
}($2, _2.scale) }), S.createElement(Or.Provider, { value: ae2 }, w2 && S.createElement(ee, null, S.createElement("mesh", M.default({ ref: U2, scale: 1, geometry: Nr }, k2), S.createElement("meshBasicMaterial", { premultipliedAlpha: false, toneMapped: false, side: T.DoubleSide, ref: B2, map: W2, transparent: true, alphaTest: null != v2 ? v2 : 0 }))), !w2 && S.createElement(Ir, M.default({ geometry: Nr, limit: null != C26 ? C26 : 1 }, k2), S.createElement("meshBasicMaterial", { premultipliedAlpha: false, toneMapped: false, side: T.DoubleSide, ref: B2, map: W2, transparent: true, alphaTest: null != v2 ? v2 : 0 }), (null != P2 ? P2 : [0]).map((e3, t3) => S.createElement(Lr, M.default({ key: t3, ref: 1 === (null == P2 ? void 0 : P2.length) ? U2 : null, position: e3, scale: 1 }, k2)))), g2));
|
|
338619
338619
|
});
|
|
338620
338620
|
var Wr = S.forwardRef(({ children: e2, curve: t2 }, r2) => {
|
|
338621
338621
|
const [n5] = S.useState(() => new T.Scene()), [o2, i3] = S.useState(), s2 = S.useRef(null);
|
|
@@ -338877,12 +338877,12 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
338877
338877
|
a.extend({ MeshReflectorMaterialImpl: Yr });
|
|
338878
338878
|
const y2 = a.useThree(({ gl: e3 }) => e3), v2 = a.useThree(({ camera: e3 }) => e3), g2 = a.useThree(({ scene: e3 }) => e3), w2 = (o2 = Array.isArray(o2) ? o2 : [o2, o2])[0] + o2[1] > 0, z3 = o2[0], b2 = o2[1], E2 = S.useRef(null);
|
|
338879
338879
|
S.useImperativeHandle(x2, () => E2.current, []);
|
|
338880
|
-
const [
|
|
338880
|
+
const [C26] = S.useState(() => new n4.Plane()), [T2] = S.useState(() => new n4.Vector3()), [P2] = S.useState(() => new n4.Vector3()), [R2] = S.useState(() => new n4.Vector3()), [D2] = S.useState(() => new n4.Matrix4()), [F2] = S.useState(() => new n4.Vector3(0, 0, -1)), [k2] = S.useState(() => new n4.Vector4()), [_2] = S.useState(() => new n4.Vector3()), [A2] = S.useState(() => new n4.Vector3()), [L2] = S.useState(() => new n4.Vector4()), [I2] = S.useState(() => new n4.Matrix4()), [B2] = S.useState(() => new n4.PerspectiveCamera()), U2 = S.useCallback(() => {
|
|
338881
338881
|
var e3;
|
|
338882
338882
|
const t3 = E2.current.parent || (null == (e3 = E2.current) || null == (e3 = e3.__r3f.parent) ? void 0 : e3.object);
|
|
338883
338883
|
if (!t3) return;
|
|
338884
338884
|
if (P2.setFromMatrixPosition(t3.matrixWorld), R2.setFromMatrixPosition(v2.matrixWorld), D2.extractRotation(t3.matrixWorld), T2.set(0, 0, 1), T2.applyMatrix4(D2), P2.addScaledVector(T2, p2), _2.subVectors(P2, R2), _2.dot(T2) > 0) return;
|
|
338885
|
-
_2.reflect(T2).negate(), _2.add(P2), D2.extractRotation(v2.matrixWorld), F2.set(0, 0, -1), F2.applyMatrix4(D2), F2.add(R2), A2.subVectors(P2, F2), A2.reflect(T2).negate(), A2.add(P2), B2.position.copy(_2), B2.up.set(0, 1, 0), B2.up.applyMatrix4(D2), B2.up.reflect(T2), B2.lookAt(A2), B2.far = v2.far, B2.updateMatrixWorld(), B2.projectionMatrix.copy(v2.projectionMatrix), I2.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1), I2.multiply(B2.projectionMatrix), I2.multiply(B2.matrixWorldInverse), I2.multiply(t3.matrixWorld),
|
|
338885
|
+
_2.reflect(T2).negate(), _2.add(P2), D2.extractRotation(v2.matrixWorld), F2.set(0, 0, -1), F2.applyMatrix4(D2), F2.add(R2), A2.subVectors(P2, F2), A2.reflect(T2).negate(), A2.add(P2), B2.position.copy(_2), B2.up.set(0, 1, 0), B2.up.applyMatrix4(D2), B2.up.reflect(T2), B2.lookAt(A2), B2.far = v2.far, B2.updateMatrixWorld(), B2.projectionMatrix.copy(v2.projectionMatrix), I2.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1), I2.multiply(B2.projectionMatrix), I2.multiply(B2.matrixWorldInverse), I2.multiply(t3.matrixWorld), C26.setFromNormalAndCoplanarPoint(T2, P2), C26.applyMatrix4(B2.matrixWorldInverse), k2.set(C26.normal.x, C26.normal.y, C26.normal.z, C26.constant);
|
|
338886
338886
|
const r3 = B2.projectionMatrix;
|
|
338887
338887
|
L2.x = (Math.sign(k2.x) + r3.elements[8]) / r3.elements[0], L2.y = (Math.sign(k2.y) + r3.elements[9]) / r3.elements[5], L2.z = -1, L2.w = (1 + r3.elements[10]) / r3.elements[14], k2.multiplyScalar(2 / k2.dot(L2)), r3.elements[2] = k2.x, r3.elements[6] = k2.y, r3.elements[10] = k2.z + 1, r3.elements[14] = k2.w;
|
|
338888
338888
|
}, [v2, p2]), [V3, O2, N2, j2] = S.useMemo(() => {
|
|
@@ -339075,10 +339075,10 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339075
339075
|
var tn = S.forwardRef(({ buffer: e2, transmissionSampler: t2 = false, backside: r2 = false, side: n5 = T.FrontSide, transmission: o2 = 1, thickness: i3 = 0, backsideThickness: s2 = 0, backsideEnvMapIntensity: l2 = 1, samples: c2 = 10, resolution: u2, backsideResolution: d2, background: m2, anisotropy: f3, anisotropicBlur: p2, ...h2 }, x2) => {
|
|
339076
339076
|
a.extend({ MeshTransmissionMaterial: en });
|
|
339077
339077
|
const y2 = S.useRef(null), [v2] = S.useState(() => new Jr()), g2 = rt(d2 || u2), w2 = rt(u2);
|
|
339078
|
-
let z3, b2, E2,
|
|
339078
|
+
let z3, b2, E2, C26;
|
|
339079
339079
|
return a.useFrame((e3) => {
|
|
339080
339080
|
var a2;
|
|
339081
|
-
(y2.current.time = e3.clock.elapsedTime, y2.current.buffer !== w2.texture || t2) || (
|
|
339081
|
+
(y2.current.time = e3.clock.elapsedTime, y2.current.buffer !== w2.texture || t2) || (C26 = null == (a2 = y2.current.__r3f.parent) ? void 0 : a2.object, C26 && (E2 = e3.gl.toneMapping, z3 = e3.scene.background, b2 = y2.current.envMapIntensity, e3.gl.toneMapping = T.NoToneMapping, m2 && (e3.scene.background = m2), C26.material = v2, r2 && (e3.gl.setRenderTarget(g2), e3.gl.render(e3.scene, e3.camera), C26.material = y2.current, C26.material.buffer = g2.texture, C26.material.thickness = s2, C26.material.side = T.BackSide, C26.material.envMapIntensity = l2), e3.gl.setRenderTarget(w2), e3.gl.render(e3.scene, e3.camera), C26.material = y2.current, C26.material.thickness = i3, C26.material.side = n5, C26.material.buffer = w2.texture, C26.material.envMapIntensity = b2, e3.scene.background = z3, e3.gl.setRenderTarget(null), e3.gl.toneMapping = E2));
|
|
339082
339082
|
}), S.useImperativeHandle(x2, () => y2.current, []), S.createElement("meshTransmissionMaterial", M.default({ args: [c2, t2], ref: y2 }, h2, { buffer: e2 || w2.texture, _transmission: o2, anisotropicBlur: null != p2 ? p2 : f3, transmission: t2 ? o2 : 0, thickness: i3, side: n5 }));
|
|
339083
339083
|
});
|
|
339084
339084
|
var rn = S.forwardRef((e2, t2) => (a.extend({ DiscardMaterialImpl: Jr }), S.createElement("discardMaterialImpl", M.default({ ref: t2 }, e2))));
|
|
@@ -339387,11 +339387,11 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339387
339387
|
}
|
|
339388
339388
|
return Kn(l2, h2, z3, M2.texture, { backgroundBlurriness: null != c2 ? c2 : u2, backgroundIntensity: d2, backgroundRotation: m2, environmentIntensity: f3, environmentRotation: p2 });
|
|
339389
339389
|
}, [e2, E2, M2.texture, h2, z3, l2, i3, w2]);
|
|
339390
|
-
let
|
|
339390
|
+
let C26 = 1;
|
|
339391
339391
|
return a.useFrame(() => {
|
|
339392
|
-
if (i3 === 1 / 0 ||
|
|
339392
|
+
if (i3 === 1 / 0 || C26 < i3) {
|
|
339393
339393
|
const e3 = w2.autoClear;
|
|
339394
|
-
w2.autoClear = true, b2.current.update(w2, E2), w2.autoClear = e3,
|
|
339394
|
+
w2.autoClear = true, b2.current.update(w2, E2), w2.autoClear = e3, C26++;
|
|
339395
339395
|
}
|
|
339396
339396
|
}), S.createElement(S.Fragment, null, a.createPortal(S.createElement(S.Fragment, null, e2, S.createElement("cubeCamera", { ref: b2, args: [t2, r2, M2] }), x2 || v2 ? S.createElement(ea, { background: true, files: x2, preset: v2, path: y2, extensions: g2 }) : s2 ? S.createElement(Jn, { background: true, map: s2, extensions: g2 }) : null), E2));
|
|
339397
339397
|
}
|
|
@@ -339410,7 +339410,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339410
339410
|
var aa = S.forwardRef(({ scale: e2 = 10, frames: t2 = 1 / 0, opacity: r2 = 1, width: n5 = 1, height: o2 = 1, blur: i3 = 1, near: s2 = 0, far: l2 = 10, resolution: u2 = 512, smooth: d2 = true, color: m2 = "#000000", depthWrite: f3 = false, renderOrder: p2, ...h2 }, x2) => {
|
|
339411
339411
|
const y2 = S.useRef(null), v2 = a.useThree((e3) => e3.scene), g2 = a.useThree((e3) => e3.gl), w2 = S.useRef(null);
|
|
339412
339412
|
n5 *= Array.isArray(e2) ? e2[0] : e2 || 1, o2 *= Array.isArray(e2) ? e2[1] : e2 || 1;
|
|
339413
|
-
const [z3, b2, E2,
|
|
339413
|
+
const [z3, b2, E2, C26, P2, R2, D2] = S.useMemo(() => {
|
|
339414
339414
|
const e3 = new T.WebGLRenderTarget(u2, u2), t3 = new T.WebGLRenderTarget(u2, u2);
|
|
339415
339415
|
t3.texture.generateMipmaps = e3.texture.generateMipmaps = false;
|
|
339416
339416
|
const r3 = new T.PlaneGeometry(n5, o2).rotateX(Math.PI / 2), a2 = new T.Mesh(r3), i4 = new T.MeshDepthMaterial();
|
|
@@ -339420,7 +339420,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339420
339420
|
const s3 = new T.ShaderMaterial(c.HorizontalBlurShader), l3 = new T.ShaderMaterial(c.VerticalBlurShader);
|
|
339421
339421
|
return l3.depthTest = s3.depthTest = false, [e3, r3, i4, a2, s3, l3, t3];
|
|
339422
339422
|
}, [u2, n5, o2, e2, m2]), F2 = (e3) => {
|
|
339423
|
-
|
|
339423
|
+
C26.visible = true, C26.material = P2, P2.uniforms.tDiffuse.value = z3.texture, P2.uniforms.h.value = 1 * e3 / 256, g2.setRenderTarget(D2), g2.render(C26, w2.current), C26.material = R2, R2.uniforms.tDiffuse.value = D2.texture, R2.uniforms.v.value = 1 * e3 / 256, g2.setRenderTarget(z3), g2.render(C26, w2.current), C26.visible = false;
|
|
339424
339424
|
};
|
|
339425
339425
|
let k2, _2, A2 = 0;
|
|
339426
339426
|
return a.useFrame(() => {
|
|
@@ -339560,7 +339560,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339560
339560
|
var va = { minFilter: T.LinearMipmapLinearFilter, magFilter: T.LinearFilter, type: T.FloatType, generateMipmaps: true };
|
|
339561
339561
|
var ga = S.forwardRef(({ debug: e2, children: t2, frames: r2 = 1, ior: n5 = 1.1, color: o2 = "white", causticsOnly: i3 = false, backside: s2 = false, backsideIOR: l2 = 1.1, worldRadius: u2 = 0.3125, intensity: d2 = 0.05, resolution: m2 = 2024, lightSource: f3 = [5, 5, 5], ...p2 }, h2) => {
|
|
339562
339562
|
a.extend({ CausticsProjectionMaterial: ha });
|
|
339563
|
-
const x2 = S.useRef(null), y2 = S.useRef(null), v2 = S.useRef(null), g2 = S.useRef(null), w2 = a.useThree((e3) => e3.gl), z3 = lr(e2 && y2, T.CameraHelper), b2 = rt(m2, m2, ya), E2 = rt(m2, m2, ya),
|
|
339563
|
+
const x2 = S.useRef(null), y2 = S.useRef(null), v2 = S.useRef(null), g2 = S.useRef(null), w2 = a.useThree((e3) => e3.gl), z3 = lr(e2 && y2, T.CameraHelper), b2 = rt(m2, m2, ya), E2 = rt(m2, m2, ya), C26 = rt(m2, m2, va), P2 = rt(m2, m2, va), [R2] = S.useState(() => pa()), [D2] = S.useState(() => pa(T.BackSide)), [F2] = S.useState(() => new xa()), [k2] = S.useState(() => new c.FullScreenQuad(F2));
|
|
339564
339564
|
S.useLayoutEffect(() => {
|
|
339565
339565
|
x2.current.updateWorldMatrix(false, true);
|
|
339566
339566
|
});
|
|
@@ -339584,9 +339584,9 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339584
339584
|
const T2 = W2.map((e3, t4) => e3.add(H2[t4].copy(U2).multiplyScalar(-e3.y / U2.y))), _3 = T2.reduce((e3, t4) => e3.add(t4), A2.set(0, 0, 0)).divideScalar(T2.length), q2 = 2 * T2.map((e3) => Math.hypot(e3.x - _3.x, e3.z - _3.z)).reduce((e3, t4) => Math.max(e3, t4));
|
|
339585
339585
|
g2.current.scale.setScalar(q2), g2.current.position.copy(_3), e2 && (null == (a2 = z3.current) || a2.update()), D2.viewMatrix.value = R2.viewMatrix.value = y2.current.matrixWorldInverse;
|
|
339586
339586
|
const X2 = L2.setFromProjectionMatrix(I2.multiplyMatrices(y2.current.projectionMatrix, y2.current.matrixWorldInverse)).planes[4];
|
|
339587
|
-
F2.cameraMatrixWorld = y2.current.matrixWorld, F2.cameraProjectionMatrixInv = y2.current.projectionMatrixInverse, F2.lightDir = V3, F2.lightPlaneNormal = X2.normal, F2.lightPlaneConstant = X2.constant, F2.near = y2.current.near, F2.far = y2.current.far, F2.resolution = m2, F2.size = c2, F2.intensity = d2, F2.worldRadius = u2, v2.current.visible = true, w2.setRenderTarget(b2), w2.clear(), v2.current.overrideMaterial = R2, w2.render(v2.current, y2.current), w2.setRenderTarget(E2), w2.clear(), s2 && (v2.current.overrideMaterial = D2, w2.render(v2.current, y2.current)), v2.current.overrideMaterial = null, F2.ior = n5, g2.current.material.lightProjMatrix = y2.current.projectionMatrix, g2.current.material.lightViewMatrix = y2.current.matrixWorldInverse, F2.normalTexture = b2.texture, F2.depthTexture = b2.depthTexture, w2.setRenderTarget(
|
|
339587
|
+
F2.cameraMatrixWorld = y2.current.matrixWorld, F2.cameraProjectionMatrixInv = y2.current.projectionMatrixInverse, F2.lightDir = V3, F2.lightPlaneNormal = X2.normal, F2.lightPlaneConstant = X2.constant, F2.near = y2.current.near, F2.far = y2.current.far, F2.resolution = m2, F2.size = c2, F2.intensity = d2, F2.worldRadius = u2, v2.current.visible = true, w2.setRenderTarget(b2), w2.clear(), v2.current.overrideMaterial = R2, w2.render(v2.current, y2.current), w2.setRenderTarget(E2), w2.clear(), s2 && (v2.current.overrideMaterial = D2, w2.render(v2.current, y2.current)), v2.current.overrideMaterial = null, F2.ior = n5, g2.current.material.lightProjMatrix = y2.current.projectionMatrix, g2.current.material.lightViewMatrix = y2.current.matrixWorldInverse, F2.normalTexture = b2.texture, F2.depthTexture = b2.depthTexture, w2.setRenderTarget(C26), w2.clear(), k2.render(w2), F2.ior = l2, F2.normalTexture = E2.texture, F2.depthTexture = E2.depthTexture, w2.setRenderTarget(P2), w2.clear(), s2 && k2.render(w2), w2.setRenderTarget(null), i3 && (v2.current.visible = false);
|
|
339588
339588
|
}
|
|
339589
|
-
}), S.useImperativeHandle(h2, () => x2.current, []), S.createElement("group", M.default({ ref: x2 }, p2), S.createElement("scene", { ref: v2 }, S.createElement("orthographicCamera", { ref: y2, up: [0, 1, 0] }), t2), S.createElement("mesh", { renderOrder: 2, ref: g2, "rotation-x": -Math.PI / 2 }, S.createElement("planeGeometry", null), S.createElement("causticsProjectionMaterial", { transparent: true, color: o2, causticsTexture:
|
|
339589
|
+
}), S.useImperativeHandle(h2, () => x2.current, []), S.createElement("group", M.default({ ref: x2 }, p2), S.createElement("scene", { ref: v2 }, S.createElement("orthographicCamera", { ref: y2, up: [0, 1, 0] }), t2), S.createElement("mesh", { renderOrder: 2, ref: g2, "rotation-x": -Math.PI / 2 }, S.createElement("planeGeometry", null), S.createElement("causticsProjectionMaterial", { transparent: true, color: o2, causticsTexture: C26.texture, causticsTextureB: P2.texture, blending: T.CustomBlending, blendSrc: T.OneFactor, blendDst: T.SrcAlphaFactor, depthWrite: false }), e2 && S.createElement(ke, null, S.createElement("lineBasicMaterial", { color: "#ffff00", toneMapped: false }))));
|
|
339590
339590
|
});
|
|
339591
339591
|
var wa = class extends T.ShaderMaterial {
|
|
339592
339592
|
constructor() {
|
|
@@ -339768,9 +339768,9 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
339768
339768
|
const e3 = Math.min(i3, void 0 !== o2 ? o2 : i3, p2.current.length);
|
|
339769
339769
|
f3.current.count = e3, br(f3.current.instanceMatrix, { start: 0, count: 16 * e3 }), f3.current.instanceColor && br(f3.current.instanceColor, { start: 0, count: 3 * e3 }), br(f3.current.geometry.attributes.cloudOpacity, { start: 0, count: e3 });
|
|
339770
339770
|
});
|
|
339771
|
-
let
|
|
339772
|
-
const T2 = Math.max(
|
|
339773
|
-
return
|
|
339771
|
+
let C26 = [null !== (u2 = y2.image.width) && void 0 !== u2 ? u2 : 1, null !== (d2 = y2.image.height) && void 0 !== d2 ? d2 : 1];
|
|
339772
|
+
const T2 = Math.max(C26[0], C26[1]);
|
|
339773
|
+
return C26 = [C26[0] / T2, C26[1] / T2], S.createElement("group", M.default({ ref: c2 }, l2), S.createElement(Ua.Provider, { value: p2 }, e2, S.createElement("instancedMesh", { matrixAutoUpdate: false, ref: f3, args: [null, null, i3], frustumCulled: s2 }, S.createElement("instancedBufferAttribute", { usage: n4.DynamicDrawUsage, attach: "instanceColor", args: [x2, 3] }), S.createElement("planeGeometry", { args: [...C26] }, S.createElement("instancedBufferAttribute", { usage: n4.DynamicDrawUsage, attach: "attributes-cloudOpacity", args: [h2, 1] })), S.createElement("cloudMaterial", { key: t2.name, map: y2, transparent: true, depthWrite: false }))));
|
|
339774
339774
|
});
|
|
339775
339775
|
var Oa = S.forwardRef(({ opacity: e2 = 1, speed: t2 = 0, bounds: r2 = [5, 1, 1], segments: o2 = 20, color: i3 = "#ffffff", fade: s2 = 10, volume: l2 = 6, smallestVolume: c2 = 0.25, distribute: u2 = null, growth: d2 = 4, concentrate: m2 = "inside", seed: f3 = Math.random(), ...p2 }, h2) => {
|
|
339776
339776
|
function x2() {
|
|
@@ -340123,13 +340123,13 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340123
340123
|
}, t2), S.createElement(S.Fragment, null, r2);
|
|
340124
340124
|
}
|
|
340125
340125
|
var ko = S.forwardRef(({ children: e2, compute: t2, renderPriority: r2 = -1, eventPriority: n5 = 0, frames: o2 = 1 / 0, stencilBuffer: i3 = false, depthBuffer: s2 = true, generateMipmaps: l2 = false, resolution: c2 = 896, near: u2 = 0.1, far: d2 = 1e3, flip: m2 = false, position: f3, rotation: p2, scale: h2, quaternion: x2, matrix: y2, matrixAutoUpdate: v2, ...g2 }, w2) => {
|
|
340126
|
-
const { size: z3, viewport: b2 } = a.useThree(), E2 = S.useRef(null),
|
|
340126
|
+
const { size: z3, viewport: b2 } = a.useThree(), E2 = S.useRef(null), C26 = S.useMemo(() => {
|
|
340127
340127
|
const e3 = new T.WebGLCubeRenderTarget(Math.max((c2 || z3.width) * b2.dpr, (c2 || z3.height) * b2.dpr), { stencilBuffer: i3, depthBuffer: s2, generateMipmaps: l2 });
|
|
340128
340128
|
return e3.texture.isRenderTargetTexture = !m2, e3.texture.flipY = true, e3.texture.type = T.HalfFloatType, e3;
|
|
340129
340129
|
}, [c2, m2]);
|
|
340130
|
-
S.useEffect(() => () =>
|
|
340130
|
+
S.useEffect(() => () => C26.dispose(), [C26]);
|
|
340131
340131
|
const [P2] = S.useState(() => new T.Scene());
|
|
340132
|
-
return S.useImperativeHandle(w2, () => ({ scene: P2, fbo:
|
|
340132
|
+
return S.useImperativeHandle(w2, () => ({ scene: P2, fbo: C26, camera: E2.current }), [C26]), S.createElement(S.Fragment, null, a.createPortal(S.createElement(_o, { renderPriority: r2, frames: o2, camera: E2 }, e2, S.createElement("group", { onPointerOver: () => null })), P2, { events: { compute: t2, priority: n5 } }), S.createElement("primitive", M.default({ object: C26.texture }, g2)), S.createElement("cubeCamera", { ref: E2, args: [u2, d2, C26], position: f3, rotation: p2, scale: h2, quaternion: x2, matrix: y2, matrixAutoUpdate: v2 }));
|
|
340133
340133
|
});
|
|
340134
340134
|
function _o({ frames: e2, renderPriority: t2, children: r2, camera: n5 }) {
|
|
340135
340135
|
let o2 = 0;
|
|
@@ -340354,12 +340354,12 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340354
340354
|
var Jo = new T.Vector3(0, 1, 0);
|
|
340355
340355
|
var ei = new T.Matrix4();
|
|
340356
340356
|
var ti = ({ direction: e2, axis: t2 }) => {
|
|
340357
|
-
const { translation: r2, translationLimits: n5, annotations: o2, annotationsClass: i3, depthTest: s2, scale: l2, lineWidth: c2, fixed: u2, axisColors: d2, hoveredColor: m2, opacity: f3, renderOrder: p2, onDragStart: h2, onDrag: x2, onDragEnd: y2, userData: v2 } = S.useContext(Yo), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(0), [M2,
|
|
340357
|
+
const { translation: r2, translationLimits: n5, annotations: o2, annotationsClass: i3, depthTest: s2, scale: l2, lineWidth: c2, fixed: u2, axisColors: d2, hoveredColor: m2, opacity: f3, renderOrder: p2, onDragStart: h2, onDrag: x2, onDragEnd: y2, userData: v2 } = S.useContext(Yo), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(0), [M2, C26] = S.useState(false), P2 = S.useCallback((n6) => {
|
|
340358
340358
|
o2 && (w2.current.innerText = `${r2.current[t2].toFixed(2)}`, w2.current.style.display = "block"), n6.stopPropagation();
|
|
340359
340359
|
const a2 = new T.Matrix4().extractRotation(z3.current.matrixWorld), i4 = n6.point.clone(), s3 = new T.Vector3().setFromMatrixPosition(z3.current.matrixWorld), l3 = e2.clone().applyMatrix4(a2).normalize();
|
|
340360
340360
|
b2.current = { clickPoint: i4, dir: l3 }, E2.current = r2.current[t2], h2({ component: "Arrow", axis: t2, origin: s3, directions: [l3] }), g2 && (g2.enabled = false), n6.target.setPointerCapture(n6.pointerId);
|
|
340361
340361
|
}, [o2, e2, g2, h2, r2, t2]), R2 = S.useCallback((e3) => {
|
|
340362
|
-
if (e3.stopPropagation(), M2 ||
|
|
340362
|
+
if (e3.stopPropagation(), M2 || C26(true), b2.current) {
|
|
340363
340363
|
const { clickPoint: a2, dir: i4 } = b2.current, [s3, l3] = (null == n5 ? void 0 : n5[t2]) || [void 0, void 0];
|
|
340364
340364
|
let c3 = ((e4, t3, r3, n6) => {
|
|
340365
340365
|
const a3 = t3.dot(t3), o3 = t3.dot(e4) - t3.dot(r3), i5 = t3.dot(n6);
|
|
@@ -340370,7 +340370,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340370
340370
|
}, [o2, x2, M2, r2, n5, t2]), D2 = S.useCallback((e3) => {
|
|
340371
340371
|
o2 && (w2.current.style.display = "none"), e3.stopPropagation(), b2.current = null, y2(), g2 && (g2.enabled = true), e3.target.releasePointerCapture(e3.pointerId);
|
|
340372
340372
|
}, [o2, g2, y2]), F2 = S.useCallback((e3) => {
|
|
340373
|
-
e3.stopPropagation(),
|
|
340373
|
+
e3.stopPropagation(), C26(false);
|
|
340374
340374
|
}, []), { cylinderLength: k2, coneWidth: _2, coneLength: A2, matrixL: L2 } = S.useMemo(() => {
|
|
340375
340375
|
const t3 = u2 ? c2 / l2 * 1.6 : l2 / 20, r3 = u2 ? 0.2 : l2 / 5, n6 = u2 ? 1 - r3 : l2 - r3, a2 = new T.Quaternion().setFromUnitVectors(Jo, e2.clone().normalize());
|
|
340376
340376
|
return { cylinderLength: n6, coneWidth: t3, coneLength: r3, matrixL: new T.Matrix4().makeRotationFromQuaternion(a2) };
|
|
@@ -340392,12 +340392,12 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340392
340392
|
var li = new T.Ray();
|
|
340393
340393
|
var ci = new T.Vector3();
|
|
340394
340394
|
var ui = ({ dir1: e2, dir2: t2, axis: r2 }) => {
|
|
340395
|
-
const { rotationLimits: n5, annotations: o2, annotationsClass: i3, depthTest: s2, scale: l2, lineWidth: c2, fixed: u2, axisColors: d2, hoveredColor: m2, renderOrder: f3, opacity: p2, onDragStart: h2, onDrag: x2, onDragEnd: y2, userData: v2 } = S.useContext(Yo), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(0), E2 = S.useRef(0), M2 = S.useRef(null), [
|
|
340395
|
+
const { rotationLimits: n5, annotations: o2, annotationsClass: i3, depthTest: s2, scale: l2, lineWidth: c2, fixed: u2, axisColors: d2, hoveredColor: m2, renderOrder: f3, opacity: p2, onDragStart: h2, onDrag: x2, onDragEnd: y2, userData: v2 } = S.useContext(Yo), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(0), E2 = S.useRef(0), M2 = S.useRef(null), [C26, P2] = S.useState(false), R2 = S.useCallback((e3) => {
|
|
340396
340396
|
o2 && (w2.current.innerText = `${ai(E2.current).toFixed(0)}\xBA`, w2.current.style.display = "block"), e3.stopPropagation();
|
|
340397
340397
|
const t3 = e3.point.clone(), n6 = new T.Vector3().setFromMatrixPosition(z3.current.matrixWorld), a2 = new T.Vector3().setFromMatrixColumn(z3.current.matrixWorld, 0).normalize(), i4 = new T.Vector3().setFromMatrixColumn(z3.current.matrixWorld, 1).normalize(), s3 = new T.Vector3().setFromMatrixColumn(z3.current.matrixWorld, 2).normalize(), l3 = new T.Plane().setFromNormalAndCoplanarPoint(s3, n6);
|
|
340398
340398
|
M2.current = { clickPoint: t3, origin: n6, e1: a2, e2: i4, normal: s3, plane: l3 }, h2({ component: "Rotator", axis: r2, origin: n6, directions: [a2, i4, s3] }), g2 && (g2.enabled = false), e3.target.setPointerCapture(e3.pointerId);
|
|
340399
340399
|
}, [o2, g2, h2, r2]), D2 = S.useCallback((e3) => {
|
|
340400
|
-
if (e3.stopPropagation(),
|
|
340400
|
+
if (e3.stopPropagation(), C26 || P2(true), M2.current) {
|
|
340401
340401
|
const { clickPoint: t3, origin: a2, e1: i4, e2: s3, normal: l3, plane: c3 } = M2.current, [u3, d3] = (null == n5 ? void 0 : n5[r2]) || [void 0, void 0];
|
|
340402
340402
|
li.copy(e3.ray), li.intersectPlane(c3, ci), li.direction.negate(), li.intersectPlane(c3, ci);
|
|
340403
340403
|
let m3 = ((e4, t4, r3, n6, a3) => {
|
|
@@ -340407,7 +340407,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340407
340407
|
})(t3, ci, a2, i4, s3), f4 = ai(m3);
|
|
340408
340408
|
e3.shiftKey && (f4 = 10 * Math.round(f4 / 10), m3 = ((e4) => e4 * Math.PI / 180)(f4)), void 0 !== u3 && void 0 !== d3 && d3 - u3 < 2 * Math.PI ? (m3 = oi(m3), m3 = m3 > Math.PI ? m3 - 2 * Math.PI : m3, m3 = T.MathUtils.clamp(m3, u3 - b2.current, d3 - b2.current), E2.current = b2.current + m3) : (E2.current = oi(b2.current + m3), E2.current = E2.current > Math.PI ? E2.current - 2 * Math.PI : E2.current), o2 && (f4 = ai(E2.current), w2.current.innerText = `${f4.toFixed(0)}\xBA`), ii.makeRotationAxis(l3, m3), si.copy(a2).applyMatrix4(ii).sub(a2).negate(), ii.setPosition(si), x2(ii);
|
|
340409
340409
|
}
|
|
340410
|
-
}, [o2, x2,
|
|
340410
|
+
}, [o2, x2, C26, n5, r2]), F2 = S.useCallback((e3) => {
|
|
340411
340411
|
o2 && (w2.current.style.display = "none"), e3.stopPropagation(), b2.current = E2.current, M2.current = null, y2(), g2 && (g2.enabled = true), e3.target.releasePointerCapture(e3.pointerId);
|
|
340412
340412
|
}, [o2, g2, y2]), k2 = S.useCallback((e3) => {
|
|
340413
340413
|
e3.stopPropagation(), P2(false);
|
|
@@ -340422,16 +340422,16 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340422
340422
|
}
|
|
340423
340423
|
return e3;
|
|
340424
340424
|
}, [A2]);
|
|
340425
|
-
return S.createElement("group", { ref: z3, onPointerDown: R2, onPointerMove: D2, onPointerUp: F2, onPointerOut: k2, matrix: _2, matrixAutoUpdate: false }, o2 && S.createElement(j, { position: [A2, A2, 0] }, S.createElement("div", { style: { display: "none", background: "#151520", color: "white", padding: "6px 8px", borderRadius: 7, whiteSpace: "nowrap" }, className: i3, ref: w2 })), S.createElement(ce, { points: L2, lineWidth: 4 * c2, visible: false, userData: v2 }), S.createElement(ce, { transparent: true, raycast: () => null, depthTest: s2, points: L2, lineWidth: c2, side: T.DoubleSide, color:
|
|
340425
|
+
return S.createElement("group", { ref: z3, onPointerDown: R2, onPointerMove: D2, onPointerUp: F2, onPointerOut: k2, matrix: _2, matrixAutoUpdate: false }, o2 && S.createElement(j, { position: [A2, A2, 0] }, S.createElement("div", { style: { display: "none", background: "#151520", color: "white", padding: "6px 8px", borderRadius: 7, whiteSpace: "nowrap" }, className: i3, ref: w2 })), S.createElement(ce, { points: L2, lineWidth: 4 * c2, visible: false, userData: v2 }), S.createElement(ce, { transparent: true, raycast: () => null, depthTest: s2, points: L2, lineWidth: c2, side: T.DoubleSide, color: C26 ? m2 : d2[r2], opacity: p2, polygonOffset: true, polygonOffsetFactor: -10, renderOrder: f3, fog: false }));
|
|
340426
340426
|
};
|
|
340427
340427
|
var di = new T.Ray();
|
|
340428
340428
|
var mi = new T.Vector3();
|
|
340429
340429
|
var fi = new T.Matrix4();
|
|
340430
340430
|
var pi = ({ dir1: e2, dir2: t2, axis: r2 }) => {
|
|
340431
|
-
const { translation: n5, translationLimits: o2, annotations: i3, annotationsClass: s2, depthTest: l2, scale: c2, lineWidth: u2, fixed: d2, axisColors: m2, hoveredColor: f3, opacity: p2, renderOrder: h2, onDragStart: x2, onDrag: y2, onDragEnd: v2, userData: g2 } = S.useContext(Yo), w2 = a.useThree((e3) => e3.controls), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(null), M2 = S.useRef(0),
|
|
340431
|
+
const { translation: n5, translationLimits: o2, annotations: i3, annotationsClass: s2, depthTest: l2, scale: c2, lineWidth: u2, fixed: d2, axisColors: m2, hoveredColor: f3, opacity: p2, renderOrder: h2, onDragStart: x2, onDrag: y2, onDragEnd: v2, userData: g2 } = S.useContext(Yo), w2 = a.useThree((e3) => e3.controls), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(null), M2 = S.useRef(0), C26 = S.useRef(0), [P2, R2] = S.useState(false), D2 = S.useCallback((e3) => {
|
|
340432
340432
|
i3 && (z3.current.innerText = `${n5.current[(r2 + 1) % 3].toFixed(2)}, ${n5.current[(r2 + 2) % 3].toFixed(2)}`, z3.current.style.display = "block"), e3.stopPropagation();
|
|
340433
340433
|
const t3 = e3.point.clone(), a2 = new T.Vector3().setFromMatrixPosition(b2.current.matrixWorld), o3 = new T.Vector3().setFromMatrixColumn(b2.current.matrixWorld, 0).normalize(), s3 = new T.Vector3().setFromMatrixColumn(b2.current.matrixWorld, 1).normalize(), l3 = new T.Vector3().setFromMatrixColumn(b2.current.matrixWorld, 2).normalize(), c3 = new T.Plane().setFromNormalAndCoplanarPoint(l3, a2);
|
|
340434
|
-
E2.current = { clickPoint: t3, e1: o3, e2: s3, plane: c3 }, M2.current = n5.current[(r2 + 1) % 3],
|
|
340434
|
+
E2.current = { clickPoint: t3, e1: o3, e2: s3, plane: c3 }, M2.current = n5.current[(r2 + 1) % 3], C26.current = n5.current[(r2 + 2) % 3], x2({ component: "Slider", axis: r2, origin: a2, directions: [o3, s3, l3] }), w2 && (w2.enabled = false), e3.target.setPointerCapture(e3.pointerId);
|
|
340435
340435
|
}, [i3, w2, x2, r2]), F2 = S.useCallback((e3) => {
|
|
340436
340436
|
if (e3.stopPropagation(), P2 || R2(true), E2.current) {
|
|
340437
340437
|
const { clickPoint: t3, e1: a2, e2: s3, plane: l3 } = E2.current, [c3, u3] = (null == o2 ? void 0 : o2[(r2 + 1) % 3]) || [void 0, void 0], [d3, m3] = (null == o2 ? void 0 : o2[(r2 + 2) % 3]) || [void 0, void 0];
|
|
@@ -340440,7 +340440,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340440
340440
|
const n6 = Math.abs(e4.x) >= Math.abs(e4.y) && Math.abs(e4.x) >= Math.abs(e4.z) ? 0 : Math.abs(e4.y) >= Math.abs(e4.x) && Math.abs(e4.y) >= Math.abs(e4.z) ? 1 : 2, a3 = [0, 1, 2].sort((e5, r4) => Math.abs(t4.getComponent(r4)) - Math.abs(t4.getComponent(e5))), o3 = n6 === a3[0] ? a3[1] : a3[0], i4 = e4.getComponent(n6), s4 = e4.getComponent(o3), l4 = t4.getComponent(n6), c4 = t4.getComponent(o3), u4 = r3.getComponent(n6), d4 = (r3.getComponent(o3) - u4 * (s4 / i4)) / (c4 - l4 * (s4 / i4));
|
|
340441
340441
|
return [(u4 - d4 * l4) / i4, d4];
|
|
340442
340442
|
})(a2, s3, mi);
|
|
340443
|
-
void 0 !== c3 && (f4 = Math.max(f4, c3 - M2.current)), void 0 !== u3 && (f4 = Math.min(f4, u3 - M2.current)), void 0 !== d3 && (p3 = Math.max(p3, d3 -
|
|
340443
|
+
void 0 !== c3 && (f4 = Math.max(f4, c3 - M2.current)), void 0 !== u3 && (f4 = Math.min(f4, u3 - M2.current)), void 0 !== d3 && (p3 = Math.max(p3, d3 - C26.current)), void 0 !== m3 && (p3 = Math.min(p3, m3 - C26.current)), n5.current[(r2 + 1) % 3] = M2.current + f4, n5.current[(r2 + 2) % 3] = C26.current + p3, i3 && (z3.current.innerText = `${n5.current[(r2 + 1) % 3].toFixed(2)}, ${n5.current[(r2 + 2) % 3].toFixed(2)}`), fi.makeTranslation(f4 * a2.x + p3 * s3.x, f4 * a2.y + p3 * s3.y, f4 * a2.z + p3 * s3.z), y2(fi);
|
|
340444
340444
|
}
|
|
340445
340445
|
}, [i3, y2, P2, n5, o2, r2]), k2 = S.useCallback((e3) => {
|
|
340446
340446
|
i3 && (z3.current.style.display = "none"), e3.stopPropagation(), E2.current = null, v2(), w2 && (w2.enabled = true), e3.target.releasePointerCapture(e3.pointerId);
|
|
@@ -340458,13 +340458,13 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340458
340458
|
var vi = new T.Vector3();
|
|
340459
340459
|
var gi = new T.Matrix4();
|
|
340460
340460
|
var wi = ({ direction: e2, axis: t2 }) => {
|
|
340461
|
-
const { scaleLimits: r2, annotations: n5, annotationsClass: o2, depthTest: i3, scale: s2, lineWidth: l2, fixed: c2, axisColors: u2, hoveredColor: d2, opacity: m2, renderOrder: f3, onDragStart: p2, onDrag: h2, onDragEnd: x2, userData: y2 } = S.useContext(Yo), v2 = a.useThree((e3) => e3.size), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(1), M2 = S.useRef(1),
|
|
340461
|
+
const { scaleLimits: r2, annotations: n5, annotationsClass: o2, depthTest: i3, scale: s2, lineWidth: l2, fixed: c2, axisColors: u2, hoveredColor: d2, opacity: m2, renderOrder: f3, onDragStart: p2, onDrag: h2, onDragEnd: x2, userData: y2 } = S.useContext(Yo), v2 = a.useThree((e3) => e3.size), g2 = a.useThree((e3) => e3.controls), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(1), M2 = S.useRef(1), C26 = S.useRef(null), [P2, R2] = S.useState(false), D2 = c2 ? 1.2 : 1.2 * s2, F2 = S.useCallback((r3) => {
|
|
340462
340462
|
n5 && (w2.current.innerText = `${M2.current.toFixed(2)}`, w2.current.style.display = "block"), r3.stopPropagation();
|
|
340463
340463
|
const a2 = new T.Matrix4().extractRotation(z3.current.matrixWorld), o3 = r3.point.clone(), i4 = new T.Vector3().setFromMatrixPosition(z3.current.matrixWorld), l3 = e2.clone().applyMatrix4(a2).normalize(), u3 = z3.current.matrixWorld.clone(), d3 = u3.clone().invert(), m3 = c2 ? 1 / ie(z3.current.getWorldPosition(hi), s2, r3.camera, v2) : 1;
|
|
340464
|
-
|
|
340464
|
+
C26.current = { clickPoint: o3, dir: l3, mPLG: u3, mPLGInv: d3, offsetMultiplier: m3 }, p2({ component: "Sphere", axis: t2, origin: i4, directions: [l3] }), g2 && (g2.enabled = false), r3.target.setPointerCapture(r3.pointerId);
|
|
340465
340465
|
}, [n5, g2, e2, p2, t2, c2, s2, v2]), k2 = S.useCallback((e3) => {
|
|
340466
|
-
if (e3.stopPropagation(), P2 || R2(true),
|
|
340467
|
-
const { clickPoint: a2, dir: o3, mPLG: i4, mPLGInv: l3, offsetMultiplier: u3 } =
|
|
340466
|
+
if (e3.stopPropagation(), P2 || R2(true), C26.current) {
|
|
340467
|
+
const { clickPoint: a2, dir: o3, mPLG: i4, mPLGInv: l3, offsetMultiplier: u3 } = C26.current, [d3, m3] = (null == r2 ? void 0 : r2[t2]) || [1e-5, void 0], f4 = ((e4, t3, r3, n6) => {
|
|
340468
340468
|
const a3 = t3.dot(t3), o4 = t3.dot(e4) - t3.dot(r3), i5 = t3.dot(n6);
|
|
340469
340469
|
return 0 === i5 ? -o4 / a3 : (hi.copy(n6).multiplyScalar(a3 / i5).sub(t3), xi.copy(n6).multiplyScalar(o4 / i5).add(r3).sub(e4), -hi.dot(xi) / hi.dot(hi));
|
|
340470
340470
|
})(a2, o3, e3.ray.origin, e3.ray.direction), p3 = f4 * u3, x3 = c2 ? p3 : p3 / s2;
|
|
@@ -340472,7 +340472,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340472
340472
|
e3.shiftKey && (y3 = Math.round(10 * y3) / 10), y3 = Math.max(y3, d3 / E2.current), void 0 !== m3 && (y3 = Math.min(y3, m3 / E2.current)), M2.current = E2.current * y3, b2.current.position.set(0, D2 + p3, 0), n5 && (w2.current.innerText = `${M2.current.toFixed(2)}`), vi.set(1, 1, 1), vi.setComponent(t2, y3), gi.makeScale(vi.x, vi.y, vi.z).premultiply(i4).multiply(l3), h2(gi);
|
|
340473
340473
|
}
|
|
340474
340474
|
}, [n5, D2, h2, P2, r2, t2]), _2 = S.useCallback((e3) => {
|
|
340475
|
-
n5 && (w2.current.style.display = "none"), e3.stopPropagation(), E2.current = M2.current,
|
|
340475
|
+
n5 && (w2.current.style.display = "none"), e3.stopPropagation(), E2.current = M2.current, C26.current = null, b2.current.position.set(0, D2, 0), x2(), g2 && (g2.enabled = true), e3.target.releasePointerCapture(e3.pointerId);
|
|
340476
340476
|
}, [n5, g2, x2, D2]), A2 = S.useCallback((e3) => {
|
|
340477
340477
|
e3.stopPropagation(), R2(false);
|
|
340478
340478
|
}, []), { radius: L2, matrixL: I2 } = S.useMemo(() => {
|
|
@@ -340500,7 +340500,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340500
340500
|
var Bi = new T.Vector3(1, 0, 0);
|
|
340501
340501
|
var Ui = new T.Vector3(0, 1, 0);
|
|
340502
340502
|
var Vi = new T.Vector3(0, 0, 1);
|
|
340503
|
-
var Oi = S.forwardRef(({ enabled: e2 = true, matrix: t2, onDragStart: r2, onDrag: n5, onDragEnd: o2, autoTransform: i3 = true, anchor: s2, disableAxes: l2 = false, disableSliders: c2 = false, disableRotations: u2 = false, disableScaling: d2 = false, activeAxes: m2 = [true, true, true], offset: f3 = [0, 0, 0], rotation: p2 = [0, 0, 0], scale: h2 = 1, lineWidth: x2 = 4, fixed: y2 = false, translationLimits: v2, rotationLimits: g2, scaleLimits: w2, depthTest: z3 = true, renderOrder: b2 = 500, axisColors: E2 = ["#ff2060", "#20df80", "#2080ff"], hoveredColor:
|
|
340503
|
+
var Oi = S.forwardRef(({ enabled: e2 = true, matrix: t2, onDragStart: r2, onDrag: n5, onDragEnd: o2, autoTransform: i3 = true, anchor: s2, disableAxes: l2 = false, disableSliders: c2 = false, disableRotations: u2 = false, disableScaling: d2 = false, activeAxes: m2 = [true, true, true], offset: f3 = [0, 0, 0], rotation: p2 = [0, 0, 0], scale: h2 = 1, lineWidth: x2 = 4, fixed: y2 = false, translationLimits: v2, rotationLimits: g2, scaleLimits: w2, depthTest: z3 = true, renderOrder: b2 = 500, axisColors: E2 = ["#ff2060", "#20df80", "#2080ff"], hoveredColor: C26 = "#ffff40", annotations: P2 = false, annotationsClass: R2, opacity: D2 = 1, visible: F2 = true, userData: k2, children: _2, ...A2 }, L2) => {
|
|
340504
340504
|
const I2 = a.useThree((e3) => e3.invalidate), B2 = S.useRef(null), U2 = S.useRef(null), V3 = S.useRef(null), O2 = S.useRef(null), N2 = S.useRef([0, 0, 0]), j2 = S.useRef(new T.Vector3(1, 1, 1)), W2 = S.useRef(new T.Vector3(1, 1, 1));
|
|
340505
340505
|
S.useLayoutEffect(() => {
|
|
340506
340506
|
s2 && (O2.current.updateWorldMatrix(true, true), Mi.copy(O2.current.matrixWorld).invert(), Di.makeEmpty(), O2.current.traverse((e3) => {
|
|
@@ -340513,7 +340513,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340513
340513
|
Ei.copy(B2.current.matrixWorld), Mi.copy(Ei).invert(), Si.copy(bi).premultiply(e3), Ci.copy(Si).premultiply(Mi), Ti.copy(zi).invert(), Pi.copy(Ci).multiply(Ti), i3 && U2.current.matrix.copy(Ci), n5 && n5(Ci, Pi, Si, e3), I2();
|
|
340514
340514
|
}, onDragEnd: () => {
|
|
340515
340515
|
o2 && o2(), I2();
|
|
340516
|
-
}, translation: N2, translationLimits: v2, rotationLimits: g2, axisColors: E2, hoveredColor:
|
|
340516
|
+
}, translation: N2, translationLimits: v2, rotationLimits: g2, axisColors: E2, hoveredColor: C26, opacity: D2, scale: h2, lineWidth: x2, fixed: y2, depthTest: z3, renderOrder: b2, userData: k2, annotations: P2, annotationsClass: R2 }), [r2, n5, o2, N2, v2, g2, w2, z3, h2, x2, y2, ...E2, C26, D2, k2, i3, P2, R2]), H2 = new T.Vector3();
|
|
340517
340517
|
return a.useFrame((e3) => {
|
|
340518
340518
|
if (y2) {
|
|
340519
340519
|
const t3 = ie(V3.current.getWorldPosition(H2), h2, e3.camera, e3.size);
|
|
@@ -340544,7 +340544,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340544
340544
|
var Hi = S.forwardRef(({ points: e2 = Xi.SAMPLE_FACELANDMARKER_RESULT.faceLandmarks[0], face: t2, facialTransformationMatrix: r2, faceBlendshapes: n5, offset: o2, offsetScalar: i3 = 80, width: s2, height: l2, depth: c2 = 1, verticalTri: u2 = [159, 386, 152], origin: d2, eyes: m2 = true, eyesAsOrigin: f3 = false, debug: p2 = false, children: h2, ...x2 }, y2) => {
|
|
340545
340545
|
var v2;
|
|
340546
340546
|
t2 && (e2 = t2.keypoints, console.warn("Facemesh `face` prop is deprecated: use `points` instead"));
|
|
340547
|
-
const g2 = S.useRef(null), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(null), M2 = S.useRef(null),
|
|
340547
|
+
const g2 = S.useRef(null), w2 = S.useRef(null), z3 = S.useRef(null), b2 = S.useRef(null), E2 = S.useRef(null), M2 = S.useRef(null), C26 = S.useRef(null), [P2] = S.useState(() => new T.Vector3()), [R2] = S.useState(() => new T.Object3D()), [D2] = S.useState(() => new T.Quaternion()), [F2] = S.useState(() => new T.Vector3()), { invalidate: k2 } = a.useThree();
|
|
340548
340548
|
S.useEffect(() => {
|
|
340549
340549
|
var e3;
|
|
340550
340550
|
null == (e3 = E2.current) || e3.geometry.setIndex(Xi.TRIANGULATION);
|
|
@@ -340558,12 +340558,12 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340558
340558
|
(h3.setFromPoints(e2), h3.setDrawRange(0, Xi.TRIANGULATION.length), r2) ? (R2.matrix.fromArray(r2.data), R2.matrix.decompose(R2.position, R2.quaternion, R2.scale), R2.rotation.y *= -1, R2.rotation.z *= -1, D2.setFromEuler(R2.rotation), o2 ? (R2.position.y *= -1, R2.position.z *= -1, null == (x3 = g2.current) || x3.position.copy(R2.position.divideScalar(i3))) : null == (y3 = g2.current) || y3.position.set(0, 0, 0)) : (Gi(e2[u2[0]], e2[u2[1]], e2[u2[2]], P2), D2.setFromUnitVectors(Wi, P2));
|
|
340559
340559
|
const v3 = D2.clone().invert();
|
|
340560
340560
|
if (h3.computeBoundingBox(), p2 && k2(), h3.center(), h3.applyQuaternion(v3), null == (a2 = b2.current) || a2.setRotationFromQuaternion(D2), m2) if (n5) {
|
|
340561
|
-
if (M2.current &&
|
|
340562
|
-
const e3 = M2.current._computeSphere(h3), t4 =
|
|
340561
|
+
if (M2.current && C26.current && z3.current) if (f3) {
|
|
340562
|
+
const e3 = M2.current._computeSphere(h3), t4 = C26.current._computeSphere(h3), r3 = function(e4, t5) {
|
|
340563
340563
|
return e4.clone().add(t5).multiplyScalar(0.5);
|
|
340564
340564
|
}(e3.center, t4.center);
|
|
340565
|
-
d2 = r3.negate(), M2.current._update(h3, n5, e3),
|
|
340566
|
-
} else M2.current._update(h3, n5),
|
|
340565
|
+
d2 = r3.negate(), M2.current._update(h3, n5, e3), C26.current._update(h3, n5, t4);
|
|
340566
|
+
} else M2.current._update(h3, n5), C26.current._update(h3, n5);
|
|
340567
340567
|
} else console.warn("Facemesh `eyes` option only works if `faceBlendshapes` is provided: skipping.");
|
|
340568
340568
|
if (z3.current) {
|
|
340569
340569
|
if (void 0 !== d2) if ("number" == typeof d2) {
|
|
@@ -340579,10 +340579,10 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340579
340579
|
}
|
|
340580
340580
|
h3.computeVertexNormals(), h3.attributes.position.needsUpdate = true;
|
|
340581
340581
|
}, [e2, r2, n5, R2, o2, i3, s2, l2, c2, u2, d2, m2, p2, k2, P2, D2, _2, F2]);
|
|
340582
|
-
const A2 = S.useMemo(() => ({ outerRef: b2, meshRef: E2, eyeRightRef: M2, eyeLeftRef:
|
|
340582
|
+
const A2 = S.useMemo(() => ({ outerRef: b2, meshRef: E2, eyeRightRef: M2, eyeLeftRef: C26 }), []);
|
|
340583
340583
|
S.useImperativeHandle(y2, () => A2, [A2]);
|
|
340584
340584
|
const [L2] = S.useState(() => new T.Vector3()), I2 = null == (v2 = E2.current) ? void 0 : v2.geometry.boundingBox, B2 = (null == I2 ? void 0 : I2.getSize(L2).z) || 1;
|
|
340585
|
-
return S.createElement("group", x2, S.createElement("group", { ref: g2 }, S.createElement("group", { ref: b2 }, S.createElement("group", { ref: w2 }, p2 ? S.createElement(S.Fragment, null, S.createElement("axesHelper", { args: [B2] }), S.createElement(ce, { points: [[0, 0, 0], [0, 0, -B2]], color: 65535 })) : null, S.createElement("group", { ref: z3 }, m2 && n5 && S.createElement("group", { name: "eyes" }, S.createElement(qi, { side: "left", ref: M2, debug: p2 }), S.createElement(qi, { side: "right", ref:
|
|
340585
|
+
return S.createElement("group", x2, S.createElement("group", { ref: g2 }, S.createElement("group", { ref: b2 }, S.createElement("group", { ref: w2 }, p2 ? S.createElement(S.Fragment, null, S.createElement("axesHelper", { args: [B2] }), S.createElement(ce, { points: [[0, 0, 0], [0, 0, -B2]], color: 65535 })) : null, S.createElement("group", { ref: z3 }, m2 && n5 && S.createElement("group", { name: "eyes" }, S.createElement(qi, { side: "left", ref: M2, debug: p2 }), S.createElement(qi, { side: "right", ref: C26, debug: p2 })), S.createElement("mesh", { ref: E2, name: "face" }, h2, p2 ? S.createElement(S.Fragment, null, I2 && S.createElement("box3Helper", { args: [I2] })) : null))))));
|
|
340586
340586
|
});
|
|
340587
340587
|
var $i = { contourLandmarks: { right: [33, 133, 159, 145, 153], left: [263, 362, 386, 374, 380] }, blendshapes: { right: [14, 16, 18, 12], left: [13, 15, 17, 11] }, color: { right: "red", left: "#00ff00" }, fov: { horizontal: 100, vertical: 90 } };
|
|
340588
340588
|
var qi = S.forwardRef(({ side: e2, debug: t2 = true }, r2) => {
|
|
@@ -340628,27 +340628,27 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
340628
340628
|
var ts = t.createContext({});
|
|
340629
340629
|
var rs = t.forwardRef(({ camera: e2, videoTexture: r2 = { start: true }, manualDetect: n5 = false, faceLandmarkerResult: o2, manualUpdate: i3 = false, makeDefault: l2, smoothTime: c2 = 0.25, offset: u2 = true, offsetScalar: d2 = 80, eyes: m2 = false, eyesAsOrigin: f3 = true, depth: p2 = 0.15, debug: h2 = false, facemesh: x2 }, y2) => {
|
|
340630
340630
|
var v2, g2;
|
|
340631
|
-
const w2 = a.useThree((e3) => e3.scene), z3 = a.useThree((e3) => e3.camera), b2 = a.useThree((e3) => e3.set), E2 = a.useThree((e3) => e3.get),
|
|
340632
|
-
R2.parent =
|
|
340631
|
+
const w2 = a.useThree((e3) => e3.scene), z3 = a.useThree((e3) => e3.camera), b2 = a.useThree((e3) => e3.set), E2 = a.useThree((e3) => e3.get), C26 = e2 || z3, P2 = t.useRef(null), [R2] = t.useState(() => new T.Object3D()), [D2] = t.useState(() => new T.Vector3()), [F2] = t.useState(() => new T.Vector3()), [k2] = t.useState(() => new T.Vector3()), [_2] = t.useState(() => new T.Vector3()), A2 = t.useCallback(() => {
|
|
340632
|
+
R2.parent = C26.parent;
|
|
340633
340633
|
const e3 = P2.current;
|
|
340634
340634
|
if (e3) {
|
|
340635
340635
|
const { outerRef: t2, eyeRightRef: r3, eyeLeftRef: n6 } = e3;
|
|
340636
340636
|
if (r3.current && n6.current) {
|
|
340637
340637
|
const { irisDirRef: e4 } = r3.current, { irisDirRef: a2 } = n6.current;
|
|
340638
|
-
e4.current && a2.current && t2.current && (D2.copy(es(e4.current, new T.Vector3(0, 0, 0), t2.current)), F2.copy(es(a2.current, new T.Vector3(0, 0, 0), t2.current)), R2.position.copy(es(t2.current, Ji(D2, F2),
|
|
340639
|
-
} else t2.current && (R2.position.copy(es(t2.current, new T.Vector3(0, 0, 0),
|
|
340638
|
+
e4.current && a2.current && t2.current && (D2.copy(es(e4.current, new T.Vector3(0, 0, 0), t2.current)), F2.copy(es(a2.current, new T.Vector3(0, 0, 0), t2.current)), R2.position.copy(es(t2.current, Ji(D2, F2), C26.parent || w2)), k2.copy(es(e4.current, new T.Vector3(0, 0, 1), t2.current)), _2.copy(es(a2.current, new T.Vector3(0, 0, 1), t2.current)), R2.lookAt(t2.current.localToWorld(Ji(k2, _2))));
|
|
340639
|
+
} else t2.current && (R2.position.copy(es(t2.current, new T.Vector3(0, 0, 0), C26.parent || w2)), R2.lookAt(t2.current.localToWorld(new T.Vector3(0, 0, 1))));
|
|
340640
340640
|
}
|
|
340641
340641
|
return R2;
|
|
340642
|
-
}, [
|
|
340643
|
-
if (
|
|
340642
|
+
}, [C26, F2, _2, D2, k2, w2, R2]), [L2] = t.useState(() => new T.Object3D()), I2 = t.useCallback(function(e3, t2) {
|
|
340643
|
+
if (C26) {
|
|
340644
340644
|
var r3;
|
|
340645
340645
|
if (null !== (r3 = t2) && void 0 !== r3 || (t2 = A2()), c2 > 0) {
|
|
340646
340646
|
const r4 = 1e-9;
|
|
340647
340647
|
s.easing.damp3(L2.position, t2.position, c2, e3, void 0, void 0, r4), s.easing.dampE(L2.rotation, t2.rotation, c2, e3, void 0, void 0, r4);
|
|
340648
340648
|
} else L2.position.copy(t2.position), L2.rotation.copy(t2.rotation);
|
|
340649
|
-
|
|
340649
|
+
C26.position.copy(L2.position), C26.rotation.copy(L2.rotation);
|
|
340650
340650
|
}
|
|
340651
|
-
}, [
|
|
340651
|
+
}, [C26, A2, c2, L2.position, L2.rotation]);
|
|
340652
340652
|
a.useFrame((e3, t2) => {
|
|
340653
340653
|
i3 || I2(t2);
|
|
340654
340654
|
});
|
|
@@ -341013,14 +341013,14 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
341013
341013
|
v2.style.cursor = "default", y2.domElement.style.cursor = "default";
|
|
341014
341014
|
};
|
|
341015
341015
|
}, [r2, l2, v2, e2]);
|
|
341016
|
-
const [E2] = S.useState({ scale: 1, rotation: b2, damping: h2 }),
|
|
341016
|
+
const [E2] = S.useState({ scale: 1, rotation: b2, damping: h2 }), C26 = S.useRef(null);
|
|
341017
341017
|
a.useFrame((e3, t3) => {
|
|
341018
|
-
s.easing.damp3(
|
|
341018
|
+
s.easing.damp3(C26.current.scale, E2.scale, E2.damping, t3), s.easing.dampE(C26.current.rotation, E2.rotation, E2.damping, t3);
|
|
341019
341019
|
});
|
|
341020
341020
|
const T2 = i2.useGesture({ onHover: ({ last: t3 }) => {
|
|
341021
341021
|
l2 && !r2 && e2 && (v2.style.cursor = t3 ? "auto" : "grab");
|
|
341022
341022
|
}, onDrag: ({ down: r3, delta: [a2, o3], memo: [i3, s2] = E2.rotation || b2 }) => e2 ? (l2 && (v2.style.cursor = r3 ? "grabbing" : "grab"), a2 = n4.MathUtils.clamp(s2 + a2 / g2.width * Math.PI * u2, ...z3), o3 = n4.MathUtils.clamp(i3 + o3 / g2.height * Math.PI * u2, ...w2), E2.scale = r3 && o3 > w2[1] / 2 ? m2 : 1, E2.rotation = t2 && !r3 ? b2 : [o3, a2, 0], E2.damping = t2 && !r3 && "boolean" != typeof t2 ? t2 : h2, [o3, a2]) : [o3, a2] }, { target: r2 ? v2 : void 0 });
|
|
341023
|
-
return S.createElement("group", M.default({ ref:
|
|
341023
|
+
return S.createElement("group", M.default({ ref: C26 }, null == T2 ? void 0 : T2()), c2);
|
|
341024
341024
|
}, exports2.Progress = function({ children: e2 }) {
|
|
341025
341025
|
const t2 = G();
|
|
341026
341026
|
return S.createElement(S.Fragment, null, null == e2 ? void 0 : e2(t2));
|
|
@@ -341081,18 +341081,18 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
341081
341081
|
};
|
|
341082
341082
|
}
|
|
341083
341083
|
}, [g2, v2, x2, r2, M2, y2, n5, t2]);
|
|
341084
|
-
let
|
|
341084
|
+
let C26 = 0;
|
|
341085
341085
|
return a.useFrame((t3, r3) => {
|
|
341086
|
-
|
|
341086
|
+
C26 = M2.offset, s.easing.damp(M2, "offset", E2.current, l2, r3, c2, void 0, e2), s.easing.damp(M2, "delta", Math.abs(C26 - M2.offset), l2, r3, c2, void 0, e2), M2.delta > e2 && y2();
|
|
341087
341087
|
}), S.createElement(q.Provider, { value: M2 }, m2);
|
|
341088
341088
|
}, exports2.Segment = Eo, exports2.SegmentObject = zo, exports2.Segments = wo, exports2.Select = function({ box: e2, multiple: t2, children: r2, onChange: n5, onChangePointerUp: o2, border: i3 = "1px solid #55aaff", backgroundColor: s2 = "rgba(75, 160, 255, 0.1)", filter: l2 = (e3) => e3, ...d2 }) {
|
|
341089
|
-
const [m2, f3] = S.useState(false), { setEvents: p2, camera: h2, raycaster: x2, gl: y2, controls: v2, size: g2, get: w2 } = a.useThree(), [z3, b2] = S.useState(false), [E2,
|
|
341089
|
+
const [m2, f3] = S.useState(false), { setEvents: p2, camera: h2, raycaster: x2, gl: y2, controls: v2, size: g2, get: w2 } = a.useThree(), [z3, b2] = S.useState(false), [E2, C26] = S.useReducer((e3, { object: t3, shift: r3 }) => void 0 === t3 ? [] : Array.isArray(t3) ? t3 : r3 ? e3.includes(t3) ? e3.filter((e4) => e4 !== t3) : [t3, ...e3] : e3[0] === t3 ? [] : [t3], []);
|
|
341090
341090
|
S.useEffect(() => {
|
|
341091
341091
|
m2 ? null == n5 || n5(E2) : null == o2 || o2(E2);
|
|
341092
341092
|
}, [E2, m2]);
|
|
341093
341093
|
const P2 = S.useCallback((e3) => {
|
|
341094
|
-
e3.stopPropagation(),
|
|
341095
|
-
}, []), R2 = S.useCallback((e3) => !z3 &&
|
|
341094
|
+
e3.stopPropagation(), C26({ object: l2([e3.object])[0], shift: t2 && e3.shiftKey });
|
|
341095
|
+
}, []), R2 = S.useCallback((e3) => !z3 && C26({}), [z3]), D2 = S.useRef(null);
|
|
341096
341096
|
return S.useEffect(() => {
|
|
341097
341097
|
if (!e2 || !t2) return;
|
|
341098
341098
|
const r3 = new c.SelectionBox(h2, D2.current), n6 = document.createElement("div");
|
|
@@ -341116,7 +341116,7 @@ vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289(vec4 x){ret
|
|
|
341116
341116
|
d3.x = Math.max(a2.x, e4.clientX), d3.y = Math.max(a2.y, e4.clientY), o3.x = Math.min(a2.x, e4.clientX), o3.y = Math.min(a2.y, e4.clientY), n6.style.left = `${o3.x}px`, n6.style.top = `${o3.y}px`, n6.style.width = d3.x - o3.x + "px", n6.style.height = d3.y - o3.y + "px";
|
|
341117
341117
|
}(e3), b3(e3, r3.endPoint);
|
|
341118
341118
|
const t3 = r3.select().sort((e4) => e4.uuid).filter((e4) => e4.isMesh);
|
|
341119
|
-
u.shallow(t3, M2) || (M2 = t3,
|
|
341119
|
+
u.shallow(t3, M2) || (M2 = t3, C26({ object: l2(t3) }));
|
|
341120
341120
|
}
|
|
341121
341121
|
}
|
|
341122
341122
|
function P3(e3) {
|
|
@@ -341262,11 +341262,11 @@ float PCSS (sampler2D shadowMap, vec4 coords) {
|
|
|
341262
341262
|
return e2.shader ? S.createElement(Ea, e2) : S.createElement(Ma, e2);
|
|
341263
341263
|
}, exports2.SpriteAnimator = jr, exports2.Stage = function({ children: e2, center: t2, adjustCamera: r2 = true, intensity: n5 = 0.5, shadows: a2 = "contact", environment: o2 = "city", preset: i3 = "rembrandt", ...s2 }) {
|
|
341264
341264
|
var l2, c2, u2, d2, m2, f3, p2, h2;
|
|
341265
|
-
const x2 = "string" == typeof i3 ? ua[i3] : i3, [{ radius: y2, height: v2 }, g2] = S.useState({ radius: 0, width: 0, height: 0, depth: 0 }), w2 = null !== (l2 = null == a2 ? void 0 : a2.bias) && void 0 !== l2 ? l2 : -1e-4, z3 = null !== (c2 = null == a2 ? void 0 : a2.normalBias) && void 0 !== c2 ? c2 : 0, b2 = null !== (u2 = null == a2 ? void 0 : a2.size) && void 0 !== u2 ? u2 : 1024, E2 = null !== (d2 = null == a2 ? void 0 : a2.offset) && void 0 !== d2 ? d2 : 0,
|
|
341265
|
+
const x2 = "string" == typeof i3 ? ua[i3] : i3, [{ radius: y2, height: v2 }, g2] = S.useState({ radius: 0, width: 0, height: 0, depth: 0 }), w2 = null !== (l2 = null == a2 ? void 0 : a2.bias) && void 0 !== l2 ? l2 : -1e-4, z3 = null !== (c2 = null == a2 ? void 0 : a2.normalBias) && void 0 !== c2 ? c2 : 0, b2 = null !== (u2 = null == a2 ? void 0 : a2.size) && void 0 !== u2 ? u2 : 1024, E2 = null !== (d2 = null == a2 ? void 0 : a2.offset) && void 0 !== d2 ? d2 : 0, C26 = "contact" === a2 || "contact" === (null == a2 ? void 0 : a2.type), T2 = "accumulative" === a2 || "accumulative" === (null == a2 ? void 0 : a2.type), P2 = { ..."object" == typeof a2 ? a2 : {} }, R2 = o2 ? "string" == typeof o2 ? { preset: o2 } : o2 : null, D2 = S.useCallback((e3) => {
|
|
341266
341266
|
const { width: r3, height: n6, depth: a3, boundingSphere: o3 } = e3;
|
|
341267
341267
|
g2({ radius: o3.radius, width: r3, height: n6, depth: a3 }), null != t2 && t2.onCentered && t2.onCentered(e3);
|
|
341268
341268
|
}, []);
|
|
341269
|
-
return S.createElement(S.Fragment, null, S.createElement("ambientLight", { intensity: n5 / 3 }), S.createElement("spotLight", { penumbra: 1, position: [x2.main[0] * y2, x2.main[1] * y2, x2.main[2] * y2], intensity: 2 * n5, castShadow: !!a2, "shadow-bias": w2, "shadow-normalBias": z3, "shadow-mapSize": b2 }), S.createElement("pointLight", { position: [x2.fill[0] * y2, x2.fill[1] * y2, x2.fill[2] * y2], intensity: n5 }), S.createElement(Un, M.default({ fit: !!r2, clip: !!r2, margin: Number(r2), observe: true }, s2), S.createElement(da, { radius: y2, adjustCamera: r2 }), S.createElement(wr, M.default({}, t2, { position: [0, E2 / 2, 0], onCentered: D2 }), e2)), S.createElement("group", { position: [0, -v2 / 2 - E2 / 2, 0] },
|
|
341269
|
+
return S.createElement(S.Fragment, null, S.createElement("ambientLight", { intensity: n5 / 3 }), S.createElement("spotLight", { penumbra: 1, position: [x2.main[0] * y2, x2.main[1] * y2, x2.main[2] * y2], intensity: 2 * n5, castShadow: !!a2, "shadow-bias": w2, "shadow-normalBias": z3, "shadow-mapSize": b2 }), S.createElement("pointLight", { position: [x2.fill[0] * y2, x2.fill[1] * y2, x2.fill[2] * y2], intensity: n5 }), S.createElement(Un, M.default({ fit: !!r2, clip: !!r2, margin: Number(r2), observe: true }, s2), S.createElement(da, { radius: y2, adjustCamera: r2 }), S.createElement(wr, M.default({}, t2, { position: [0, E2 / 2, 0], onCentered: D2 }), e2)), S.createElement("group", { position: [0, -v2 / 2 - E2 / 2, 0] }, C26 && S.createElement(aa, M.default({ scale: 4 * y2, far: y2, blur: 2 }, P2)), T2 && S.createElement(sa, M.default({ temporal: true, frames: 100, alphaTest: 0.9, toneMapped: true, scale: 4 * y2 }, P2), S.createElement(la, { amount: null !== (m2 = P2.amount) && void 0 !== m2 ? m2 : 8, radius: null !== (f3 = P2.radius) && void 0 !== f3 ? f3 : y2, ambient: null !== (p2 = P2.ambient) && void 0 !== p2 ? p2 : 0.5, intensity: null !== (h2 = P2.intensity) && void 0 !== h2 ? h2 : 1, position: [x2.main[0] * y2, x2.main[1] * y2, x2.main[2] * y2], size: 4 * y2, bias: -w2, mapSize: b2 }))), o2 && S.createElement(na, R2));
|
|
341270
341270
|
}, exports2.Stars = Fa, exports2.Stats = function({ showPanel: e2 = 0, className: t2, parent: r2 }) {
|
|
341271
341271
|
const n5 = function(e3, t3 = [], r3) {
|
|
341272
341272
|
const [n6, a2] = S.useState();
|
|
@@ -421010,24 +421010,24 @@ function convertToString(attrs) {
|
|
|
421010
421010
|
function intersectLine2Line(line1, line2) {
|
|
421011
421011
|
let ip = [];
|
|
421012
421012
|
let [A1, B1, C110] = line1.standard;
|
|
421013
|
-
let [A2, B2,
|
|
421013
|
+
let [A2, B2, C26] = line2.standard;
|
|
421014
421014
|
let det = A1 * B2 - B1 * A2;
|
|
421015
|
-
let detX = C110 * B2 - B1 *
|
|
421016
|
-
let detY = A1 *
|
|
421015
|
+
let detX = C110 * B2 - B1 * C26;
|
|
421016
|
+
let detY = A1 * C26 - C110 * A2;
|
|
421017
421017
|
if (!Flatten.Utils.EQ_0(det)) {
|
|
421018
421018
|
let x, y;
|
|
421019
421019
|
if (B1 === 0) {
|
|
421020
421020
|
x = C110 / A1;
|
|
421021
421021
|
y = detY / det;
|
|
421022
421022
|
} else if (B2 === 0) {
|
|
421023
|
-
x =
|
|
421023
|
+
x = C26 / A2;
|
|
421024
421024
|
y = detY / det;
|
|
421025
421025
|
} else if (A1 === 0) {
|
|
421026
421026
|
x = detX / det;
|
|
421027
421027
|
y = C110 / B1;
|
|
421028
421028
|
} else if (A2 === 0) {
|
|
421029
421029
|
x = detX / det;
|
|
421030
|
-
y =
|
|
421030
|
+
y = C26 / B2;
|
|
421031
421031
|
} else {
|
|
421032
421032
|
x = detX / det;
|
|
421033
421033
|
y = detY / det;
|
|
@@ -429173,9 +429173,10 @@ var C21 = {
|
|
|
429173
429173
|
const rooms = ctx.model.byType("room");
|
|
429174
429174
|
if (rooms.length === 0) return findings;
|
|
429175
429175
|
const lowest = Math.min(...rooms.map((r) => r.floor));
|
|
429176
|
+
const tol = Math.max(ctx.defaults.wall_thickness, 8);
|
|
429176
429177
|
for (const { floorNum, summary } of eachStaircaseSummary(ctx)) {
|
|
429177
429178
|
if (!summary.box || floorNum !== lowest) continue;
|
|
429178
|
-
const fp = boxFootprint(summary.box);
|
|
429179
|
+
const fp = boxFootprint(summary.box, tol) ?? boxFootprint(summary.box);
|
|
429179
429180
|
if (!fp) continue;
|
|
429180
429181
|
if (!footprintContains(plinthArea, fp)) {
|
|
429181
429182
|
const b = summary.box;
|
|
@@ -429429,14 +429430,25 @@ var C24 = {
|
|
|
429429
429430
|
},
|
|
429430
429431
|
check(ctx) {
|
|
429431
429432
|
const { findings, report } = makeReport("C24", "warn");
|
|
429432
|
-
const
|
|
429433
|
+
const tol = Math.max(ctx.defaults.wall_thickness, 8);
|
|
429434
|
+
const floors = ctx.resolved.floors ?? [];
|
|
429435
|
+
const decksByFloor = /* @__PURE__ */ new Map();
|
|
429436
|
+
for (const fl of floors) {
|
|
429437
|
+
const fps = [];
|
|
429438
|
+
for (const o of fl.objects ?? []) {
|
|
429439
|
+
if (o.type !== "floor_slab" || o.enabled === false) continue;
|
|
429440
|
+
const fp = boxFootprint({ x: num2(o.x), y: num2(o.y), width: num2(o.width), length: num2(o.length) });
|
|
429441
|
+
if (fp) fps.push(fp);
|
|
429442
|
+
}
|
|
429443
|
+
decksByFloor.set(num2(fl.floor_number), fps);
|
|
429444
|
+
}
|
|
429433
429445
|
for (const { floorNum, floorSlabThickness, summary } of eachStaircaseSummary(ctx)) {
|
|
429434
429446
|
if (!summary.box) continue;
|
|
429435
429447
|
if (!(floorSlabThickness > 0)) continue;
|
|
429436
|
-
const fp = boxFootprint(summary.box);
|
|
429448
|
+
const fp = boxFootprint(summary.box, tol) ?? boxFootprint(summary.box);
|
|
429437
429449
|
if (!fp) continue;
|
|
429438
|
-
const
|
|
429439
|
-
const covered2 =
|
|
429450
|
+
const decks = decksByFloor.get(floorNum) ?? [];
|
|
429451
|
+
const covered2 = decks.length > 0 && footprintContains(footprintUnion(decks), fp);
|
|
429440
429452
|
if (!covered2) {
|
|
429441
429453
|
const b = summary.box;
|
|
429442
429454
|
report(
|
|
@@ -429475,7 +429487,7 @@ function house23(slabThickness, objects) {
|
|
|
429475
429487
|
};
|
|
429476
429488
|
}
|
|
429477
429489
|
function slab(x, y, width, length) {
|
|
429478
|
-
return { type: "
|
|
429490
|
+
return { type: "floor_slab", name: "Slab", x, y, width, length };
|
|
429479
429491
|
}
|
|
429480
429492
|
function stair5(start_x, start_y, width, length) {
|
|
429481
429493
|
return {
|
|
@@ -429492,6 +429504,85 @@ function stair5(start_x, start_y, width, length) {
|
|
|
429492
429504
|
};
|
|
429493
429505
|
}
|
|
429494
429506
|
|
|
429507
|
+
// ../editor/src/lint/constraints/c25_pillar_reach.ts
|
|
429508
|
+
function isLanding(n4) {
|
|
429509
|
+
return /_L\d+$/.test(String(n4.raw?.name ?? ""));
|
|
429510
|
+
}
|
|
429511
|
+
var C25 = {
|
|
429512
|
+
id: "C25",
|
|
429513
|
+
title: "A pillar under a slab or landing should reach it",
|
|
429514
|
+
level: "warn",
|
|
429515
|
+
doc: {
|
|
429516
|
+
statement: "When a floor slab or a staircase landing sits directly over a pillar, the pillar should rise to the underside of it. A pillar that stops short leaves a gap and carries nothing.",
|
|
429517
|
+
rationale: "A column exists to carry the slab or landing above it; if it stops below that level there is a gap and the load has nothing to bear on. Agents often shrink a copied pillar's height (or a configurator lowers it) so it no longer reaches. This checks the LOWEST floor slab / staircase landing that actually sits over the pillar \u2014 so a pillar added to carry a stair's turn landings is checked against those landings, and a pillar supporting only a roof (which slopes, no flat datum) or with nothing above is never flagged. (A warning \u2014 a deliberately low post under a slab is allowed.)",
|
|
429518
|
+
fix: "Raise the pillar height so its top meets the slab/landing above it. The warning reports the gap and a suggested height."
|
|
429519
|
+
},
|
|
429520
|
+
check(ctx) {
|
|
429521
|
+
const { findings, report } = makeReport("C25", "warn");
|
|
429522
|
+
const model = ctx.model;
|
|
429523
|
+
const slabs = model.byType("floor_slab");
|
|
429524
|
+
const maxGap = Math.max(ctx.defaults.slab_thickness / 2, 2);
|
|
429525
|
+
const eps = 1;
|
|
429526
|
+
for (const p of model.byType("pillar")) {
|
|
429527
|
+
if (p.raw.enabled === false) continue;
|
|
429528
|
+
const above2 = slabs.filter(
|
|
429529
|
+
(s) => s.z.lo > p.z.lo + eps && footprintsOverlap(s.footprint, p.footprint)
|
|
429530
|
+
);
|
|
429531
|
+
if (above2.length === 0) continue;
|
|
429532
|
+
const deckTops = above2.filter((s) => !isLanding(s)).map((s) => s.z.lo);
|
|
429533
|
+
const cap2 = deckTops.length ? Math.min(...deckTops) : Infinity;
|
|
429534
|
+
const inReach = above2.filter((s) => s.z.lo <= cap2 + eps);
|
|
429535
|
+
const target = inReach.reduce((a, s) => s.z.lo > a.z.lo ? s : a, inReach[0]);
|
|
429536
|
+
const gap = target.z.lo - p.z.hi;
|
|
429537
|
+
if (gap > maxGap) {
|
|
429538
|
+
const kind = isLanding(target) ? "landing" : "slab";
|
|
429539
|
+
const suggested = Math.round(num2(p.raw.height) + gap);
|
|
429540
|
+
report(
|
|
429541
|
+
`Pillar ${objLabel(p.raw)} on floor ${p.floor} is short of the ${kind} above it \u2014 its top is ${Math.round(gap)} below the underside of that ${kind}, leaving a gap with nothing bearing on it. Raise its height to about ${suggested} so it meets the ${kind}.`,
|
|
429542
|
+
{ floor: p.floor, where: objLabel(p.raw) }
|
|
429543
|
+
);
|
|
429544
|
+
}
|
|
429545
|
+
}
|
|
429546
|
+
return findings;
|
|
429547
|
+
},
|
|
429548
|
+
fixtures: {
|
|
429549
|
+
pass: [
|
|
429550
|
+
{
|
|
429551
|
+
name: "a full-height pillar reaches the slab above",
|
|
429552
|
+
config: house24(116, true)
|
|
429553
|
+
},
|
|
429554
|
+
{
|
|
429555
|
+
name: "a short pillar with NO slab above (supports a roof) is not flagged",
|
|
429556
|
+
config: house24(75, false)
|
|
429557
|
+
}
|
|
429558
|
+
],
|
|
429559
|
+
fail: [
|
|
429560
|
+
{
|
|
429561
|
+
name: "a short pillar under a slab",
|
|
429562
|
+
config: house24(75, true),
|
|
429563
|
+
expect: { count: 1, level: "warn", messageIncludes: "short of the slab" }
|
|
429564
|
+
}
|
|
429565
|
+
]
|
|
429566
|
+
}
|
|
429567
|
+
};
|
|
429568
|
+
function house24(pillarHeight, slabAbove) {
|
|
429569
|
+
const floor2Objects = slabAbove ? [{ type: "floor_slab", name: "Deck", x: 0, y: 0, width: 100, length: 100 }] : [];
|
|
429570
|
+
return {
|
|
429571
|
+
floors: [
|
|
429572
|
+
{
|
|
429573
|
+
floor_number: 1,
|
|
429574
|
+
name: "Ground",
|
|
429575
|
+
height: 116,
|
|
429576
|
+
slab_thickness: 8,
|
|
429577
|
+
objects: [
|
|
429578
|
+
{ type: "pillar", name: "P", x: 40, y: 40, width: 12, length: 12, height: pillarHeight }
|
|
429579
|
+
]
|
|
429580
|
+
},
|
|
429581
|
+
{ floor_number: 2, name: "Upper", height: 116, slab_thickness: 8, objects: floor2Objects }
|
|
429582
|
+
]
|
|
429583
|
+
};
|
|
429584
|
+
}
|
|
429585
|
+
|
|
429495
429586
|
// ../editor/src/lint/constraints/index.ts
|
|
429496
429587
|
var CONSTRAINTS = [
|
|
429497
429588
|
C1,
|
|
@@ -429517,7 +429608,8 @@ var CONSTRAINTS = [
|
|
|
429517
429608
|
C21,
|
|
429518
429609
|
C22,
|
|
429519
429610
|
C23,
|
|
429520
|
-
C24
|
|
429611
|
+
C24,
|
|
429612
|
+
C25
|
|
429521
429613
|
];
|
|
429522
429614
|
function allConstraints() {
|
|
429523
429615
|
const primitive = allNodes().flatMap((n4) => n4.constraints ?? []);
|
|
@@ -429566,7 +429658,7 @@ function wallRings(obj) {
|
|
|
429566
429658
|
]
|
|
429567
429659
|
];
|
|
429568
429660
|
}
|
|
429569
|
-
var BASE_Z_TYPES = /* @__PURE__ */ new Set(["floor_slab", "slab", "beam", "roof", "gable_roof", "ground"]);
|
|
429661
|
+
var BASE_Z_TYPES = /* @__PURE__ */ new Set(["floor_slab", "slab", "beam", "roof", "gable_roof", "ground", "pillar"]);
|
|
429570
429662
|
function zBandOf(type, obj, band) {
|
|
429571
429663
|
const zOffDefault = BASE_Z_TYPES.has(type) ? 0 : band.slabThickness;
|
|
429572
429664
|
const z0 = band.slabZ + num4(obj.z_offset, zOffDefault);
|
|
@@ -434283,15 +434375,15 @@ var DOCS = {
|
|
|
434283
434375
|
},
|
|
434284
434376
|
"dsl": {
|
|
434285
434377
|
"title": "The Wadi DSL (.wdl) \u2014 syntax reference",
|
|
434286
|
-
"body": '# The Wadi DSL (`.wdl`) \u2014 authoring reference\n\nYou author houses in the **Wadi DSL** \u2014 a small, formal language (`.wdl`) that\ncompiles to a resolved `.wadi` (`house_config.json`). The DSL is **complete**:\nevery object type in the model has first-class syntax, so you rarely need the\n`raw` escape. Authoring the DSL is more direct and less error-prone than writing\nJSON \u2014 the grammar enforces structure, and `check.sh` reports parse errors with\nline:col.\n\n**This file is the SYNTAX reference.** The *semantics* live in the other\nreferences and apply unchanged \u2014 read them:\n\n- `coordinate-system.md` \u2014 X\u2192right, **Y\u2192DOWN**, Z\u2192up; **10 units = 1 ft**;\n the **centreline** convention. The #1 source of mistakes.\n- `conventions.md` \u2014 the **structural coding conventions** (`check.sh` enforces\n them): plinth-floor height must match the plinth block, rooms must wall every\n exterior side, a no-slab floor must set `slab_thickness 0`.\n- `parametric-conventions.md` \u2014 the grid-first recipe for reusable templates.\n- `roof-v2-guide.md` \u2014 roof segments, hip vs gable, trusses, joints.\n- `data-model.md` \u2014 the underlying `.wadi` schema (what the DSL compiles to; also\n the field reference for the `raw` escape).\n\n## The loop\n\n1. Write / edit `house.wdl` \u2014 the **single shared source** (you and the human\n co-edit it; the app\'s DSL previewer renders it live). You never produce a `.wadi`.\n2. `wadi-skill/architect/scripts/check.sh house.wdl` \u2014 runs the DSL compiler +\n validator (schema + wall/roof geometry) against a **throwaway temp** just for\n feedback; fix any reported error and re-run.\n3. `preview.sh house.wdl` \u2192 read the PNGs (plans / elevations / roof) to check your\n work. (It also compiles to a throwaway temp \u2014 no persistent `.wadi`.)\n\n## Skeleton\n\n```wdl\nhouse MyHouse {\n convention center // ALWAYS use center (wall-centreline coords)\n units feet_inches per_unit 10 // 10 project units = 1 ft\n site { plot (WIDTH, LENGTH) ref (0, 0) }\n defaults { floor_height 120 wall_height 108 slab_thickness 8 wall_thickness 8 }\n\n // parametric core (optional): var, point, grid, configurator\n // component / layer declarations (optional)\n floor 0 "Plinth" { \u2026 } // floors stack in source order (0 = plinth)\n floor 1 "Ground Floor" { \u2026 }\n floor 2 "Loft" { roof \u2026 } // roof lives ALONE on its own top floor\n}\n```\n\nNumbers are **project units** (feet \xD7 10 by default). Names after `house`,\n`room`, `pillar`, `var`, `point`, `grid`, and `use`/`component` are bare\nidentifiers (no spaces); names introduced with the `name` keyword are quoted\nstrings.\n\n## Parametric core (domain-neutral)\n\n```wdl\nvar wallT = 8 // a knob; may reference other vars\nvar pilInset = (pillarW - wallT) / 2\n\npoint House { x = 420, y = 470 } // reference as House.x / House.W / House.L\n // (.W = x, .L = y \u2014 a point doubles as a size)\n\nguides main { // NAMED wall centrelines; publishes main.x1 / main.yA\n x: 1 @ wallT / 2, 2 @ House.W / 2, 3 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L / 2, C @ House.L - wallT / 2\n}\n// each line may add: \u2026 @ <expr> thick <expr> role structural|planning\n// (`grid` is a deprecated alias for a named `guides` object \u2014 still parses.)\n\nguides module { // GENERATED \u2014 a uniform family from origin+spacing\n origin (0, 0) spacing (30, 30) [extent (40, 30)] // origin default (0,0); extent optional\n}\n// Reference a generated line by INDEX: module.x8 (integer) or module.x(<expr>)\n// (call form \u2014 required for fractional/negative/computed indices). `extent`\n// (line counts per axis) only bounds where the lines are DRAWN. A `guides` object\n// is EITHER named (x/y lists) OR generated (spacing) \u2014 never both.\n\nconfigurator { // the owner-facing template you author\n title "Configure your home" // panel heading (optional)\n note "Everything re-flows to fit." // panel subtitle (optional)\n\n slider pillarW "Column size" ft [8 .. 14 step 1] note "help text" // trailing note optional\n number ceiling "Ceiling height" ft\n toggle has_loft "Add a loft"\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n select floorH "Ceiling height" { "9 ft" = 90, "10 ft (std)" = 100 } // labels can be quoted strings\n\n group "Plot" note "about this section" { // sections the panel; label only\n slider W "Plot width" ft [340 .. 520 step 10]\n }\n}\n```\n\nEvery knob binds to a `var` by name (`target`). To expose a plot dimension, model\nit as a `var` and reference it from the point (`point House { x = W }`), then bind\nthe knob to `W` \u2014 knobs target vars, never a point field like `House.W`.\n\n**Formulas are automatic.** Any geometry number can be a formula \u2014 just write the\nexpression instead of a literal (`at (main.x1, main.yA)`, `size (House.W/2, 200)`).\nOperators: `+ - * /`, unary `-`, parentheses, and the functions\n`min max clamp round floor ceil abs`. References: a `var`, a `point`\n(`House.W`), a named guide (`main.x3 - main.x1`), or a generated guide\n(`module.x8`, `module.x(n+1)`). No comparison operators \u2014 gate\nthings with the `min/abs` idiom (see `enabled` below).\n\n## Common attribute tail (every object)\n\nAfter an object\'s geometry, in THIS order, any of:\n\n```\n\u2026 z_offset <expr> enabled <expr> layer "id" [material "id"]\n```\n\n- `enabled <expr>` \u2014 the on/off switch. A `0`/`false` value hides the object. To\n gate on a configurator variable, use a 0/1 formula:\n `enabled 1 - min(1, abs(roof_style - 3))` renders the object only when\n `roof_style == 3`. (This is how one template carries several roofs and shows\n only the chosen one.)\n- `z_offset <expr>` \u2014 lift above the floor base (split levels).\n- `material "id"` \u2014 only on plinth / ground / room / wall / staircase / kitchen /\n roof.\n\n## Objects \u2014 structure & envelope\n\n```wdl\nslab [name "N"] at (x,y) size (w,l) [thickness <t>] // floor_slab\nbeam [name "N"] at (x,y) size (w,l) [height <h>]\nplinth [name "N"] at (x,y) size (w,l) height <h> // raised base (Plinth floor)\nground [name "N"] at (x,y) size (w,l) [height <h>] // terrain plane\npillar Name at (x,y) size (w,l) [height <h>] // (x,y) = TOP-LEFT corner\n```\n\n`at (x,y)` is the **TOP-LEFT CORNER** \u2014 **not the centre** \u2014 for every one of these\n(same as rooms/slabs/beams); `size (w,l)` is width \xD7 length. All accept the common tail.\n\n**Pillars catch people out here.** A column reads as "placed at a point," but `at` is\nstill its corner. To **centre a column on a point** `(cx, cy)` \u2014 a grid node, a room\ncorner \u2014 place it at **`at (cx - w/2, cy - l/2)`**, never at `(cx, cy)`. On a grid, the\n`pilInset` idiom (see `parametric-conventions.md`) does exactly this so columns sit flush.\n\n## Objects \u2014 rooms, walls & openings\n\nA room shows exactly the walls you declare. A **bare room (no `wall` lines) is\nenclosed on all four sides.** List plain walls compactly; give a wall its own line\nonly when it carries a door/window; omit a side to leave it open (verandah).\n\n```wdl\nroom Name at (x,y) size (w,l) [height <h>] [material "\u2026"] {\n connect Kitchen Hall // rooms this room opens into (same floor)\n wall east west north // plain walls \u2014 several in one statement\n wall south { door Main at <offset> [from start|center|end] size (w,h) [open] } // wall WITH openings\n wall west { window W at <offset> [from start|center|end] size (w,h) [sill <s>] [open] }\n item asset { \u2026 } anchor center [gap (gx,gy)] // furniture anchored inside the room\n}\n```\n\n- `connect A B \u2026` records that this room adjoins the named room(s) \u2014 space-separated,\n quote a name with spaces (`connect "Guest Room"`). It\'s design intent + a functional\n test, **not geometry** (the renderer ignores it). May go anywhere in the block and\n repeat. Validated by C11: connected rooms must OVERLAP on a wall AND be passable \u2014\n a door in the overlap, or the shared wall left off BOTH rooms (an open passage).\n- `wall <side>\u2026` sides are `north|south|east|west`. A `wall <side>` line may also\n add `height <h>` / `height_end <h>` (sloped).\n- `door`/`window` `at <offset>` is measured along the wall; `size (width, height)`;\n `window \u2026 sill <s>` sets the sill height; `open` = a bare hole (no leaf/glazing).\n- `from start|center|end` (default `start`) picks which end `<offset>` is measured\n from, so the opening keeps its place when the wall or room scales \u2014 no formula\n needed. `start`: offset from the wall start to the near edge (the legacy default).\n `end`: offset from the wall end to the far edge (`at 0 from end` = flush to the end).\n `center`: signed shift of the opening centre from the wall midpoint (`at 0 from\n center` = centred; the offset may be negative).\n\nA **free-standing wall** (not a room side):\n\n```wdl\nwall Name from (x1,y1) to (x2,y2) [height <h>] [height_end <h>] [facing north|\u2026] {\n \u2026 door/window openings \u2026\n}\n```\n\n- `from`/`to` are the wall\'s **centreline** endpoints; the wall is drawn as a rectangle\n `wall_thickness` wide, centred on that line.\n- **Overlap walls at corners \u2014 they do NOT auto-mitre.** Two free-standing walls that\n merely *touch* at a shared endpoint leave an unfilled square notch (\xBD\xB7`wall_thickness`)\n at the corner, because each is just a rectangle capped at its endpoint. To fill the\n corner, **extend the endpoints so the wall bodies OVERLAP** \u2014 run at least one wall\'s\n end **half the wall thickness past** the shared point (overlapping by the full thickness\n is fine and simplest). For an L of thickness 8 meeting at `(160,40)`:\n\n ```wdl\n wall H from (40, 40) to (164, 40) height 108 // ends 4 (\xBD\xB78) PAST the corner\n wall V from (160, 40) to (160, 160) height 108 // butts into H\'s overlapped body\n ```\n\n (Room walls handle their own corners; this only applies to `wall \u2026 from \u2026 to \u2026`.)\n\n## Objects \u2014 circulation & fittings\n\n```wdl\nstaircase [name "N"] at (start_x, start_y) step (rise, tread, width)\n direction north|south|east|west [climb up|down]\n [total_height <h>] [max_run <r>] [landing_depth <d>]\n [landing_thickness <t>] [turn clockwise|anticlockwise] [flight_gap <g>]\n\nkitchen [name "N"] path ((x,y), (x,y), \u2026) side left|right\n depth <d> height <h> [base_z <z>] // path points are literal numbers\n```\n\n**`climb` picks the anchor + z direction.** Prefer **`climb up`** \u2014 the intuitive way:\n\n- Put the stair on the **LOWER** floor it rises FROM. `at (x,y)` is the **bottom** step\'s\n near corner on that floor; `direction` is the **ascent** direction; the flight climbs UP.\n- `total_height` is the **rise** to the next level (omit \u2192 this floor\'s own height).\n- `max_run` caps a flight\'s run; exceed it and the stair auto-splits into switchback\n flights with turn landings (`landing_depth`/`turn`/`flight_gap` tune the switchback).\n\n```wdl\nfloor 1 "Ground Floor" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name "Stair" at (212, 64) step (7, 11, 44) // bottom = this floor\n direction south climb up // ascends south to the floor above\n}\n```\n\n`climb down` is the legacy mode (**DEFAULT** for older configs): put the stair on the\n**UPPER** destination floor; `at (x,y)` is the **top** connection, `direction` is the\ndescent, and `total_height` defaults to the floor-below\'s height. New designs should use\n`climb up`.\n\n// three ways to name the GLB, in order of preference:\nitem [name "N"] f."sofa" // 1. from an imported module (see Imports)\nitem [name "N"] "sofa" // 2. a same-file / bare-imported `asset` id\nitem [name "N"] asset { id "sofa" src "\u2026/sofa.glb" dims (w,h,d) [category "\u2026"] } // 3. inline one-off\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nPrefer the module form (`item f."bed_double"`) \u2014 `import "std-furniture" as f`\nonce and every piece is a short id, no URLs. The bare form (`item "sofa"`) needs\na matching top-level `asset "sofa" \u2026` in the file (or a bare `import`). The inline\n`asset { \u2026 }` block is only for a one-off GLB not in any pack. All three produce\nthe identical `{id,src,dims}` downstream. Furniture `dims` are the real-world size\nin **metres** `(width, height, depth)`; `src` is a GLB URL (an unreachable GLB\nshows a placeholder box, never a blank). `anchor` is one of `top-left top-center\ntop-right center-left center center-right bottom-left bottom-center bottom-right`.\n\n**Orientation \u2014 this is how you point furniture the right way.** A piece\'s FRONT\n(the side you sit at / the doors / the open side) faces a known compass direction\nper its `rotation` (degrees):\n\n| `rotation` | front faces |\n|---|---|\n| `0` | **South** (the plot front / entrance side, +Y) |\n| `90` | East |\n| `180` | North |\n| `270` | West |\n\nSo a sofa against the NORTH wall (facing into the room, i.e. south) is `rotation\n0`; against the SOUTH wall (facing north) it\'s `rotation 180`; against the WEST\nwall (facing east) `rotation 90`. The floor plan (`wadi_preview plans`) draws a\nsmall triangle on each piece\'s front edge so you can verify the way it points;\nfor a definitive 3D check use `wadi_capture_3d({ room: "\u2026" })` (first-person from\ninside the room).\n\n**Anchoring auto-orients.** When you `anchor` a piece to a wall and DON\'T give a\n`rotation`, it automatically faces away from that wall, into the room \u2014 `anchor\ntop-center` \u2192 faces south, `bottom-center` \u2192 north, `center-left` \u2192 east,\n`center-right` \u2192 west (a corner uses its north/south edge). So `item f."bed_double"\nanchor top-center` needs no rotation. An explicit `rotation` always overrides,\nand the derived value is written into the resolved model, so the plan notch and\nthe 3D view show it \u2014 anchoring never changes facing silently.\n\n## Objects \u2014 GLB models (`model`)\n\nA `model` places a **GLB at real metre scale** and manipulates it through a **rig**\nof named-node operations. It is distinct from `item` (catalog furniture): `model`\ntargets the GLB\'s internal node graph, so you can hide, move, recolour, or array\nsub-parts of one asset.\n\n```wdl\nmodel [name "N"] <asset> at (x,y) [rotation <deg>] [scale <s>] {\n translate "nodeName" (x,y,z) // move a named node\n rotate "nodeName" (x,y,z) // rotate it (degrees)\n scale "nodeName" (x,y,z) // scale it\n visible "nodeName" false // hide a node\n material "nodeName" color "#rrggbb" // recolour a node\n array "nodeName" count N step (dx,dy,dz) { \u2026 nested rig ops \u2026 }\n}\n```\n\n`<asset>` names the GLB the same three ways as `item` (module `ns."id"`, bare\n`"id"`, or an inline `asset { \u2026 }` block). The rig ops run against the GLB\'s named\nnodes; `array` replicates a node `count` times along `step`, and its nested block\napplies further ops per copy. Use `model` for a rigged mechanism (a fan, a louvre\nbank, a spiral of balusters); use `item` for a plain piece of furniture.\n\n## Imports & modules (reusable `.wdl` libraries)\n\nA `.wdl` file can be a **module** \u2014 top-level declarations (no `house` needed) \u2014\nthat another file `import`s. Two bundled ones: `std-furniture` (asset pack \u2192\n`item ns."id"`) and `konkan/base` (goal-tagged component pack \u2192 `use ns.Comp`;\nStairwell, Verandah, Otla, Bathroom, Kitchen, TulsiVrindavan, Parapet). The\n`konkan_cottage` example (`wadi_examples`) assembles a whole house from both.\n\n```wdl\nhouse Home {\n import "std-furniture" as f // aliased: refer to its assets as f."<id>"\n // import "std-furniture" // bare: its ids drop into scope for item "<id>"\n floor 1 "G" slab_thickness 0 {\n room Bed at (20,20) size (160,200) { wall north east south west\n item f."bed_double" anchor center }\n }\n}\n```\n\nA module file itself is just top-level `asset` and `component` decls:\n\n```wdl\n// my-furniture.wdl \u2014 a house-less module (a reusable library)\nasset "daybed" src "https://\u2026/daybed.glb" dims (1.8, 0.4, 0.9) name "Daybed" category "Living"\n```\n\nOver MCP, `wadi_modules` lists importable modules and `wadi_module "<name>"`\nshows a module\'s asset ids + dimensions (filter with a `query`). Import refs\nresolve by name against the bundled `std-*` packs (a local `modules/` search\npath and git refs come later).\n\n## Objects \u2014 roof (one object; flat / shed / gable / hip)\n\nThe roof lives ALONE on its own top floor and you never set its Z (see\n`roof-v2-guide.md`). `endpoint`: `closed` = hip triangle, `open` = gable end-wall.\n\n```wdl\nroof [name "N"] pitched|shed|flat\n [endpoint open|closed]\n [slope angle <deg> | slope height <ridge_h>] // symmetric pitch (one value)\n [slope angle (<left>, <right>)] // asymmetric (saltbox) gable \u2014 angle pair\n [overhang <o>] [slab_thickness <t>] [parapet <h> x <t>] [gable_wall_thickness <t>] {\n segment "id" from (x,y) to (x,y) width <w>\n [high_side left|right] // shed only\n [start_endpoint open|closed] [end_endpoint open|closed]\n [hip_setback (a,b)] [gable_overhang (a,b)] [hip_ridge_extension (a,b)]\n // hip_ridge_extension = a flying-rafter ridge stub PAST the hip apex; OFF by\n // default (0). Add it only for a deliberate ridge overhang, not routinely.\n [overhang <o>] // uniform eave, all four sides\n [overhang_start <o>] [overhang_end <o>] // per-side along the axis (shed;\n // on a gable end = gable_overhang)\n [overhang_low <o>] [overhang_high <o>] // SHED eaves (down-slope / up-slope)\n [overhang_left <o>] [overhang_right <o>] // PITCHED eaves (left / right of ridge)\n [tie_beams N]\n truss "segId" fink|mono_pitch at (pos, pos, \u2026)\n }\n```\n\nSegment `from`/`to`/`width` and the `hip_setback`/\u2026 values accept formulas, so a\nroof scales with the plot (e.g. `width House.W`, `hip_setback (Verandah.L, Padvi.L)`).\n\n**Per-side overhang (cantilever one edge).** `overhang <o>` sets a uniform eave on\nall four sides. Any sloping roof can override a side independently \u2014 each defaults to\n`overhang`. **Along the axis:** `overhang_start` / `overhang_end` (on a shed, or a\ngable open end \u2014 there they\'re the same as `gable_overhang`; a hip end is geometric,\ntuned via `hip_setback`). **Eaves:** `overhang_low` / `overhang_high` on a **shed**\n(down-slope / up-slope); `overhang_left` / `overhang_right` on a **pitched** roof\n(the two eaves either side of the ridge). A bigger eave overhang also drops that\neave\'s edge along the same pitch, so the slope stays planar. (Per-eave on a *pitched*\nroof is single-segment only \u2014 on a multi-segment roof the eaves share one height so\njoints line up.)\n\n**Asymmetric gable (saltbox) \u2014 an angle pair.** A pitched roof\'s `slope` gives both\nfaces the same pitch (a symmetric gable). To make the two sides different, give\n`slope angle` a **pair** instead of one value \u2014 `slope angle (45, 25)`. A single value\nis symmetric; a pair is asymmetric, and that\'s the whole distinction (no separate\nkeywords, nothing half-settable). The two eaves stay put (footprint unchanged) and the\nridge shifts across the width so each face takes its angle; the gable-end triangle and\ntrusses follow. The pair is `(left, right)` by the segment\'s left normal \u2014 the **same\nsides as** `overhang_left`/`overhang_right`. Angles are measured to the wall-top eave\nline. `height` is single-value only (both faces share one ridge line). Intended for a\nsingle-segment gable (`endpoint open`); on a hip end the ridge shift skews the hip.\n\n```wdl\nroof pitched endpoint open slope angle (45, 25) {\n segment "s0" from (House.W/2, 0) to (House.W/2, House.L) width House.W\n}\n```\nIdiom: keep the roof FOOTPRINT (its supported edges) on the main room, then cantilever\none eave to cover an entry landing / stair \u2014 end the axis on the room wall and set a big\n`overhang_end`:\n```wdl\n// footprint ends on the main room\'s east wall (x204 centreline \u2192 x208 outer);\n// the east eave reaches 258, covering a landing that sticks out to x256.\nsegment "seg0" from (4,124) to (204,124) width 240 high_side right overhang 25 overhang_end 50\n```\n\n**Roof coordinates are wall centrelines (under `convention center`), same as\nrooms.** `from`/`to` is the segment\'s ridge/axis and `width` its span *centred on\nthat axis*. Author them on the **same centreline grid as the walls** \u2014 a segment\nwhose axis + width match the rooms\' centrelines auto-grows to the **outer wall\nface** on every side (the compiler extends the axis by \xBD\xB7wall_thickness at each end\nand widens by wall_thickness, exactly the grow a room gets). `overhang` then\nextends *beyond* the outer face. So to cover a footprint spanning wall centrelines\n`x1..x2` (E\u2013W) and `yA..yB` (N\u2013S), write `from (x1, (yA+yB)/2) to (x2, (yA+yB)/2)\nwidth (yB - yA)` \u2014 do **not** add \xBD-wall fudge factors; the convention handles it.\n(Before this, a roof drawn on the grid sat half a wall-thickness *inside* the walls.)\n\n## Components & layers\n\n```wdl\ncomponent Bench { // a reusable mini-house in LOCAL coords (origin 0,0)\n param blen = 60 label "Bench length"\n beam name "Top" at (0,0) size (blen, 18) height 6\n}\nuse Bench as "B1" at (x,y) [rotation <deg>] with { blen = 80 } // stamp onto a floor\n\nlayer "structure" "Structure" [color "#rrggbb"] [group "Frame"] // per-house layer registry\n```\n\nA component may carry a **`goal`** \u2014 a short description of what it accomplishes,\nthe discovery key for module lookup (`wadi_module` / a `wadi_modules` query):\n\n```wdl\ncomponent Stairwell goal "climb to the next floor" {\n param rise = 116\n staircase name "Stair" at (0,0) step (7,11,44) direction south total_height rise\n}\n```\n\nComponents can also come from an **imported module** (see *Imports & modules*),\nstamped with a namespaced `use ns.Comp`:\n\n```wdl\nhouse Home {\n import "konkan/base" as kb // Stairwell, Verandah, Otla (goal-tagged)\n floor 1 "G" slab_thickness 0 {\n room Hall at (20,20) size (200,200) { wall north east south west }\n use kb.Stairwell at (60,60) with { rise = 116 } // param args use `=`, not `:`\n }\n}\n```\n\n`use ns.Comp` expands byte-identical to an inline `component`. Components **nest\nfreely**: a library component may `use` a sibling, `use` a component from a\nlibrary it itself `import`s, and place `item ns."id"` furniture from its own\nimports \u2014 imports resolve **transitively** (cycles are a compile error).\nUn-overridden `param`s fall back to their declared defaults.\n\n`rotation <deg>` (optional, yaw\xB0: 0=south, 90=east) turns the whole stamped\nassembly about its origin. **Right angles (0/90/180/270) are exact for any\ncomponent** (rooms/pillars/beams/slabs swap dims + remap wall sides; furniture\nturns with them). A **non-right angle** is allowed **only for a furniture-only\ncomponent** (items/free walls rotate to any angle); a free angle on a component\nthat contains a room/pillar/beam/slab/staircase is a compile error (arbitrary\nstructural rotation is a future feature).\n\n### Promote a component to a primitive (`expose as`)\n\nA component can be **promoted to a runtime typed primitive** with `expose as`, so it\nreads and behaves like a built-in object type (`pack.type`) rather than a `use`\ninstance:\n\n```wdl\ncomponent Bench goal "a place to sit" expose as garden.bench [layer "id"] [label "\u2026"] {\n param length = 60\n beam name "Seat" at (0,0) size (length, 18) height 6\n}\n```\n\n`expose as <pack>.<type>` names the promoted primitive (a dotted `pack.type` id);\noptional `layer "id"` and `label "\u2026"` set its default layer and menu label. Once\nexposed, the component is available as a first-class object type named `pack.type`\nthroughout the model, its `param`s becoming that type\'s fields.\n\n## Template metadata (`template`)\n\nA `template { \u2026 }` block **self-describes a template** on its `.wdl` / `.wadi`, so a\ngallery can index the file without a separate catalog. It carries display metadata\nonly (no geometry):\n\n```wdl\ntemplate {\n title "Coastal Cottage"\n description "A compact single-storey Konkan home."\n style "konkan"\n roof "gable"\n tags ("coastal", "1BHK", "compact")\n thumbnails ("thumb-iso.png", "thumb-plan.png")\n min_plot (340, 400)\n}\n```\n\nFields: `title`, `description`, `style`, `roof`, `tags` (a list), `thumbnails` (a\nlist of image paths), `min_plot` (the minimum plot the template needs). The folder\nof self-describing files IS the catalog.\n\n## The `raw` escape (rarely needed)\n\nAnything the first-class syntax doesn\'t cover can be written as literal JSON per\nthe `.wadi` schema (`data-model.md`):\n\n```wdl\nraw "type" { "field": 1, "formulas": { "field": "= expr" } }\n```\n\n## DSL-specific pitfalls\n\n- **`convention center` and `units \u2026 per_unit 10`** belong at the top of every\n `house` \u2014 same as the JSON path. All the `coordinate-system.md` rules (Y-down,\n units, centreline abutment) apply identically; the DSL just writes them shorter.\n- **Formulas are bare expressions**, not `"= \u2026"` strings \u2014 the compiler emits the\n `= \u2026` form for you. Write `at (main.x1, main.yA)`, not `at ("= main.x1", \u2026)`.\n- **`name "\u2026"` is quoted; `room`/`pillar`/`var`/grid-line names are bare** ids\n (no spaces, and not a reserved word like `width`, `height`, `size`, `at`).\n- **Roof alone on the top floor**; segment widths/positions come from the walls\n they sit on. See `roof-v2-guide.md`.\n- **A pillar\'s `at` is its TOP-LEFT corner, not its centre.** To centre a column on\n `(cx,cy)`, author `at (cx - w/2, cy - l/2)`.\n- **Free-standing walls don\'t auto-mitre at corners** \u2014 extend endpoints so the wall\n bodies overlap (\u2265 \xBD\xB7`wall_thickness` past the shared point), or the corner is left\n as a gap.\n- **Prefer `climb up` \u2014 it\'s bottom-anchored.** Put the stair on the LOWER floor it\n rises FROM; `at` is the bottom step and the flight ascends into `direction`.\n Top-anchored is the legacy `climb down` (default only for older configs): the stair\n sits on the UPPER floor and descends. `check.sh` C5 flags a stair that lands below\n ground. See the staircase note.\n- **Structural conventions are enforced** \u2014 `check.sh` fails on floating floors\n (plinth-floor `height` \u2260 plinth block height; a no-slab floor with nonzero\n `slab_thickness`) and warns on exterior room sides left open. See\n `conventions.md`; the DSL editor shows the same findings in its status pill.\n- Compile after **every** edit; a parse error means the `.wadi` wasn\'t updated, so\n the live model just won\'t change \u2014 never silently wrong.\n'
|
|
434378
|
+
"body": '# The Wadi DSL (`.wdl`) \u2014 authoring reference\n\nYou author houses in the **Wadi DSL** \u2014 a small, formal language (`.wdl`) that\ncompiles to a resolved model (`HouseConfig`). The DSL is **complete**:\nevery object type in the model has first-class syntax, so you rarely need the\n`raw` escape. Authoring the DSL is more direct and less error-prone than writing\nJSON \u2014 the grammar enforces structure, and `check.sh` reports parse errors with\nline:col.\n\n**This file is the SYNTAX reference.** The *semantics* live in the other\nreferences and apply unchanged \u2014 read them:\n\n- `coordinate-system.md` \u2014 X\u2192right, **Y\u2192DOWN**, Z\u2192up; **10 units = 1 ft**;\n the **centreline** convention. The #1 source of mistakes.\n- `conventions.md` \u2014 the **structural coding conventions** (`check.sh` enforces\n them): plinth-floor height must match the plinth block, rooms must wall every\n exterior side, a no-slab floor must set `slab_thickness 0`.\n- `parametric-conventions.md` \u2014 the grid-first recipe for reusable templates.\n- `roof-v2-guide.md` \u2014 roof segments, hip vs gable, trusses, joints.\n- `data-model.md` \u2014 the underlying `.wadi` schema (what the DSL compiles to; also\n the field reference for the `raw` escape).\n\n## The loop\n\n1. Write / edit `house.wdl` \u2014 the **single shared source** (you and the human\n co-edit it; the app\'s DSL previewer renders it live). You never produce a `.wadi`.\n2. `wadi-skill/architect/scripts/check.sh house.wdl` \u2014 runs the DSL compiler +\n validator (schema + wall/roof geometry) against a **throwaway temp** just for\n feedback; fix any reported error and re-run.\n3. `preview.sh house.wdl` \u2192 read the PNGs (plans / elevations / roof) to check your\n work. (It also compiles to a throwaway temp \u2014 no persistent `.wadi`.)\n\n## Skeleton\n\n```wdl\nhouse MyHouse {\n convention center // ALWAYS use center (wall-centreline coords)\n units feet_inches per_unit 10 // 10 project units = 1 ft\n site { plot (WIDTH, LENGTH) ref (0, 0) }\n defaults { floor_height 120 wall_height 108 slab_thickness 8 wall_thickness 8 }\n\n // parametric core (optional): var, point, grid, configurator\n // component / layer declarations (optional)\n floor 0 "Plinth" { \u2026 } // floors stack in source order (0 = plinth)\n floor 1 "Ground Floor" { \u2026 }\n floor 2 "Loft" { roof \u2026 } // roof lives ALONE on its own top floor\n}\n```\n\nNumbers are **project units** (feet \xD7 10 by default). Names after `house`,\n`room`, `pillar`, `var`, `point`, `grid`, and `use`/`component` are bare\nidentifiers (no spaces); names introduced with the `name` keyword are quoted\nstrings.\n\n## Parametric core (domain-neutral)\n\n```wdl\nvar wallT = 8 // a knob; may reference other vars\nvar pilInset = (pillarW - wallT) / 2\n\npoint House { x = 420, y = 470 } // reference as House.x / House.W / House.L\n // (.W = x, .L = y \u2014 a point doubles as a size)\n\nguides main { // NAMED wall centrelines; publishes main.x1 / main.yA\n x: 1 @ wallT / 2, 2 @ House.W / 2, 3 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L / 2, C @ House.L - wallT / 2\n}\n// each line may add: \u2026 @ <expr> thick <expr> role structural|planning\n// (`grid` is a deprecated alias for a named `guides` object \u2014 still parses.)\n\nguides module { // GENERATED \u2014 a uniform family from origin+spacing\n origin (0, 0) spacing (30, 30) [extent (40, 30)] // origin default (0,0); extent optional\n}\n// Reference a generated line by INDEX: module.x8 (integer) or module.x(<expr>)\n// (call form \u2014 required for fractional/negative/computed indices). `extent`\n// (line counts per axis) only bounds where the lines are DRAWN. A `guides` object\n// is EITHER named (x/y lists) OR generated (spacing) \u2014 never both.\n\nconfigurator { // the owner-facing template you author\n title "Configure your home" // panel heading (optional)\n note "Everything re-flows to fit." // panel subtitle (optional)\n\n slider pillarW "Column size" ft [8 .. 14 step 1] note "help text" // trailing note optional\n number ceiling "Ceiling height" ft\n toggle has_loft "Add a loft"\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n select floorH "Ceiling height" { "9 ft" = 90, "10 ft (std)" = 100 } // labels can be quoted strings\n\n group "Plot" note "about this section" { // sections the panel; label only\n slider W "Plot width" ft [340 .. 520 step 10]\n }\n}\n```\n\nEvery knob binds to a `var` by name (`target`). To expose a plot dimension, model\nit as a `var` and reference it from the point (`point House { x = W }`), then bind\nthe knob to `W` \u2014 knobs target vars, never a point field like `House.W`.\n\n**Formulas are automatic.** Any geometry number can be a formula \u2014 just write the\nexpression instead of a literal (`at (main.x1, main.yA)`, `size (House.W/2, 200)`).\nOperators: `+ - * /`, unary `-`, parentheses, and the functions\n`min max clamp round floor ceil abs`. References: a `var`, a `point`\n(`House.W`), a named guide (`main.x3 - main.x1`), or a generated guide\n(`module.x8`, `module.x(n+1)`). No comparison operators \u2014 gate\nthings with the `min/abs` idiom (see `enabled` below).\n\n## Common attribute tail (every object)\n\nAfter an object\'s geometry, in THIS order, any of:\n\n```\n\u2026 z_offset <expr> enabled <expr> layer "id" [material "id"]\n```\n\n- `enabled <expr>` \u2014 the on/off switch. A `0`/`false` value hides the object. To\n gate on a configurator variable, use a 0/1 formula:\n `enabled 1 - min(1, abs(roof_style - 3))` renders the object only when\n `roof_style == 3`. (This is how one template carries several roofs and shows\n only the chosen one.)\n- `z_offset <expr>` \u2014 lift above the floor base (split levels).\n- `material "id"` \u2014 only on plinth / ground / room / wall / staircase / kitchen /\n roof.\n\n## Objects \u2014 structure & envelope\n\n```wdl\nslab [name "N"] at (x,y) size (w,l) [thickness <t>] // floor_slab\nbeam [name "N"] at (x,y) size (w,l) [height <h>]\nplinth [name "N"] at (x,y) size (w,l) height <h> // raised base (Plinth floor)\nground [name "N"] at (x,y) size (w,l) [height <h>] // terrain plane\npillar Name at (x,y) size (w,l) [height <h>] // (x,y) = TOP-LEFT corner\n```\n\n`at (x,y)` is the **TOP-LEFT CORNER** \u2014 **not the centre** \u2014 for every one of these\n(same as rooms/slabs/beams); `size (w,l)` is width \xD7 length. All accept the common tail.\n\n**Pillars catch people out here.** A column reads as "placed at a point," but `at` is\nstill its corner. To **centre a column on a point** `(cx, cy)` \u2014 a grid node, a room\ncorner \u2014 place it at **`at (cx - w/2, cy - l/2)`**, never at `(cx, cy)`. On a grid, the\n`pilInset` idiom (see `parametric-conventions.md`) does exactly this so columns sit flush.\n\n## Objects \u2014 rooms, walls & openings\n\nA room shows exactly the walls you declare. A **bare room (no `wall` lines) is\nenclosed on all four sides.** List plain walls compactly; give a wall its own line\nonly when it carries a door/window; omit a side to leave it open (verandah).\n\n```wdl\nroom Name at (x,y) size (w,l) [height <h>] [material "\u2026"] {\n connect Kitchen Hall // rooms this room opens into (same floor)\n wall east west north // plain walls \u2014 several in one statement\n wall south { door Main at <offset> [from start|center|end] size (w,h) [open] } // wall WITH openings\n wall west { window W at <offset> [from start|center|end] size (w,h) [sill <s>] [open] }\n item asset { \u2026 } anchor center [gap (gx,gy)] // furniture anchored inside the room\n}\n```\n\n- `connect A B \u2026` records that this room adjoins the named room(s) \u2014 space-separated,\n quote a name with spaces (`connect "Guest Room"`). It\'s design intent + a functional\n test, **not geometry** (the renderer ignores it). May go anywhere in the block and\n repeat. Validated by C11: connected rooms must OVERLAP on a wall AND be passable \u2014\n a door in the overlap, or the shared wall left off BOTH rooms (an open passage).\n- `wall <side>\u2026` sides are `north|south|east|west`. A `wall <side>` line may also\n add `height <h>` / `height_end <h>` (sloped).\n- `door`/`window` `at <offset>` is measured along the wall; `size (width, height)`;\n `window \u2026 sill <s>` sets the sill height; `open` = a bare hole (no leaf/glazing).\n- `from start|center|end` (default `start`) picks which end `<offset>` is measured\n from, so the opening keeps its place when the wall or room scales \u2014 no formula\n needed. `start`: offset from the wall start to the near edge (the legacy default).\n `end`: offset from the wall end to the far edge (`at 0 from end` = flush to the end).\n `center`: signed shift of the opening centre from the wall midpoint (`at 0 from\n center` = centred; the offset may be negative).\n\nA **free-standing wall** (not a room side):\n\n```wdl\nwall Name from (x1,y1) to (x2,y2) [height <h>] [height_end <h>] [facing north|\u2026] {\n \u2026 door/window openings \u2026\n}\n```\n\n- `from`/`to` are the wall\'s **centreline** endpoints; the wall is drawn as a rectangle\n `wall_thickness` wide, centred on that line.\n- **Overlap walls at corners \u2014 they do NOT auto-mitre.** Two free-standing walls that\n merely *touch* at a shared endpoint leave an unfilled square notch (\xBD\xB7`wall_thickness`)\n at the corner, because each is just a rectangle capped at its endpoint. To fill the\n corner, **extend the endpoints so the wall bodies OVERLAP** \u2014 run at least one wall\'s\n end **half the wall thickness past** the shared point (overlapping by the full thickness\n is fine and simplest). For an L of thickness 8 meeting at `(160,40)`:\n\n ```wdl\n wall H from (40, 40) to (164, 40) height 108 // ends 4 (\xBD\xB78) PAST the corner\n wall V from (160, 40) to (160, 160) height 108 // butts into H\'s overlapped body\n ```\n\n (Room walls handle their own corners; this only applies to `wall \u2026 from \u2026 to \u2026`.)\n\n## Objects \u2014 circulation & fittings\n\n```wdl\nstaircase [name "N"] at (start_x, start_y) step (rise, tread, width)\n direction north|south|east|west [climb up|down]\n [total_height <h>] [max_run <r>] [landing_depth <d>]\n [landing_thickness <t>] [turn clockwise|anticlockwise] [flight_gap <g>]\n\nkitchen [name "N"] path ((x,y), (x,y), \u2026) side left|right\n depth <d> height <h> [base_z <z>] // path points are literal numbers\n```\n\n**`climb` picks the anchor + z direction.** Prefer **`climb up`** \u2014 the intuitive way:\n\n- Put the stair on the **LOWER** floor it rises FROM. `at (x,y)` is the **bottom** step\'s\n near corner on that floor; `direction` is the **ascent** direction; the flight climbs UP.\n- `total_height` is the **rise** to the next level (omit \u2192 this floor\'s own height).\n- `max_run` caps a flight\'s run; exceed it and the stair auto-splits into switchback\n flights with turn landings (`landing_depth`/`turn`/`flight_gap` tune the switchback).\n\n```wdl\nfloor 1 "Ground Floor" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name "Stair" at (212, 64) step (7, 11, 44) // bottom = this floor\n direction south climb up // ascends south to the floor above\n}\n```\n\n`climb down` is the legacy mode (**DEFAULT** for older configs): put the stair on the\n**UPPER** destination floor; `at (x,y)` is the **top** connection, `direction` is the\ndescent, and `total_height` defaults to the floor-below\'s height. New designs should use\n`climb up`.\n\n// three ways to name the GLB, in order of preference:\nitem [name "N"] f."sofa" // 1. from an imported module (see Imports)\nitem [name "N"] "sofa" // 2. a same-file / bare-imported `asset` id\nitem [name "N"] asset { id "sofa" src "\u2026/sofa.glb" dims (w,h,d) [category "\u2026"] } // 3. inline one-off\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nPrefer the module form (`item f."bed_double"`) \u2014 `import "std-furniture" as f`\nonce and every piece is a short id, no URLs. The bare form (`item "sofa"`) needs\na matching top-level `asset "sofa" \u2026` in the file (or a bare `import`). The inline\n`asset { \u2026 }` block is only for a one-off GLB not in any pack. All three produce\nthe identical `{id,src,dims}` downstream. Furniture `dims` are the real-world size\nin **metres** `(width, height, depth)`; `src` is a GLB URL (an unreachable GLB\nshows a placeholder box, never a blank). `anchor` is one of `top-left top-center\ntop-right center-left center center-right bottom-left bottom-center bottom-right`.\n\n**Orientation \u2014 this is how you point furniture the right way.** A piece\'s FRONT\n(the side you sit at / the doors / the open side) faces a known compass direction\nper its `rotation` (degrees):\n\n| `rotation` | front faces |\n|---|---|\n| `0` | **South** (the plot front / entrance side, +Y) |\n| `90` | East |\n| `180` | North |\n| `270` | West |\n\nSo a sofa against the NORTH wall (facing into the room, i.e. south) is `rotation\n0`; against the SOUTH wall (facing north) it\'s `rotation 180`; against the WEST\nwall (facing east) `rotation 90`. The floor plan (`wadi_preview plans`) draws a\nsmall triangle on each piece\'s front edge so you can verify the way it points;\nfor a definitive 3D check use `wadi_capture_3d({ room: "\u2026" })` (first-person from\ninside the room).\n\n**Anchoring auto-orients.** When you `anchor` a piece to a wall and DON\'T give a\n`rotation`, it automatically faces away from that wall, into the room \u2014 `anchor\ntop-center` \u2192 faces south, `bottom-center` \u2192 north, `center-left` \u2192 east,\n`center-right` \u2192 west (a corner uses its north/south edge). So `item f."bed_double"\nanchor top-center` needs no rotation. An explicit `rotation` always overrides,\nand the derived value is written into the resolved model, so the plan notch and\nthe 3D view show it \u2014 anchoring never changes facing silently.\n\n## Objects \u2014 GLB models (`model`)\n\nA `model` places a **GLB at real metre scale** and manipulates it through a **rig**\nof named-node operations. It is distinct from `item` (catalog furniture): `model`\ntargets the GLB\'s internal node graph, so you can hide, move, recolour, or array\nsub-parts of one asset.\n\n```wdl\nmodel [name "N"] <asset> at (x,y) [rotation <deg>] [scale <s>] {\n translate "nodeName" (x,y,z) // move a named node\n rotate "nodeName" (x,y,z) // rotate it (degrees)\n scale "nodeName" (x,y,z) // scale it\n visible "nodeName" false // hide a node\n material "nodeName" color "#rrggbb" // recolour a node\n array "nodeName" count N step (dx,dy,dz) { \u2026 nested rig ops \u2026 }\n}\n```\n\n`<asset>` names the GLB the same three ways as `item` (module `ns."id"`, bare\n`"id"`, or an inline `asset { \u2026 }` block). The rig ops run against the GLB\'s named\nnodes; `array` replicates a node `count` times along `step`, and its nested block\napplies further ops per copy. Use `model` for a rigged mechanism (a fan, a louvre\nbank, a spiral of balusters); use `item` for a plain piece of furniture.\n\n## Imports & modules (reusable `.wdl` libraries)\n\nA `.wdl` file can be a **module** \u2014 top-level declarations (no `house` needed) \u2014\nthat another file `import`s. Two bundled ones: `std-furniture` (asset pack \u2192\n`item ns."id"`) and `konkan/base` (goal-tagged component pack \u2192 `use ns.Comp`;\nStairwell, Verandah, Otla, Bathroom, Kitchen, TulsiVrindavan, Parapet). The\n`konkan_cottage` example (`wadi_examples`) assembles a whole house from both.\n\n```wdl\nhouse Home {\n import "std-furniture" as f // aliased: refer to its assets as f."<id>"\n // import "std-furniture" // bare: its ids drop into scope for item "<id>"\n floor 1 "G" slab_thickness 0 {\n room Bed at (20,20) size (160,200) { wall north east south west\n item f."bed_double" anchor center }\n }\n}\n```\n\nA module file itself is just top-level `asset` and `component` decls:\n\n```wdl\n// my-furniture.wdl \u2014 a house-less module (a reusable library)\nasset "daybed" src "https://\u2026/daybed.glb" dims (1.8, 0.4, 0.9) name "Daybed" category "Living"\n```\n\nOver MCP, `wadi_modules` lists importable modules and `wadi_module "<name>"`\nshows a module\'s asset ids + dimensions (filter with a `query`). Import refs\nresolve by name against the bundled `std-*` packs (a local `modules/` search\npath and git refs come later).\n\n## Objects \u2014 roof (one object; flat / shed / gable / hip)\n\nThe roof lives ALONE on its own top floor and you never set its Z (see\n`roof-v2-guide.md`). `endpoint`: `closed` = hip triangle, `open` = gable end-wall.\n\n```wdl\nroof [name "N"] pitched|shed|flat\n [endpoint open|closed]\n [slope angle <deg> | slope height <ridge_h>] // symmetric pitch (one value)\n [slope angle (<left>, <right>)] // asymmetric (saltbox) gable \u2014 angle pair\n [overhang <o>] [slab_thickness <t>] [parapet <h> x <t>] [gable_wall_thickness <t>] {\n segment "id" from (x,y) to (x,y) width <w>\n [high_side left|right] // shed only\n [start_endpoint open|closed] [end_endpoint open|closed]\n [hip_setback (a,b)] [gable_overhang (a,b)] [hip_ridge_extension (a,b)]\n // hip_ridge_extension = a flying-rafter ridge stub PAST the hip apex; OFF by\n // default (0). Add it only for a deliberate ridge overhang, not routinely.\n [overhang <o>] // uniform eave, all four sides\n [overhang_start <o>] [overhang_end <o>] // per-side along the axis (shed;\n // on a gable end = gable_overhang)\n [overhang_low <o>] [overhang_high <o>] // SHED eaves (down-slope / up-slope)\n [overhang_left <o>] [overhang_right <o>] // PITCHED eaves (left / right of ridge)\n [tie_beams N]\n truss "segId" fink|mono_pitch at (pos, pos, \u2026)\n }\n```\n\nSegment `from`/`to`/`width` and the `hip_setback`/\u2026 values accept formulas, so a\nroof scales with the plot (e.g. `width House.W`, `hip_setback (Verandah.L, Padvi.L)`).\n\n**Per-side overhang (cantilever one edge).** `overhang <o>` sets a uniform eave on\nall four sides. Any sloping roof can override a side independently \u2014 each defaults to\n`overhang`. **Along the axis:** `overhang_start` / `overhang_end` (on a shed, or a\ngable open end \u2014 there they\'re the same as `gable_overhang`; a hip end is geometric,\ntuned via `hip_setback`). **Eaves:** `overhang_low` / `overhang_high` on a **shed**\n(down-slope / up-slope); `overhang_left` / `overhang_right` on a **pitched** roof\n(the two eaves either side of the ridge). A bigger eave overhang also drops that\neave\'s edge along the same pitch, so the slope stays planar. (Per-eave on a *pitched*\nroof is single-segment only \u2014 on a multi-segment roof the eaves share one height so\njoints line up.)\n\n**Asymmetric gable (saltbox) \u2014 an angle pair.** A pitched roof\'s `slope` gives both\nfaces the same pitch (a symmetric gable). To make the two sides different, give\n`slope angle` a **pair** instead of one value \u2014 `slope angle (45, 25)`. A single value\nis symmetric; a pair is asymmetric, and that\'s the whole distinction (no separate\nkeywords, nothing half-settable). The two eaves stay put (footprint unchanged) and the\nridge shifts across the width so each face takes its angle; the gable-end triangle and\ntrusses follow. The pair is `(left, right)` by the segment\'s left normal \u2014 the **same\nsides as** `overhang_left`/`overhang_right`. Angles are measured to the wall-top eave\nline. `height` is single-value only (both faces share one ridge line). Intended for a\nsingle-segment gable (`endpoint open`); on a hip end the ridge shift skews the hip.\n\n```wdl\nroof pitched endpoint open slope angle (45, 25) {\n segment "s0" from (House.W/2, 0) to (House.W/2, House.L) width House.W\n}\n```\nIdiom: keep the roof FOOTPRINT (its supported edges) on the main room, then cantilever\none eave to cover an entry landing / stair \u2014 end the axis on the room wall and set a big\n`overhang_end`:\n```wdl\n// footprint ends on the main room\'s east wall (x204 centreline \u2192 x208 outer);\n// the east eave reaches 258, covering a landing that sticks out to x256.\nsegment "seg0" from (4,124) to (204,124) width 240 high_side right overhang 25 overhang_end 50\n```\n\n**Roof coordinates are wall centrelines (under `convention center`), same as\nrooms.** `from`/`to` is the segment\'s ridge/axis and `width` its span *centred on\nthat axis*. Author them on the **same centreline grid as the walls** \u2014 a segment\nwhose axis + width match the rooms\' centrelines auto-grows to the **outer wall\nface** on every side (the compiler extends the axis by \xBD\xB7wall_thickness at each end\nand widens by wall_thickness, exactly the grow a room gets). `overhang` then\nextends *beyond* the outer face. So to cover a footprint spanning wall centrelines\n`x1..x2` (E\u2013W) and `yA..yB` (N\u2013S), write `from (x1, (yA+yB)/2) to (x2, (yA+yB)/2)\nwidth (yB - yA)` \u2014 do **not** add \xBD-wall fudge factors; the convention handles it.\n(Before this, a roof drawn on the grid sat half a wall-thickness *inside* the walls.)\n\n## Components & layers\n\n```wdl\ncomponent Bench { // a reusable mini-house in LOCAL coords (origin 0,0)\n param blen = 60 label "Bench length"\n beam name "Top" at (0,0) size (blen, 18) height 6\n}\nuse Bench as "B1" at (x,y) [rotation <deg>] with { blen = 80 } // stamp onto a floor\n\nlayer "structure" "Structure" [color "#rrggbb"] [group "Frame"] // per-house layer registry\n```\n\nA component may carry a **`goal`** \u2014 a short description of what it accomplishes,\nthe discovery key for module lookup (`wadi_module` / a `wadi_modules` query):\n\n```wdl\ncomponent Stairwell goal "climb to the next floor" {\n param rise = 116\n staircase name "Stair" at (0,0) step (7,11,44) direction south total_height rise\n}\n```\n\nComponents can also come from an **imported module** (see *Imports & modules*),\nstamped with a namespaced `use ns.Comp`:\n\n```wdl\nhouse Home {\n import "konkan/base" as kb // Stairwell, Verandah, Otla (goal-tagged)\n floor 1 "G" slab_thickness 0 {\n room Hall at (20,20) size (200,200) { wall north east south west }\n use kb.Stairwell at (60,60) with { rise = 116 } // param args use `=`, not `:`\n }\n}\n```\n\n`use ns.Comp` expands byte-identical to an inline `component`. Components **nest\nfreely**: a library component may `use` a sibling, `use` a component from a\nlibrary it itself `import`s, and place `item ns."id"` furniture from its own\nimports \u2014 imports resolve **transitively** (cycles are a compile error).\nUn-overridden `param`s fall back to their declared defaults.\n\n`rotation <deg>` (optional, yaw\xB0: 0=south, 90=east) turns the whole stamped\nassembly about its origin. **Right angles (0/90/180/270) are exact for any\ncomponent** (rooms/pillars/beams/slabs swap dims + remap wall sides; furniture\nturns with them). A **non-right angle** is allowed **only for a furniture-only\ncomponent** (items/free walls rotate to any angle); a free angle on a component\nthat contains a room/pillar/beam/slab/staircase is a compile error (arbitrary\nstructural rotation is a future feature).\n\n### Promote a component to a primitive (`expose as`)\n\nA component can be **promoted to a runtime typed primitive** with `expose as`, so it\nreads and behaves like a built-in object type (`pack.type`) rather than a `use`\ninstance:\n\n```wdl\ncomponent Bench goal "a place to sit" expose as garden.bench [layer "id"] [label "\u2026"] {\n param length = 60\n beam name "Seat" at (0,0) size (length, 18) height 6\n}\n```\n\n`expose as <pack>.<type>` names the promoted primitive (a dotted `pack.type` id);\noptional `layer "id"` and `label "\u2026"` set its default layer and menu label. Once\nexposed, the component is available as a first-class object type named `pack.type`\nthroughout the model, its `param`s becoming that type\'s fields.\n\n## Template metadata (`template`)\n\nA `template { \u2026 }` block **self-describes a template** on its `.wdl` / `.wadi`, so a\ngallery can index the file without a separate catalog. It carries display metadata\nonly (no geometry):\n\n```wdl\ntemplate {\n title "Coastal Cottage"\n description "A compact single-storey Konkan home."\n style "konkan"\n roof "gable"\n tags ("coastal", "1BHK", "compact")\n thumbnails ("thumb-iso.png", "thumb-plan.png")\n min_plot (340, 400)\n}\n```\n\nFields: `title`, `description`, `style`, `roof`, `tags` (a list), `thumbnails` (a\nlist of image paths), `min_plot` (the minimum plot the template needs). The folder\nof self-describing files IS the catalog.\n\n## The `raw` escape (rarely needed)\n\nAnything the first-class syntax doesn\'t cover can be written as literal JSON per\nthe `.wadi` schema (`data-model.md`):\n\n```wdl\nraw "type" { "field": 1, "formulas": { "field": "= expr" } }\n```\n\n## DSL-specific pitfalls\n\n- **`convention center` and `units \u2026 per_unit 10`** belong at the top of every\n `house` \u2014 same as the JSON path. All the `coordinate-system.md` rules (Y-down,\n units, centreline abutment) apply identically; the DSL just writes them shorter.\n- **Formulas are bare expressions**, not `"= \u2026"` strings \u2014 the compiler emits the\n `= \u2026` form for you. Write `at (main.x1, main.yA)`, not `at ("= main.x1", \u2026)`.\n- **`name "\u2026"` is quoted; `room`/`pillar`/`var`/grid-line names are bare** ids\n (no spaces, and not a reserved word like `width`, `height`, `size`, `at`).\n- **Roof alone on the top floor**; segment widths/positions come from the walls\n they sit on. See `roof-v2-guide.md`.\n- **A pillar\'s `at` is its TOP-LEFT corner, not its centre.** To centre a column on\n `(cx,cy)`, author `at (cx - w/2, cy - l/2)`.\n- **Free-standing walls don\'t auto-mitre at corners** \u2014 extend endpoints so the wall\n bodies overlap (\u2265 \xBD\xB7`wall_thickness` past the shared point), or the corner is left\n as a gap.\n- **Prefer `climb up` \u2014 it\'s bottom-anchored.** Put the stair on the LOWER floor it\n rises FROM; `at` is the bottom step and the flight ascends into `direction`.\n Top-anchored is the legacy `climb down` (default only for older configs): the stair\n sits on the UPPER floor and descends. `check.sh` C5 flags a stair that lands below\n ground. See the staircase note.\n- **Structural conventions are enforced** \u2014 `check.sh` fails on floating floors\n (plinth-floor `height` \u2260 plinth block height; a no-slab floor with nonzero\n `slab_thickness`) and warns on exterior room sides left open. See\n `conventions.md`; the WDL editor shows the same findings in its status pill.\n- Compile after **every** edit; a parse error means the `.wadi` wasn\'t updated, so\n the live model just won\'t change \u2014 never silently wrong.\n'
|
|
434287
434379
|
},
|
|
434288
434380
|
"conventions": {
|
|
434289
434381
|
"title": "Structural coding conventions (C1/C2/C3\u2026)",
|
|
434290
|
-
"body": "# Wadi structural conventions (coding guidelines)\n\n<!-- GENERATED FILE \u2014 do not edit conventions.md by hand. It is built from\n conventions.preamble.md + the constraint modules in editor/src/lint/constraints.\n Regenerate with `npm --prefix editor run gen-conventions-doc`. -->\n\nA house can be **well-formed but structurally unsound**: it passes the schema and\nthe wall/roof geometry check, yet the building would not stand up \u2014 a floor floats\nin mid-air, a room is open to the weather, walls hover above a phantom slab. These\nare the *coding conventions* every Wadi house must follow.\n\nThey are **formally defined here** and **enforced in code** by the structural\nlinter (`editor/src/lint/structural.ts`), which runs automatically:\n\n- in **`check.sh`** (and `validate.mjs`) \u2014 **errors fail** the check, **warnings**\n are printed but advisory;\n- in the **DSL editor** \u2014 the status pill shows the count and lists every finding\n in its hover tooltip, while still rendering the model so you can *see* the\n unsound part.\n\nEach finding carries its convention id (`C1`, `C2`, \u2026). Each convention is a\nself-contained module under `editor/src/lint/constraints/` (its check + this doc +\nits example fixtures), and **this file is generated from those modules**\n(`editor/scripts/gen-conventions-doc.mjs`) \u2014 so the doc and the linter cannot\ndrift. Add a rule by adding a constraint module and regenerating.\n\n---\n\n## The vertical model (why C1 and C3 exist)\n\nFloors stack in source order (floor 0 = the Plinth floor). The renderer places\nthem like this (`editor/src/three/coords.ts`):\n\n- **A floor's base elevation = the running sum of the previous floors' `height`\n only.** `wall_height` and `slab_thickness` do **not** raise the next floor.\n- The **plinth block** is drawn to its *own* `height`. So the floor above sits at\n `plinth-floor.height`, while the plinth top is at `plinth.height` \u2014 they must be\n equal or the floor above floats/sinks by the difference. \u2192 **C1**\n- **`slab_thickness` lifts a floor's walls within its band** (`wallZ = base +\n slab_thickness`) \u2014 it is the deck the walls stand on. With no slab object there\n is no deck, so the walls float by that amount. \u2192 **C3**\n\n`height`, `wall_height`, and `slab_thickness` are otherwise **independent** \u2014 the\nmodel enforces no relationship between them. These conventions add the few\nrelationships that structural soundness *does* require.\n\n---\n\n## C1 \u2014 The plinth floor's height must match the plinth block height \xB7 **error**\n\n**Statement.** A floor that carries a `plinth` object (the Plinth floor) must set an explicit `height`, and that height must equal the plinth block's `height`.\n\n**Rationale.** The floor above is stacked at `plinth-floor.height`; the plinth block rises to `plinth.height`. If they differ, the floor above floats above the plinth (`floor.height > plinth.height`) or sinks into it (`<`). If the floor `height` is omitted it silently defaults to `100`, almost never the plinth height.\n\n**Fix.**\n\n```wdl\nfloor 0 \"Plinth\" height 40 { // == the plinth block height below\n ground name \"Ground\" at (0,0) size (500,500)\n plinth name \"Plinth\" at (\u2026) size (\u2026) height 40\n}\n```\n\n(If the plinth block omits its own `height`, it follows the floor height and is consistent by construction \u2014 but set the floor `height` explicitly anyway, so the stack is not left to the default.)\n\n---\n\n## C2 \u2014 A room must wall every exterior side \xB7 **warning**\n\n**Statement.** A room shown with a **partial** `walls` list must still wall every side that faces **outside** (no room beyond it). Interior (shared) sides may be omitted \u2014 the neighbour's wall stands on the shared centreline.\n\n**Rationale.** A room shows exactly the walls it declares; a **bare room (no `wall` lines) is enclosed on all four sides**. But the moment you add a `wall` line to hang a door or window, the room switches to a *whitelist* \u2014 every side you don't list is now a hole. An exterior hole leaves the room open to the weather. It is a **warning**, not an error, because an open exterior side is sometimes intentional (a verandah / open padvi).\n\n**Fix.**\n\n```wdl\nroom Living at (x,y) size (w,l) {\n wall east west // plain exterior sides \u2014 enclosed\n wall south { door Main at 120 size (36,84) }\n wall north { window N1 at 100 size (60,50) sill 35 }\n}\n```\n\n---\n\n## C3 \u2014 A floor with no slab must set slab_thickness to 0 \xB7 **error**\n\n**Statement.** A floor that has wall/room objects but **no `floor_slab` object** must set `slab_thickness 0`.\n\n**Rationale.** `slab_thickness` is the deck the floor's walls stand on (`wallZ = base + slab_thickness`). Its default is `8`. With no slab object there is no deck, so every wall on the floor floats `slab_thickness` units above the floor base. Setting it to `0` puts the walls on the floor base; alternatively, model the deck by adding a `slab`.\n\n**Fix.**\n\n```wdl\nfloor 1 \"Ground\" slab_thickness 0 { // no slab modelled \u2192 walls sit on the base\n room Studio at (\u2026) size (\u2026) { \u2026 }\n}\n```\n\n*(This does not fire on a floor that carries no walls/rooms \u2014 e.g. a Plinth floor of just `ground` + `plinth`, or a roof-only top floor \u2014 where `slab_thickness` is harmless.)*\n\n---\n\n## C4 \u2014 A stacked floor's height should equal wall_height + slab_thickness \xB7 **warning**\n\n**Statement.** A floor that carries a floor above it (and has walls/rooms) should set `height` = `wall_height` + `slab_thickness`.\n\n**Rationale.** The next floor sits at `base + height`; this floor's walls stand on the deck and reach `base + slab_thickness + wall_height`. When `height` is larger, the floor above leaves a gap over the walls; when smaller, the walls poke through it. It is a **warning** \u2014 a deliberate gap is legitimate (a service plenum, a deep transfer beam) \u2014 but usually they should match.\n\n**Fix.**\n\n```wdl\ndefaults { floor_height 116 wall_height 108 slab_thickness 8 } // 108 + 8 = 116\n```\n\n*(Skipped for the plinth floor \u2014 governed by C1 \u2014 and for the topmost floor, since nothing stacks on its walls.)*\n\n---\n\n## C5 \u2014 A staircase must land on a floor, not below ground \xB7 **warning**\n\n**Statement.** A staircase's descent must not carry it below the ground plane (z < 0).\n\n**Rationale.** Only a `climb down` (top-anchored) stair can fall below ground: you place it on the **upper** floor and it **descends**. Put it on the wrong floor, or give it too large a `total_height`, and the expanded flight lands **below ground** \u2014 it still draws in the 2D plans (which ignore Z) but is **buried and invisible in 3D**, with no other error. A `climb up` stair is anchored on its own floor and ascends, so it never trips this.\n\n**Fix.**\n\nPrefer **`climb up`**: put the stair on the **lower** floor it rises FROM and let it ascend.\n\n```wdl\nfloor 1 \"Ground Floor\" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name \"Stair\" at (212, 64) step (7, 11, 44) // `at` = the BOTTOM (this floor)\n direction south climb up // ascends to the floor above\n}\n```\n\n(Or, if you must keep it `climb down`, move it **up one floor** or reduce `total_height`.)\n\n---\n\n## C6 \u2014 Openings on the same wall must not overlap \xB7 **error**\n\n**Statement.** Two openings (doors/windows) cut into the **same physical wall** must not overlap along it. This includes openings that belong to **two different rooms sharing a boundary wall**.\n\n**Rationale.** Each opening is a boolean-subtract from the wall. Overlapping spans merge into one ragged hole (or fight over the same brick), which is never what you meant \u2014 and on a shared wall it silently punches a bigger gap than either room's plan shows.\n\n**Fix.**\n\nOffset or narrow one opening so the spans are disjoint. An opening's span is its resolved `[offset, offset+width]` along the wall \u2014 the `from start|center|end` anchor is honoured (its offset is converted to a start-based position first, exactly as the renderer does).\n\n---\n\n## C7 \u2014 Furniture items should not overlap \xB7 **warning**\n\n**Statement.** Two furniture `item`s whose plan footprints overlap are flagged \u2014 as a **warning**, because it is sometimes intentional (a rug under a table, a lamp on a desk, deliberately stacked pieces).\n\n**Rationale.** More often it's a placement slip \u2014 two beds dropped on the same spot, or an anchored piece that reflowed into another when a room was resized. The footprint used is the item's rotated bounding box (yaw-aware), so it matches what the plan draws.\n\n**Fix.**\n\nReposition one item, or ignore the warning if the overlap is deliberate.\n\n---\n\n## C8 \u2014 Two abutting rooms need a partition between them \xB7 **warning**\n\n**Statement.** Where two rooms share a boundary line and **neither** declares a wall on it, there is no partition between them.\n\n**Rationale.** A bare room (no `wall` lines) is enclosed on all four sides, so two bare neighbours have two walls on their shared line. But once **both** rooms switch to partial `walls` lists and both omit the shared side, the centreline is left open \u2014 the rooms merge into one space with no divider. C2 only guards *exterior* sides; this is its interior counterpart. It is a **warning** because an intentional open-plan link (kitchen into living) is legitimate.\n\n**Fix.**\n\nDeclare the wall on **one** of the two rooms (the neighbour's wall stands on the shared centreline, so one is enough):\n\n```wdl\nroom Kitchen at (\u2026) size (\u2026) { wall north south east } // east = the shared line\nroom Living at (\u2026) size (\u2026) { wall north south west }\n```\n\n---\n\n## C9 \u2014 A floor's slab_thickness should match its slab object's thickness \xB7 **warning**\n\n**Statement.** When a floor carries a `floor_slab` object with an explicit `thickness`, that thickness should equal the floor's `slab_thickness`.\n\n**Rationale.** The floor's `slab_thickness` is the deck the walls stand on (`wallZ = base + slab_thickness`); the slab object's own `thickness` is how thick the slab MESH is drawn. If they differ, the walls sit at the floor's `slab_thickness` while the slab top is at the object's `thickness`, so the walls float above or sink into the drawn deck. (A slab with no explicit `thickness` follows the floor's `slab_thickness` and is consistent by construction \u2014 this only fires when both are set and disagree.)\n\n**Fix.**\n\nMake them equal \u2014 most simply, drop the slab's explicit `thickness` so it follows the floor:\n\n```wdl\nfloor 1 \"Ground\" slab_thickness 8 {\n slab name \"Deck\" at (\u2026) size (\u2026) // no thickness \u2192 uses 8\n}\n```\n\n---\n\n## C10 \u2014 The roof should cover the rooms of the top occupied floor \xB7 **warning**\n\n**Statement.** Every room on the top occupied floor should sit under a roof segment \u2014 no room left entirely uncovered.\n\n**Rationale.** The roof's segments span a plan area (each segment's ridge line \xB1 its `width`). A room on the top floor whose footprint does not overlap **any** roof segment has open sky above it \u2014 usually a roof that was sized to the wrong footprint, or a room added after the roof. (Only a *completely* uncovered room is flagged, so eave overhangs and partial coverage never false-warn; a house with no roof at all \u2014 a terrace \u2014 is not flagged.)\n\n**Fix.**\n\nExtend or add a roof segment to span the room, or reduce the room. Roof segments cover `start \u2192 end` along the ridge, `width` across it, so grow `width`/`end` (or the plot variables they derive from) until the room is under it.\n\n---\n\n## C11 \u2014 A declared connection must overlap on a wall and be passable (door or open) \xB7 **error**\n\n**Statement.** For every `connect`ion a room declares, the two rooms must **overlap on a wall** (not necessarily the whole wall), and that overlap must be **passable**: either a **door** lies in it, or the wall is **left off both rooms** (an open passage).\n\n**Rationale.** A connection is a FUNCTIONAL requirement \u2014 `Living` opens into `Kitchen`. It is design intent, not geometry (the renderer never draws it), so this constraint is what verifies the intent is physically realized. It fails two ways: the rooms' walls don't overlap at all, or they overlap but a solid wall (present on either room, no door in the overlap) blocks the way. No door is ever generated \u2014 a room authors its own openings, or omits the shared wall to leave the rooms open to each other.\n\n**Fix.**\n\nOverlap the two rooms on a wall, then EITHER put a door in the overlap (on either room), OR omit that wall on both:\n\n```wdl\n// door in the shared wall\nroom Living at (\u2026) size (\u2026) { connect Kitchen wall east { door D at 80 size (40,210) } }\nroom Kitchen at (\u2026) size (\u2026)\n\n// open passage \u2014 neither room walls the shared side\nroom Living at (\u2026) size (\u2026) { connect Kitchen wall north south west }\nroom Kitchen at (\u2026) size (\u2026) { wall north south east }\n```\n\n---\n\n## C12 \u2014 Rooms should not overlap (they share walls, not floor area) \xB7 **warning**\n\n**Statement.** Two `room`s on the same floor should not overlap in plan \u2014 flagged as a **warning**, because it is occasionally intentional (embedding a corner room to carve an L-shaped space). Adjacent rooms may **touch** on a shared wall (their edges coincide); a larger intersection is reported.\n\n**Rationale.** Rooms usually share walls, not floor area. A real overlap means two rooms were placed on the same spot \u2014 the renderer draws one over the other. It most often happens when a room is placed by absolute coordinates, or when a band (a verandah, a corridor) is dropped across an existing wing. C11 checks that declared connections are realized; this checks that the geometry is physically consistent. It stays a warning because an L-shaped room is modelled by overlapping a small corner room onto a larger bounding one.\n\n**Fix.**\n\nIf the overlap is unintended, move or resize one room so they only touch on a shared wall (prefer relative placement \u2014 abut a neighbour on a side \u2014 over absolute coordinates that can land on top of another room). Ignore the warning if it is a deliberate corner embed.\n\n---\n\n## C13 \u2014 The lowest floor should carry a plinth \xB7 **warning**\n\n**Statement.** A house should rest on a plinth: the lowest floor should contain a `plinth` object.\n\n**Rationale.** The plinth is the raised base the building sits on \u2014 it lifts the ground floor above grade and gives the walls a footing. A lowest floor with rooms but no plinth reads as a slab-on-grade shortcut; most Konkan houses want an explicit plinth. (A style guide, so it only warns \u2014 a deliberately plinth-less design is allowed.)\n\n**Fix.**\n\nAdd a `plinth` to the lowest floor (usually the Plinth floor 0, alongside the `ground`), sized to cover the built footprint.\n\n---\n\n## C14 \u2014 The highest floor should carry a roof \xB7 **warning**\n\n**Statement.** A house should be capped by a roof: the highest floor should contain a `roof` object.\n\n**Rationale.** The roof sits on its own floor stacked above the walls (see the roof convention). A design whose top floor has no roof leaves the house open \u2014 usually a roof that was forgotten, or a floor added above the roof. (A style guide, so it only warns \u2014 a deliberate flat terrace with no roof is allowed.)\n\n**Fix.**\n\nAdd a `roof` to the highest floor (a floor stacked above the top occupied floor), with segments spanning the footprint.\n\n---\n\n## C15 \u2014 The plinth should cover the rooms that rest on it \xB7 **warning**\n\n**Statement.** The plinth footprint should contain every room on the lowest occupied floor \u2014 no ground-floor room sticking out past the plinth.\n\n**Rationale.** The plinth is the base the ground floor stands on. A room whose footprint extends beyond the plinth has part of its floor unsupported by the base. (Upper floors that cantilever past the plinth are a different case \u2014 those want pillars; see the cantilever guidance. This checks only the floor that sits directly on the plinth.)\n\n**Fix.**\n\nGrow the plinth (its size, or the plot variables it derives from) to cover the room, or pull the room back within the plinth.\n\n---\n\n## C16 \u2014 A room overhanging the floor below should have pillars under it \xB7 **warning**\n\n**Statement.** If a room on floor N extends beyond the rooms of the floor below (a cantilever), there should be pillars supporting the overhang.\n\n**Rationale.** An upper-floor room that sticks out past the walls below has nothing under its overhang. In a real build that extension needs columns at its outside edge. This warns when an overhanging room has no pillar anywhere near it. (A style guide \u2014 a genuinely cantilevered slab design is allowed; the warning just flags the missing support.)\n\n**Fix.**\n\nAdd `pillar` objects at the outside of the extension (under the overhanging edge), or pull the room back over the floor below.\n\n---\n\n## C17 \u2014 A hip roof segment's span should not exceed its ridge run \xB7 **warning**\n\n**Statement.** On a pitched roof with closed (hip) ends, a segment's span (`width`) should not exceed its ridge run (the `start`\u2192`end` length).\n\n**Rationale.** A closed hip end pulls the ridge inward by half the span to make room for the hip face. When the span exceeds the run, the two hip ends would meet past the centre, so the roof is clipped to a pyramid (the hip faces meet at a single apex) rather than the intended ridged hip. That is almost always a mis-oriented segment \u2014 the ridge drawn along the SHORTER dimension. It still renders as a valid pyramid, so this only warns.\n\n**Fix.**\n\nOrient the ridge along the LONGER dimension: swap the segment's `start`/`end` so it runs the long way, and set `width` to the shorter span. If a pyramid is genuinely intended, ignore this.\n\n---\n\n## C18 \u2014 Don't roof the same area twice \xB7 **warning**\n\n**Statement.** A roof segment should not sit entirely within an area another roof segment already covers.\n\n**Rationale.** Each roof segment spans a plan area (its ridge line \xB1 `width`). When a new segment (often a whole new `roof` object an agent added) falls completely inside the area an existing roof already covers, the two roofs overlap \u2014 redundant geometry that renders as z-fighting and doubles the material take-off. Almost always the fix is to extend the existing roof, not add another. (Only a segment FULLY inside prior coverage is flagged, so abutting segments and ridge joints in a legitimate multi-segment roof never false-warn.)\n\n**Fix.**\n\nRemove the redundant roof/segment, or if you meant to cover more area, extend an existing segment's `width`/`end` instead of adding an overlapping one.\n\n---\n\n## C19 \u2014 Prefer one or two flights per floor when space allows \xB7 **warning**\n\n**Statement.** A staircase should climb a floor in one or two flights unless the floor space is genuinely tight.\n\n**Rationale.** A box-model staircase derives its flight count from the run it is given: too short a box forces extra switchback flights. Agents routinely under-size the box and get cramped 3-4 flight stairs where the floor had room for a straight run or a single U-turn. Fewer flights are easier to build and to walk. (A warning, since a tight plot may legitimately need a compact switchback.)\n\n**Fix.**\n\nLengthen the staircase along its run axis (the box `length` for a N/S stair, `width` for E/W) to the reported minimum, or reduce `landing_depth`. The warning gives the exact length for one and two flights.\n\n---\n\n## C20 \u2014 A staircase's top landing must reach a room \xB7 **warning**\n\n**Statement.** The top landing of a staircase should abut (or sit inside) a room on the floor it arrives at, so there is a way off the stair onto the floor.\n\n**Rationale.** A switchback's arrival landing lands wherever the run ends, which is hard to predict and easy to get wrong (often the direction is simply flipped). When the top landing ends against a blank wall or in open space, the stair reaches the next level but there is no way onto the floor. This checks the resolved arrival rectangle against the rooms of the arrival floor. (A warning, since a landing that opens onto an outdoor terrace may not overlap a room.)\n\n**Fix.**\n\nPlace the staircase so its top landing meets a room (leave that room's wall open there or add a door), or flip the `direction`/`turn` so the landing ends on the room side. The warning reports the arrival rectangle and which way it faces.\n\n---\n\n## C21 \u2014 The plinth should extend under a staircase \xB7 **warning**\n\n**Statement.** A staircase on the lowest occupied floor should sit entirely on the plinth \u2014 the plinth footprint should cover it.\n\n**Rationale.** The plinth is the base the ground floor stands on. An external staircase added past the building edge has its flights and landings resting on nothing unless the plinth is extended under it. Agents routinely add a stair to the outside and forget to grow the plinth. (Checks only the lowest occupied floor, the one that sits on the plinth; upper-floor stairs bear on that floor's slab.)\n\n**Fix.**\n\nGrow the plinth (its size, or the plot variables it derives from) so it covers the whole staircase footprint, or move the staircase inside the building over the plinth.\n\n---\n\n## C22 \u2014 A staircase needs an enclosing room or pillars to carry its landings \xB7 **warning**\n\n**Statement.** A staircase should be enclosed by a room of (at least) its own footprint, or have pillars under it \u2014 something to carry the flights and turn landings.\n\n**Rationale.** Switchback landings are elevated slabs; the flights land on them. In a real build the surrounding walls (a stairwell) or columns carry that load. A free-standing staircase with no enclosing room and no pillars has landings hanging in the air. (A warning: an open stair against a structural wall may be fine, but the common agent mistake is a stair floating in open space.)\n\n**Fix.**\n\nPut the staircase inside a room that covers its footprint (the walls carry the landings), or add `pillar` objects under the landings.\n\n---\n\n## C23 \u2014 A staircase's steps should be a realistic size \xB7 **warning**\n\n**Statement.** A staircase's `step_rise` and `step_tread`, converted to real-world size, should fall in the human range \u2014 about a 6-7 in rise and a 10-12 in going.\n\n**Rationale.** Stair steps are physically fixed regardless of the house, so they are the reliable tell for a scale mistake. Agents often copy a staircase from another example that is in different units (metres vs feet), producing steps that are absurdly large or tiny. This converts the steps to inches using the model's `units` and flags anything outside a generous human range.\n\n**Fix.**\n\nRescale the staircase to THIS model's units. The warning gives the sensible `step_rise`/`step_tread` for this model (1 ft = `per_unit` units by default 10, so about `step_rise 6`, `step_tread 10`).\n\n---\n\n## C24 \u2014 A staircase needs a slab under it, not just a plinth \xB7 **warning**\n\n**Statement.** When a staircase's floor has a slab (slab_thickness > 0), a slab should extend under the whole staircase footprint.\n\n**Rationale.** A staircase's base rests at the floor's walking surface \u2014 its `z_offset` defaults to the floor `slab_thickness`, so it sits on TOP of the slab. If the slab does not reach under the stair (a common miss on an external stair, where the plinth was extended but the slab was not), there is a `slab_thickness` gap between the bottom of the stairs and the plinth where the slab should be. Extending only the plinth (C21) is not enough. (Skipped when the floor has no slab \u2014 `slab_thickness 0` \u2014 since the stair then rests directly on the plinth.)\n\n**Fix.**\n\nAdd or grow a `slab` on the staircase's floor so it covers the whole staircase footprint, matching the plinth below it.\n\n---\n\n## SP1 \u2014 A spiral staircase's central pole must be smaller than its radius \xB7 **error**\n\n**Statement.** A `spiral_staircase`'s `pole_radius` must be less than its outer `radius`.\n\n**Rationale.** The treads run from the central pole out to the outer radius. If the pole is as wide as (or wider than) the stair, there is no tread left to stand on \u2014 the geometry collapses.\n\n**Fix.**\n\nReduce `pole_radius` below `radius` (a pole is typically a small fraction of the radius).\n\n---\n\n## Running the checks\n\n```bash\nwadi-skill/architect/scripts/check.sh house.wdl\n```\n\n- **`\u2716 [C\u2026]`** \u2014 a structural **error**; the check exits non-zero. Fix before you\n save/share.\n- **`\u26A0 [C\u2026]`** \u2014 a structural **warning**; advisory. Fix, or keep it if the open\n side is intentional.\n\nIn the DSL editor the same findings appear in the status pill (hover for the full\nlist); the model still renders so you can see the problem.\n\n---\n\n## Design guidelines (advisory \u2014 NOT linted)\n\nGood-practice guidance the linter does not check. Apply your judgement; these are\nstyle, not rules:\n\n- **Compact, rectangular layout.** Keep the plan as rectangular as possible, with\n the minimum of nooks and crannies. A blocky footprint is cheaper to build, easier\n to roof, and wastes less wall.\n- **Room sizes follow use.** Size each room to its utilisation. The living room is\n usually the largest, then rooms like the kitchen, workshop, and bedrooms; a\n bathroom should be smaller than the bedroom it serves.\n- **Verandahs & balconies get half-height walls.** Give a verandah or balcony a\n half-height (parapet) wall rather than a full-height one \u2014 a full-height verandah\n reads as an enclosed room. (Not linted: Wadi has no verandah/balcony marker, so\n there is nothing to key a warning on without matching room names.)\n- **Staircase in a multi-storey house.** Reserve a dedicated stair space on each\n floor. The staircase starts on the lowest floor and rises to the underside of the\n topmost occupied floor (not the roof floor). Size the stair space so climbing one\n storey takes at most two flights (use `max_run` on the `staircase` to switchback).\n"
|
|
434382
|
+
"body": "# Wadi structural conventions (coding guidelines)\n\n<!-- GENERATED FILE \u2014 do not edit conventions.md by hand. It is built from\n conventions.preamble.md + the constraint modules in editor/src/lint/constraints.\n Regenerate with `npm --prefix editor run gen-conventions-doc`. -->\n\nA house can be **well-formed but structurally unsound**: it passes the schema and\nthe wall/roof geometry check, yet the building would not stand up \u2014 a floor floats\nin mid-air, a room is open to the weather, walls hover above a phantom slab. These\nare the *coding conventions* every Wadi house must follow.\n\nThey are **formally defined here** and **enforced in code** by the structural\nlinter (`editor/src/lint/structural.ts`), which runs automatically:\n\n- in **`check.sh`** (and `validate.mjs`) \u2014 **errors fail** the check, **warnings**\n are printed but advisory;\n- in the **WDL editor** \u2014 the status pill shows the count and lists every finding\n in its hover tooltip, while still rendering the model so you can *see* the\n unsound part.\n\nEach finding carries its convention id (`C1`, `C2`, \u2026). Each convention is a\nself-contained module under `editor/src/lint/constraints/` (its check + this doc +\nits example fixtures), and **this file is generated from those modules**\n(`editor/scripts/gen-conventions-doc.mjs`) \u2014 so the doc and the linter cannot\ndrift. Add a rule by adding a constraint module and regenerating.\n\n---\n\n## The vertical model (why C1 and C3 exist)\n\nFloors stack in source order (floor 0 = the Plinth floor). The renderer places\nthem like this (`editor/src/three/coords.ts`):\n\n- **A floor's base elevation = the running sum of the previous floors' `height`\n only.** `wall_height` and `slab_thickness` do **not** raise the next floor.\n- The **plinth block** is drawn to its *own* `height`. So the floor above sits at\n `plinth-floor.height`, while the plinth top is at `plinth.height` \u2014 they must be\n equal or the floor above floats/sinks by the difference. \u2192 **C1**\n- **`slab_thickness` lifts a floor's walls within its band** (`wallZ = base +\n slab_thickness`) \u2014 it is the deck the walls stand on. With no slab object there\n is no deck, so the walls float by that amount. \u2192 **C3**\n\n`height`, `wall_height`, and `slab_thickness` are otherwise **independent** \u2014 the\nmodel enforces no relationship between them. These conventions add the few\nrelationships that structural soundness *does* require.\n\n---\n\n## C1 \u2014 The plinth floor's height must match the plinth block height \xB7 **error**\n\n**Statement.** A floor that carries a `plinth` object (the Plinth floor) must set an explicit `height`, and that height must equal the plinth block's `height`.\n\n**Rationale.** The floor above is stacked at `plinth-floor.height`; the plinth block rises to `plinth.height`. If they differ, the floor above floats above the plinth (`floor.height > plinth.height`) or sinks into it (`<`). If the floor `height` is omitted it silently defaults to `100`, almost never the plinth height.\n\n**Fix.**\n\n```wdl\nfloor 0 \"Plinth\" height 40 { // == the plinth block height below\n ground name \"Ground\" at (0,0) size (500,500)\n plinth name \"Plinth\" at (\u2026) size (\u2026) height 40\n}\n```\n\n(If the plinth block omits its own `height`, it follows the floor height and is consistent by construction \u2014 but set the floor `height` explicitly anyway, so the stack is not left to the default.)\n\n---\n\n## C2 \u2014 A room must wall every exterior side \xB7 **warning**\n\n**Statement.** A room shown with a **partial** `walls` list must still wall every side that faces **outside** (no room beyond it). Interior (shared) sides may be omitted \u2014 the neighbour's wall stands on the shared centreline.\n\n**Rationale.** A room shows exactly the walls it declares; a **bare room (no `wall` lines) is enclosed on all four sides**. But the moment you add a `wall` line to hang a door or window, the room switches to a *whitelist* \u2014 every side you don't list is now a hole. An exterior hole leaves the room open to the weather. It is a **warning**, not an error, because an open exterior side is sometimes intentional (a verandah / open padvi).\n\n**Fix.**\n\n```wdl\nroom Living at (x,y) size (w,l) {\n wall east west // plain exterior sides \u2014 enclosed\n wall south { door Main at 120 size (36,84) }\n wall north { window N1 at 100 size (60,50) sill 35 }\n}\n```\n\n---\n\n## C3 \u2014 A floor with no slab must set slab_thickness to 0 \xB7 **error**\n\n**Statement.** A floor that has wall/room objects but **no `floor_slab` object** must set `slab_thickness 0`.\n\n**Rationale.** `slab_thickness` is the deck the floor's walls stand on (`wallZ = base + slab_thickness`). Its default is `8`. With no slab object there is no deck, so every wall on the floor floats `slab_thickness` units above the floor base. Setting it to `0` puts the walls on the floor base; alternatively, model the deck by adding a `slab`.\n\n**Fix.**\n\n```wdl\nfloor 1 \"Ground\" slab_thickness 0 { // no slab modelled \u2192 walls sit on the base\n room Studio at (\u2026) size (\u2026) { \u2026 }\n}\n```\n\n*(This does not fire on a floor that carries no walls/rooms \u2014 e.g. a Plinth floor of just `ground` + `plinth`, or a roof-only top floor \u2014 where `slab_thickness` is harmless.)*\n\n---\n\n## C4 \u2014 A stacked floor's height should equal wall_height + slab_thickness \xB7 **warning**\n\n**Statement.** A floor that carries a floor above it (and has walls/rooms) should set `height` = `wall_height` + `slab_thickness`.\n\n**Rationale.** The next floor sits at `base + height`; this floor's walls stand on the deck and reach `base + slab_thickness + wall_height`. When `height` is larger, the floor above leaves a gap over the walls; when smaller, the walls poke through it. It is a **warning** \u2014 a deliberate gap is legitimate (a service plenum, a deep transfer beam) \u2014 but usually they should match.\n\n**Fix.**\n\n```wdl\ndefaults { floor_height 116 wall_height 108 slab_thickness 8 } // 108 + 8 = 116\n```\n\n*(Skipped for the plinth floor \u2014 governed by C1 \u2014 and for the topmost floor, since nothing stacks on its walls.)*\n\n---\n\n## C5 \u2014 A staircase must land on a floor, not below ground \xB7 **warning**\n\n**Statement.** A staircase's descent must not carry it below the ground plane (z < 0).\n\n**Rationale.** Only a `climb down` (top-anchored) stair can fall below ground: you place it on the **upper** floor and it **descends**. Put it on the wrong floor, or give it too large a `total_height`, and the expanded flight lands **below ground** \u2014 it still draws in the 2D plans (which ignore Z) but is **buried and invisible in 3D**, with no other error. A `climb up` stair is anchored on its own floor and ascends, so it never trips this.\n\n**Fix.**\n\nPrefer **`climb up`**: put the stair on the **lower** floor it rises FROM and let it ascend.\n\n```wdl\nfloor 1 \"Ground Floor\" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name \"Stair\" at (212, 64) step (7, 11, 44) // `at` = the BOTTOM (this floor)\n direction south climb up // ascends to the floor above\n}\n```\n\n(Or, if you must keep it `climb down`, move it **up one floor** or reduce `total_height`.)\n\n---\n\n## C6 \u2014 Openings on the same wall must not overlap \xB7 **error**\n\n**Statement.** Two openings (doors/windows) cut into the **same physical wall** must not overlap along it. This includes openings that belong to **two different rooms sharing a boundary wall**.\n\n**Rationale.** Each opening is a boolean-subtract from the wall. Overlapping spans merge into one ragged hole (or fight over the same brick), which is never what you meant \u2014 and on a shared wall it silently punches a bigger gap than either room's plan shows.\n\n**Fix.**\n\nOffset or narrow one opening so the spans are disjoint. An opening's span is its resolved `[offset, offset+width]` along the wall \u2014 the `from start|center|end` anchor is honoured (its offset is converted to a start-based position first, exactly as the renderer does).\n\n---\n\n## C7 \u2014 Furniture items should not overlap \xB7 **warning**\n\n**Statement.** Two furniture `item`s whose plan footprints overlap are flagged \u2014 as a **warning**, because it is sometimes intentional (a rug under a table, a lamp on a desk, deliberately stacked pieces).\n\n**Rationale.** More often it's a placement slip \u2014 two beds dropped on the same spot, or an anchored piece that reflowed into another when a room was resized. The footprint used is the item's rotated bounding box (yaw-aware), so it matches what the plan draws.\n\n**Fix.**\n\nReposition one item, or ignore the warning if the overlap is deliberate.\n\n---\n\n## C8 \u2014 Two abutting rooms need a partition between them \xB7 **warning**\n\n**Statement.** Where two rooms share a boundary line and **neither** declares a wall on it, there is no partition between them.\n\n**Rationale.** A bare room (no `wall` lines) is enclosed on all four sides, so two bare neighbours have two walls on their shared line. But once **both** rooms switch to partial `walls` lists and both omit the shared side, the centreline is left open \u2014 the rooms merge into one space with no divider. C2 only guards *exterior* sides; this is its interior counterpart. It is a **warning** because an intentional open-plan link (kitchen into living) is legitimate.\n\n**Fix.**\n\nDeclare the wall on **one** of the two rooms (the neighbour's wall stands on the shared centreline, so one is enough):\n\n```wdl\nroom Kitchen at (\u2026) size (\u2026) { wall north south east } // east = the shared line\nroom Living at (\u2026) size (\u2026) { wall north south west }\n```\n\n---\n\n## C9 \u2014 A floor's slab_thickness should match its slab object's thickness \xB7 **warning**\n\n**Statement.** When a floor carries a `floor_slab` object with an explicit `thickness`, that thickness should equal the floor's `slab_thickness`.\n\n**Rationale.** The floor's `slab_thickness` is the deck the walls stand on (`wallZ = base + slab_thickness`); the slab object's own `thickness` is how thick the slab MESH is drawn. If they differ, the walls sit at the floor's `slab_thickness` while the slab top is at the object's `thickness`, so the walls float above or sink into the drawn deck. (A slab with no explicit `thickness` follows the floor's `slab_thickness` and is consistent by construction \u2014 this only fires when both are set and disagree.)\n\n**Fix.**\n\nMake them equal \u2014 most simply, drop the slab's explicit `thickness` so it follows the floor:\n\n```wdl\nfloor 1 \"Ground\" slab_thickness 8 {\n slab name \"Deck\" at (\u2026) size (\u2026) // no thickness \u2192 uses 8\n}\n```\n\n---\n\n## C10 \u2014 The roof should cover the rooms of the top occupied floor \xB7 **warning**\n\n**Statement.** Every room on the top occupied floor should sit under a roof segment \u2014 no room left entirely uncovered.\n\n**Rationale.** The roof's segments span a plan area (each segment's ridge line \xB1 its `width`). A room on the top floor whose footprint does not overlap **any** roof segment has open sky above it \u2014 usually a roof that was sized to the wrong footprint, or a room added after the roof. (Only a *completely* uncovered room is flagged, so eave overhangs and partial coverage never false-warn; a house with no roof at all \u2014 a terrace \u2014 is not flagged.)\n\n**Fix.**\n\nExtend or add a roof segment to span the room, or reduce the room. Roof segments cover `start \u2192 end` along the ridge, `width` across it, so grow `width`/`end` (or the plot variables they derive from) until the room is under it.\n\n---\n\n## C11 \u2014 A declared connection must overlap on a wall and be passable (door or open) \xB7 **error**\n\n**Statement.** For every `connect`ion a room declares, the two rooms must **overlap on a wall** (not necessarily the whole wall), and that overlap must be **passable**: either a **door** lies in it, or the wall is **left off both rooms** (an open passage).\n\n**Rationale.** A connection is a FUNCTIONAL requirement \u2014 `Living` opens into `Kitchen`. It is design intent, not geometry (the renderer never draws it), so this constraint is what verifies the intent is physically realized. It fails two ways: the rooms' walls don't overlap at all, or they overlap but a solid wall (present on either room, no door in the overlap) blocks the way. No door is ever generated \u2014 a room authors its own openings, or omits the shared wall to leave the rooms open to each other.\n\n**Fix.**\n\nOverlap the two rooms on a wall, then EITHER put a door in the overlap (on either room), OR omit that wall on both:\n\n```wdl\n// door in the shared wall\nroom Living at (\u2026) size (\u2026) { connect Kitchen wall east { door D at 80 size (40,210) } }\nroom Kitchen at (\u2026) size (\u2026)\n\n// open passage \u2014 neither room walls the shared side\nroom Living at (\u2026) size (\u2026) { connect Kitchen wall north south west }\nroom Kitchen at (\u2026) size (\u2026) { wall north south east }\n```\n\n---\n\n## C12 \u2014 Rooms should not overlap (they share walls, not floor area) \xB7 **warning**\n\n**Statement.** Two `room`s on the same floor should not overlap in plan \u2014 flagged as a **warning**, because it is occasionally intentional (embedding a corner room to carve an L-shaped space). Adjacent rooms may **touch** on a shared wall (their edges coincide); a larger intersection is reported.\n\n**Rationale.** Rooms usually share walls, not floor area. A real overlap means two rooms were placed on the same spot \u2014 the renderer draws one over the other. It most often happens when a room is placed by absolute coordinates, or when a band (a verandah, a corridor) is dropped across an existing wing. C11 checks that declared connections are realized; this checks that the geometry is physically consistent. It stays a warning because an L-shaped room is modelled by overlapping a small corner room onto a larger bounding one.\n\n**Fix.**\n\nIf the overlap is unintended, move or resize one room so they only touch on a shared wall (prefer relative placement \u2014 abut a neighbour on a side \u2014 over absolute coordinates that can land on top of another room). Ignore the warning if it is a deliberate corner embed.\n\n---\n\n## C13 \u2014 The lowest floor should carry a plinth \xB7 **warning**\n\n**Statement.** A house should rest on a plinth: the lowest floor should contain a `plinth` object.\n\n**Rationale.** The plinth is the raised base the building sits on \u2014 it lifts the ground floor above grade and gives the walls a footing. A lowest floor with rooms but no plinth reads as a slab-on-grade shortcut; most Konkan houses want an explicit plinth. (A style guide, so it only warns \u2014 a deliberately plinth-less design is allowed.)\n\n**Fix.**\n\nAdd a `plinth` to the lowest floor (usually the Plinth floor 0, alongside the `ground`), sized to cover the built footprint.\n\n---\n\n## C14 \u2014 The highest floor should carry a roof \xB7 **warning**\n\n**Statement.** A house should be capped by a roof: the highest floor should contain a `roof` object.\n\n**Rationale.** The roof sits on its own floor stacked above the walls (see the roof convention). A design whose top floor has no roof leaves the house open \u2014 usually a roof that was forgotten, or a floor added above the roof. (A style guide, so it only warns \u2014 a deliberate flat terrace with no roof is allowed.)\n\n**Fix.**\n\nAdd a `roof` to the highest floor (a floor stacked above the top occupied floor), with segments spanning the footprint.\n\n---\n\n## C15 \u2014 The plinth should cover the rooms that rest on it \xB7 **warning**\n\n**Statement.** The plinth footprint should contain every room on the lowest occupied floor \u2014 no ground-floor room sticking out past the plinth.\n\n**Rationale.** The plinth is the base the ground floor stands on. A room whose footprint extends beyond the plinth has part of its floor unsupported by the base. (Upper floors that cantilever past the plinth are a different case \u2014 those want pillars; see the cantilever guidance. This checks only the floor that sits directly on the plinth.)\n\n**Fix.**\n\nGrow the plinth (its size, or the plot variables it derives from) to cover the room, or pull the room back within the plinth.\n\n---\n\n## C16 \u2014 A room overhanging the floor below should have pillars under it \xB7 **warning**\n\n**Statement.** If a room on floor N extends beyond the rooms of the floor below (a cantilever), there should be pillars supporting the overhang.\n\n**Rationale.** An upper-floor room that sticks out past the walls below has nothing under its overhang. In a real build that extension needs columns at its outside edge. This warns when an overhanging room has no pillar anywhere near it. (A style guide \u2014 a genuinely cantilevered slab design is allowed; the warning just flags the missing support.)\n\n**Fix.**\n\nAdd `pillar` objects at the outside of the extension (under the overhanging edge), or pull the room back over the floor below.\n\n---\n\n## C17 \u2014 A hip roof segment's span should not exceed its ridge run \xB7 **warning**\n\n**Statement.** On a pitched roof with closed (hip) ends, a segment's span (`width`) should not exceed its ridge run (the `start`\u2192`end` length).\n\n**Rationale.** A closed hip end pulls the ridge inward by half the span to make room for the hip face. When the span exceeds the run, the two hip ends would meet past the centre, so the roof is clipped to a pyramid (the hip faces meet at a single apex) rather than the intended ridged hip. That is almost always a mis-oriented segment \u2014 the ridge drawn along the SHORTER dimension. It still renders as a valid pyramid, so this only warns.\n\n**Fix.**\n\nOrient the ridge along the LONGER dimension: swap the segment's `start`/`end` so it runs the long way, and set `width` to the shorter span. If a pyramid is genuinely intended, ignore this.\n\n---\n\n## C18 \u2014 Don't roof the same area twice \xB7 **warning**\n\n**Statement.** A roof segment should not sit entirely within an area another roof segment already covers.\n\n**Rationale.** Each roof segment spans a plan area (its ridge line \xB1 `width`). When a new segment (often a whole new `roof` object an agent added) falls completely inside the area an existing roof already covers, the two roofs overlap \u2014 redundant geometry that renders as z-fighting and doubles the material take-off. Almost always the fix is to extend the existing roof, not add another. (Only a segment FULLY inside prior coverage is flagged, so abutting segments and ridge joints in a legitimate multi-segment roof never false-warn.)\n\n**Fix.**\n\nRemove the redundant roof/segment, or if you meant to cover more area, extend an existing segment's `width`/`end` instead of adding an overlapping one.\n\n---\n\n## C19 \u2014 Prefer one or two flights per floor when space allows \xB7 **warning**\n\n**Statement.** A staircase should climb a floor in one or two flights unless the floor space is genuinely tight.\n\n**Rationale.** A box-model staircase derives its flight count from the run it is given: too short a box forces extra switchback flights. Agents routinely under-size the box and get cramped 3-4 flight stairs where the floor had room for a straight run or a single U-turn. Fewer flights are easier to build and to walk. (A warning, since a tight plot may legitimately need a compact switchback.)\n\n**Fix.**\n\nLengthen the staircase along its run axis (the box `length` for a N/S stair, `width` for E/W) to the reported minimum, or reduce `landing_depth`. The warning gives the exact length for one and two flights.\n\n---\n\n## C20 \u2014 A staircase's top landing must reach a room \xB7 **warning**\n\n**Statement.** The top landing of a staircase should abut (or sit inside) a room on the floor it arrives at, so there is a way off the stair onto the floor.\n\n**Rationale.** A switchback's arrival landing lands wherever the run ends, which is hard to predict and easy to get wrong (often the direction is simply flipped). When the top landing ends against a blank wall or in open space, the stair reaches the next level but there is no way onto the floor. This checks the resolved arrival rectangle against the rooms of the arrival floor. (A warning, since a landing that opens onto an outdoor terrace may not overlap a room.)\n\n**Fix.**\n\nPlace the staircase so its top landing meets a room (leave that room's wall open there or add a door), or flip the `direction`/`turn` so the landing ends on the room side. The warning reports the arrival rectangle and which way it faces.\n\n---\n\n## C21 \u2014 The plinth should extend under a staircase \xB7 **warning**\n\n**Statement.** A staircase on the lowest occupied floor should sit entirely on the plinth \u2014 the plinth footprint should cover it.\n\n**Rationale.** The plinth is the base the ground floor stands on. An external staircase added past the building edge has its flights and landings resting on nothing unless the plinth is extended under it. Agents routinely add a stair to the outside and forget to grow the plinth. (Checks only the lowest occupied floor, the one that sits on the plinth; upper-floor stairs bear on that floor's slab.)\n\n**Fix.**\n\nGrow the plinth (its size, or the plot variables it derives from) so it covers the whole staircase footprint, or move the staircase inside the building over the plinth.\n\n---\n\n## C22 \u2014 A staircase needs an enclosing room or pillars to carry its landings \xB7 **warning**\n\n**Statement.** A staircase should be enclosed by a room of (at least) its own footprint, or have pillars under it \u2014 something to carry the flights and turn landings.\n\n**Rationale.** Switchback landings are elevated slabs; the flights land on them. In a real build the surrounding walls (a stairwell) or columns carry that load. A free-standing staircase with no enclosing room and no pillars has landings hanging in the air. (A warning: an open stair against a structural wall may be fine, but the common agent mistake is a stair floating in open space.)\n\n**Fix.**\n\nPut the staircase inside a room that covers its footprint (the walls carry the landings), or add `pillar` objects under the landings.\n\n---\n\n## C23 \u2014 A staircase's steps should be a realistic size \xB7 **warning**\n\n**Statement.** A staircase's `step_rise` and `step_tread`, converted to real-world size, should fall in the human range \u2014 about a 6-7 in rise and a 10-12 in going.\n\n**Rationale.** Stair steps are physically fixed regardless of the house, so they are the reliable tell for a scale mistake. Agents often copy a staircase from another example that is in different units (metres vs feet), producing steps that are absurdly large or tiny. This converts the steps to inches using the model's `units` and flags anything outside a generous human range.\n\n**Fix.**\n\nRescale the staircase to THIS model's units. The warning gives the sensible `step_rise`/`step_tread` for this model (1 ft = `per_unit` units by default 10, so about `step_rise 6`, `step_tread 10`).\n\n---\n\n## C24 \u2014 A staircase needs a slab under it, not just a plinth \xB7 **warning**\n\n**Statement.** When a staircase's floor has a slab (slab_thickness > 0), a slab should extend under the whole staircase footprint.\n\n**Rationale.** A staircase's base rests at the floor's walking surface \u2014 its `z_offset` defaults to the floor `slab_thickness`, so it sits on TOP of the slab. If the slab does not reach under the stair (a common miss on an external stair, where the plinth was extended but the slab was not), there is a `slab_thickness` gap between the bottom of the stairs and the plinth where the slab should be. Extending only the plinth (C21) is not enough. (Skipped when the floor has no slab \u2014 `slab_thickness 0` \u2014 since the stair then rests directly on the plinth.)\n\n**Fix.**\n\nAdd or grow a `slab` on the staircase's floor so it covers the whole staircase footprint, matching the plinth below it.\n\n---\n\n## C25 \u2014 A pillar under a slab or landing should reach it \xB7 **warning**\n\n**Statement.** When a floor slab or a staircase landing sits directly over a pillar, the pillar should rise to the underside of it. A pillar that stops short leaves a gap and carries nothing.\n\n**Rationale.** A column exists to carry the slab or landing above it; if it stops below that level there is a gap and the load has nothing to bear on. Agents often shrink a copied pillar's height (or a configurator lowers it) so it no longer reaches. This checks the LOWEST floor slab / staircase landing that actually sits over the pillar \u2014 so a pillar added to carry a stair's turn landings is checked against those landings, and a pillar supporting only a roof (which slopes, no flat datum) or with nothing above is never flagged. (A warning \u2014 a deliberately low post under a slab is allowed.)\n\n**Fix.**\n\nRaise the pillar height so its top meets the slab/landing above it. The warning reports the gap and a suggested height.\n\n---\n\n## SP1 \u2014 A spiral staircase's central pole must be smaller than its radius \xB7 **error**\n\n**Statement.** A `spiral_staircase`'s `pole_radius` must be less than its outer `radius`.\n\n**Rationale.** The treads run from the central pole out to the outer radius. If the pole is as wide as (or wider than) the stair, there is no tread left to stand on \u2014 the geometry collapses.\n\n**Fix.**\n\nReduce `pole_radius` below `radius` (a pole is typically a small fraction of the radius).\n\n---\n\n## Running the checks\n\n```bash\nwadi-skill/architect/scripts/check.sh house.wdl\n```\n\n- **`\u2716 [C\u2026]`** \u2014 a structural **error**; the check exits non-zero. Fix before you\n save/share.\n- **`\u26A0 [C\u2026]`** \u2014 a structural **warning**; advisory. Fix, or keep it if the open\n side is intentional.\n\nIn the WDL editor the same findings appear in the status pill (hover for the full\nlist); the model still renders so you can see the problem.\n\n---\n\n## Design guidelines (advisory \u2014 NOT linted)\n\nGood-practice guidance the linter does not check. Apply your judgement; these are\nstyle, not rules:\n\n- **Compact, rectangular layout.** Keep the plan as rectangular as possible, with\n the minimum of nooks and crannies. A blocky footprint is cheaper to build, easier\n to roof, and wastes less wall.\n- **Room sizes follow use.** Size each room to its utilisation. The living room is\n usually the largest, then rooms like the kitchen, workshop, and bedrooms; a\n bathroom should be smaller than the bedroom it serves.\n- **Verandahs & balconies get half-height walls.** Give a verandah or balcony a\n half-height (parapet) wall rather than a full-height one \u2014 a full-height verandah\n reads as an enclosed room. (Not linted: Wadi has no verandah/balcony marker, so\n there is nothing to key a warning on without matching room names.)\n- **Staircase in a multi-storey house.** Reserve a dedicated stair space on each\n floor. The staircase starts on the lowest floor and rises to the underside of the\n topmost occupied floor (not the roof floor). Size the stair space so climbing one\n storey takes at most two flights (use `max_run` on the `staircase` to switchback).\n"
|
|
434291
434383
|
},
|
|
434292
434384
|
"coordinate-system": {
|
|
434293
434385
|
"title": "Coordinates, units & the centreline convention",
|
|
434294
|
-
"body": '# Coordinate system & units\n\nThis is the #1 source of mistakes. Read it before placing anything.\n\n## Axes \u2014 Inkscape frame (Y is DOWN)\n\nThe config uses an **Inkscape-style 2D frame**, the same one the SVG floor plans\nuse directly:\n\n```\n origin (0,0) \u2500\u2500 X increases \u2192 (east / right)\n \u2502\n \u2502 Y increases \u2193 (south / DOWN)\n \u25BC\n```\n\n- **X** \u2192 right (east).\n- **Y** \u2192 **DOWN** (south). This is NOT the math/Blender convention. A room "to\n the **north**" of another has a **smaller** Y. Moving something "up" on the plan\n = **decreasing** Y.\n- **Z** \u2192 up (height), in the 3D model. Z isn\'t in the 2D placement fields; it\'s\n derived from `height`/floor stacking. `sill_height`, `z_offset`, `base_z`,\n `plinth.height`, wall `height` are vertical (Z) measures.\n\nEvery position field is the object\'s **top-left corner** in this frame:\n`x`/`y` (rooms, slabs, beams, pillars), `start_x`/`start_y` + `end_x`/`end_y`\n(walls, staircases), `path: [[x,y],\u2026]` (kitchen platforms), roof segment\n`start`/`end` (`[x, y]`).\n\nBecause Y points down:\n- `plinth.length` runs along **X**; `plinth.width` runs along **Y**.\n- A room\'s `width` runs along **X**; its `length` runs along **Y**.\n- A north-up architect\'s sketch has its **Y flipped** when you transcribe it: the\n top of the drawing (north) maps to small Y, the bottom (south) to large Y.\n\n## Units \u2014 10 project units = 1 foot\n\nAll lengths/coordinates are **project units**. The display/dimension convention is\n**`unit_conversion = 10` \u2192 10 units = 1 ft**. So:\n\n| The user says | Config value |\n|---|---|\n| 1 ft | 10 |\n| 12 ft (a 12-foot room) | 120 |\n| 45 ft (plot) | 450 |\n| 6 in (0.5 ft) | 5 |\n\nAlways **multiply feet by 10**. `plot_length: 450` displays as `45\'`.\n\n
|
|
434386
|
+
"body": '# Coordinate system & units\n\nThis is the #1 source of mistakes. Read it before placing anything.\n\n## Axes \u2014 Inkscape frame (Y is DOWN)\n\nThe config uses an **Inkscape-style 2D frame**, the same one the SVG floor plans\nuse directly:\n\n```\n origin (0,0) \u2500\u2500 X increases \u2192 (east / right)\n \u2502\n \u2502 Y increases \u2193 (south / DOWN)\n \u25BC\n```\n\n- **X** \u2192 right (east).\n- **Y** \u2192 **DOWN** (south). This is NOT the math/Blender convention. A room "to\n the **north**" of another has a **smaller** Y. Moving something "up" on the plan\n = **decreasing** Y.\n- **Z** \u2192 up (height), in the 3D model. Z isn\'t in the 2D placement fields; it\'s\n derived from `height`/floor stacking. `sill_height`, `z_offset`, `base_z`,\n `plinth.height`, wall `height` are vertical (Z) measures.\n\nEvery position field is the object\'s **top-left corner** in this frame:\n`x`/`y` (rooms, slabs, beams, pillars), `start_x`/`start_y` + `end_x`/`end_y`\n(walls, staircases), `path: [[x,y],\u2026]` (kitchen platforms), roof segment\n`start`/`end` (`[x, y]`).\n\nBecause Y points down:\n- `plinth.length` runs along **X**; `plinth.width` runs along **Y**.\n- A room\'s `width` runs along **X**; its `length` runs along **Y**.\n- A north-up architect\'s sketch has its **Y flipped** when you transcribe it: the\n top of the drawing (north) maps to small Y, the bottom (south) to large Y.\n\n## Units \u2014 10 project units = 1 foot\n\nAll lengths/coordinates are **project units**. The display/dimension convention is\n**`unit_conversion = 10` \u2192 10 units = 1 ft**. So:\n\n| The user says | Config value |\n|---|---|\n| 1 ft | 10 |\n| 12 ft (a 12-foot room) | 120 |\n| 45 ft (plot) | 450 |\n| 6 in (0.5 ft) | 5 |\n\nAlways **multiply feet by 10**. `plot_length: 450` displays as `45\'`.\n\n### Changing how dimensions are *labelled*\n\nThe `10-units-per-foot` rule above is only the **default label**. The optional\ntop-level `units` block changes the drawing labels **without touching geometry** \u2014\ncoordinates you write stay in project units either way:\n\n```jsonc\n"units": { "system": "meters", "per_unit": 100, "precision": 2 }\n```\n\n- `system` \u2208 `feet_inches` (default), `feet`, `meters`, `centimeters`, `millimeters`.\n- `per_unit` = project units per one display unit (the label divisor). Default `10`.\n- `precision` = decimals for the decimal systems (default `2`; `feet_inches` ignores it).\n\nSo a 120-unit wall labels as `12\'` by default, or `1.20 m` with the block above.\nNever change coordinates to switch units \u2014 only add/edit this block.\n\n### Label font sizes auto-scale (no config needed)\n\nDimension and room-label font sizes are **derived automatically** from the house\'s\nphysical span (larger of `plinth.length`/`plinth.width`), so text stays legible at\nfit-to-view whether the house is tiny or huge. There is **no font-size field** to\nset \u2014 don\'t try to add one. A house at the reference span (450 units \u2248 45 ft)\nrenders at the baseline sizes; larger/smaller houses scale proportionally (clamped\n0.6\xD7\u20136\xD7).\n\n## Rooms, walls, and the centreline convention\n\nSet **`"coord_convention": "center"`** at the top of the house (the canonical mode).\nThen a rect object\'s `x, y, width, length` are **wall CENTRELINES** \u2014 the line each\nwall is centred on, *not* the outer face.\n\n**Two rooms that share a wall simply ABUT on the shared centreline** \u2014 no overlap, no\nwall math:\n\n- Room A spans X `[0, 150]`, room B spans X `[150, 300]`. They meet at `150`; the wall\n is centred on `150` and belongs to both \u2192 **one shared wall.** Done.\n- The clear interior of a room is `width \u2212 t` \xD7 `length \u2212 t` (half a wall on each side);\n its outer extent is `width + t` \xD7 `length + t`. The system grows each footprint to the\n outer face automatically at render time (`expandRoomWalls`), so you never write `t`.\n\nThat\'s the whole rule. No "overlap by `wall_thickness`," no directional offsets.\n\n### Placing a plan on a grid \u2014 rooms reference the grid directly\n\nBecause room coords *are* wall centrelines and a **grid** (`grids` block) is a set of\nnamed wall centrelines, a room is just the rectangle between four grid lines \u2014 with\n**no arithmetic**:\n\n```jsonc\n"grids": { "main": { "x": [ {"name":"1","at":"= wallT/2"}, {"name":"2","at":"= House.W - wallT/2"} ],\n "y": [ {"name":"A","at":0}, {"name":"B","at":"= House.L - wallT/2"} ] } }\n```\n```jsonc\n{ "type":"room", "name":"Hall",\n "formulas": { "x":"= main.x1", "y":"= main.yA",\n "width":"= main.x2 - main.x1", "length":"= main.yB - main.yA" } }\n```\n\nEach grid line is published as a formula **symbol** `<gridId>.x<name>` / `<gridId>.y<name>`\n(e.g. `main.x1`, `main.yA`), so rooms, slabs and the plinth all place themselves off the\nsame lines. Move a grid line (or the `House` knobs its `at` depends on) and every room on\nit follows. The **grid is the single parametric layer** \u2014 `House` + knobs \u2192 grid lines \u2192\nrooms; nothing flows the other way. See `parametric-conventions.md`.\n\n(Legacy files without `coord_convention` are read as `"outer"`: coords are the OUTER\nface and adjacent rooms must **overlap** by `wall_thickness`. New work uses `"center"`.)\n\n## Vertical (Z) fields recap\n\n- `plinth.height` \u2014 how high the base sits above ground.\n- floor `height` \u2014 floor-to-floor rise; floors stack on top of each other by these.\n- floor `wall_height` \u2014 standing wall height on that floor.\n- `slab_thickness` \u2014 RCC deck between a floor and the one above.\n- opening `sill_height` \u2014 window sill height above its floor.\n- `beam.z_offset`, `kitchen_platform.base_z` \u2014 vertical offsets.\n\n## Sloping tops\n\nA wall (or room wall side) with a sloping top sets both `height` (at the start)\nand `height_end` (at the end). Full gable/hip geometry, though, comes from the\n`roof` object, not from wall slopes \u2014 see `roof-v2-guide.md`.\n\n## Quick self-check before saving\n\n- Did I treat "north/up" as **smaller** Y?\n- Are all my numbers in **project units** (feet \xD7 10)?\n- Is `"coord_convention": "center"` set, so room coords are wall centrelines and adjacent\n rooms **abut** on the shared line (no overlap, no `t` math)?\n- Does the plinth rectangle (`length`\xD7`width`) contain all my rooms?\n- Does the roof footprint cover the plinth?\n'
|
|
434295
434387
|
},
|
|
434296
434388
|
"parametric-conventions": {
|
|
434297
434389
|
"title": "Building a fully-parametric template",
|
|
@@ -434299,7 +434391,7 @@ var DOCS = {
|
|
|
434299
434391
|
},
|
|
434300
434392
|
"roof-v2-guide": {
|
|
434301
434393
|
"title": "The roof object (hip/gable/shed/flat)",
|
|
434302
|
-
"body": '# Roof v2 guide (the unified `roof` object)\n\nThe `roof` object replaces the legacy `hip_roof`/`gable_roof`/`flat_roof`/\n`shed_roof`. It is **segment-based**. The schema is permissive; the real\nvalidation happens in the compute pipeline, so **always run `validate.mjs`**\nafter writing a roof (it runs the exact derivation the app uses).\n\n> **Strategy:** roof geometry is the subtlest part of the config. Don\'t\n> hand-invent it \u2014 **copy the roof from the closest example** (see the shape map\n> below), then adjust its segment coordinates/widths to your plinth, and validate.\n\n## Where the roof lives, and its height (you do NOT set a Z)\n\nRead this first \u2014 it\'s the part that isn\'t obvious and that you must not try to\nreverse-engineer from the geometry.\n\n- **A roof object goes on its OWN top floor** \u2014 a floor whose `floor_number` is\n ABOVE every floor it covers, containing only the roof object(s). Name it\n something like `"Roof"` or `"Loft Floor"`. Example: ground = 0, first = 1 \u2192 the\n roof floor is `floor_number` **2**. The roof then covers the floors below it.\n (All the examples do this: the roof sits alone on the topmost floor.)\n- **You NEVER write a Z or height on the roof.** There is no Z field anywhere in\n the roof object. Its base sits automatically at the top of the walls below it,\n computed by the pipeline as:\n\n ```\n roof base Z = plinth.height + \u03A3 (height of every floor BELOW the roof\'s floor)\n ```\n\n using each floor\'s own `height` (falling back to `defaults.floor_height`).\n **`slab_thickness` does NOT enter this stack** \u2014 `height` already is the full\n floor-to-floor rise.\n- **To raise or lower the roof, change the FLOORS\' `height` (or `plinth.height`),\n not the roof.** If the roof floats too low/high, you put it on the wrong floor\n or a floor below it has the wrong `height`.\n- `slope` / `ridge_h` is the rise **above** that computed wall-top; segment\n `start`/`end` are 2D `[x, y]` only. Don\'t compute a Z \u2014 the pipeline does it\n entirely from the floor stack.\n\n## The core idea\n\nA roof is a set of **segments**. Each segment is a line (`start`\u2192`end`) with a\n**`width`** measured *perpendicular* to it, and **the segment sits at the CENTRE\nof that width** (the roof extends `width/2` to each side of the line). What the\nline means depends on `roof_type`:\n\n- **pitched** \u2014 the segment **is the ridge**; two symmetric slopes rise from the\n eaves up to it. Ends are covered per `default_endpoint`:\n - `"closed"` \u2192 **hip** (a sloped triangular hip face at the end).\n - `"open"` \u2192 **gable** (a vertical gable-end wall triangle).\n- **shed** \u2014 the segment is the **high edge**; the roof slopes down perpendicular\n to the far side. `shed_high_side` names which side is high.\n- **flat** \u2014 the segment\'s width band is a flat slab extruded down.\n\nSegments can run in **any direction** (not just axis-aligned).\n\n**Centreline convention (`coord_convention: "center"`).** A segment\'s\n`start`/`end`/`width` are **wall centrelines**, exactly like a room\'s\n`x/y/width/length`. On expand, each segment grows to the **outer wall face** \u2014\nthe axis is extended by \xBD\xB7`wall_thickness` at each end and `width` grows by\n`wall_thickness` \u2014 then `overhang` extends beyond that. So author segments on the\n**same centreline grid as the walls below** (e.g. `width House.W`, axis endpoints\non grid lines); do **not** hand-add \xBD-wall offsets, or the roof lands half a wall\nthickness inside the walls. (In the default `"outer"` convention the coordinates\nare literal outer-face positions and no grow happens.)\n\n## Object shape\n\n```jsonc\n{\n "type": "roof",\n "roof_type": "pitched" | "shed" | "flat",\n "default_endpoint": "closed" | "open", // pitched only: closed=hip, open=gable\n "min_overhang": 25, // > 0 \u2014 eave overhang beyond the walls\n "slope": { "by": "height", "ridge_h": 50 }, // rise above wall top \u2026\n // or { "by": "angle", "angle_deg": 30 }, // \u2026 or a pitch angle\n "segments": [\n {\n "id": "seg0",\n "start": [x, y], "end": [x, y], // the ridge (pitched) / high edge (shed)\n "width": 200, // perpendicular span, segment centred in it\n "slope": { \u2026 }, // optional per-segment slope override\n "shed_high_side": "\u2026", // shed only\n "hip_setback_start": \u2026, "hip_setback_end": \u2026, // optional, tune closed hip faces\n // Per-side overhang overrides (each defaults to min_overhang). Cantilever one\n // edge \u2014 e.g. big overhang_end over an entry landing \u2014 while others stay tight.\n "overhang_start": \u2026, "overhang_end": \u2026, // along the ridge axis (shed; on a gable\n // open end = gable_overhang_*)\n "overhang_low": \u2026, "overhang_high": \u2026, // SHED eaves (down-slope / up-slope)\n "overhang_left": \u2026, "overhang_right": \u2026 // PITCHED eaves (left / right of ridge;\n // single-segment only \u2014 see below)\n }\n ],\n "trusses": [\n { "segment_id": "seg0", "type": "fink", "positions_along": [100, 210, 320] }\n // type: "fink" (pitched) | "mono_pitch" (shed)\n // positions_along: distances along the segment where a truss sits\n ],\n "framing": { \u2026 } // optional overrides \u2014 omit unless asked\n}\n```\n\n`slope` may be given once at the roof level (applies to all segments) or per\nsegment. `by: "height"` \u2192 `ridge_h` is the ridge rise above the wall top; `by:\n"angle"` \u2192 `angle_deg` is the pitch.\n\n## Joints (multi-segment shapes)\n\nWhen two segments share an **exactly coincident endpoint**, the pipeline\nauto-resolves that end as a **joint** (no hip/gable face there) \u2014 this is how L,\nU, and closed-loop (courtyard) roofs are formed. Get the endpoints numerically\nequal or they won\'t join.\n\n## Shape \u2192
|
|
434394
|
+
"body": '# Roof v2 guide (the unified `roof` object)\n\nThe `roof` object replaces the legacy `hip_roof`/`gable_roof`/`flat_roof`/\n`shed_roof`. It is **segment-based**. The schema is permissive; the real\nvalidation happens in the compute pipeline, so **always run `validate.mjs`**\nafter writing a roof (it runs the exact derivation the app uses).\n\n> **Strategy:** roof geometry is the subtlest part of the config. Don\'t\n> hand-invent it \u2014 **copy the roof from the closest example** (see the shape map\n> below), then adjust its segment coordinates/widths to your plinth, and validate.\n\n## Where the roof lives, and its height (you do NOT set a Z)\n\nRead this first \u2014 it\'s the part that isn\'t obvious and that you must not try to\nreverse-engineer from the geometry.\n\n- **A roof object goes on its OWN top floor** \u2014 a floor whose `floor_number` is\n ABOVE every floor it covers, containing only the roof object(s). Name it\n something like `"Roof"` or `"Loft Floor"`. Example: ground = 0, first = 1 \u2192 the\n roof floor is `floor_number` **2**. The roof then covers the floors below it.\n (All the examples do this: the roof sits alone on the topmost floor.)\n- **You NEVER write a Z or height on the roof.** There is no Z field anywhere in\n the roof object. Its base sits automatically at the top of the walls below it,\n computed by the pipeline as:\n\n ```\n roof base Z = plinth.height + \u03A3 (height of every floor BELOW the roof\'s floor)\n ```\n\n using each floor\'s own `height` (falling back to `defaults.floor_height`).\n **`slab_thickness` does NOT enter this stack** \u2014 `height` already is the full\n floor-to-floor rise.\n- **To raise or lower the roof, change the FLOORS\' `height` (or `plinth.height`),\n not the roof.** If the roof floats too low/high, you put it on the wrong floor\n or a floor below it has the wrong `height`.\n- `slope` / `ridge_h` is the rise **above** that computed wall-top; segment\n `start`/`end` are 2D `[x, y]` only. Don\'t compute a Z \u2014 the pipeline does it\n entirely from the floor stack.\n\n## The core idea\n\nA roof is a set of **segments**. Each segment is a line (`start`\u2192`end`) with a\n**`width`** measured *perpendicular* to it, and **the segment sits at the CENTRE\nof that width** (the roof extends `width/2` to each side of the line). What the\nline means depends on `roof_type`:\n\n- **pitched** \u2014 the segment **is the ridge**; two symmetric slopes rise from the\n eaves up to it. Ends are covered per `default_endpoint`:\n - `"closed"` \u2192 **hip** (a sloped triangular hip face at the end).\n - `"open"` \u2192 **gable** (a vertical gable-end wall triangle).\n- **shed** \u2014 the segment is the **high edge**; the roof slopes down perpendicular\n to the far side. `shed_high_side` names which side is high.\n- **flat** \u2014 the segment\'s width band is a flat slab extruded down.\n\nSegments can run in **any direction** (not just axis-aligned).\n\n**Centreline convention (`coord_convention: "center"`).** A segment\'s\n`start`/`end`/`width` are **wall centrelines**, exactly like a room\'s\n`x/y/width/length`. On expand, each segment grows to the **outer wall face** \u2014\nthe axis is extended by \xBD\xB7`wall_thickness` at each end and `width` grows by\n`wall_thickness` \u2014 then `overhang` extends beyond that. So author segments on the\n**same centreline grid as the walls below** (e.g. `width House.W`, axis endpoints\non grid lines); do **not** hand-add \xBD-wall offsets, or the roof lands half a wall\nthickness inside the walls. (In the default `"outer"` convention the coordinates\nare literal outer-face positions and no grow happens.)\n\n## Object shape\n\n```jsonc\n{\n "type": "roof",\n "roof_type": "pitched" | "shed" | "flat",\n "default_endpoint": "closed" | "open", // pitched only: closed=hip, open=gable\n "min_overhang": 25, // > 0 \u2014 eave overhang beyond the walls\n "slope": { "by": "height", "ridge_h": 50 }, // rise above wall top \u2026\n // or { "by": "angle", "angle_deg": 30 }, // \u2026 or a pitch angle\n "segments": [\n {\n "id": "seg0",\n "start": [x, y], "end": [x, y], // the ridge (pitched) / high edge (shed)\n "width": 200, // perpendicular span, segment centred in it\n "slope": { \u2026 }, // optional per-segment slope override\n "shed_high_side": "\u2026", // shed only\n "hip_setback_start": \u2026, "hip_setback_end": \u2026, // optional, tune closed hip faces\n // Per-side overhang overrides (each defaults to min_overhang). Cantilever one\n // edge \u2014 e.g. big overhang_end over an entry landing \u2014 while others stay tight.\n "overhang_start": \u2026, "overhang_end": \u2026, // along the ridge axis (shed; on a gable\n // open end = gable_overhang_*)\n "overhang_low": \u2026, "overhang_high": \u2026, // SHED eaves (down-slope / up-slope)\n "overhang_left": \u2026, "overhang_right": \u2026 // PITCHED eaves (left / right of ridge;\n // single-segment only \u2014 see below)\n }\n ],\n "trusses": [\n { "segment_id": "seg0", "type": "fink", "positions_along": [100, 210, 320] }\n // type: "fink" (pitched) | "mono_pitch" (shed)\n // positions_along: distances along the segment where a truss sits\n ],\n "framing": { \u2026 } // optional overrides \u2014 omit unless asked\n}\n```\n\n`slope` may be given once at the roof level (applies to all segments) or per\nsegment. `by: "height"` \u2192 `ridge_h` is the ridge rise above the wall top; `by:\n"angle"` \u2192 `angle_deg` is the pitch.\n\n## Joints (multi-segment shapes)\n\nWhen two segments share an **exactly coincident endpoint**, the pipeline\nauto-resolves that end as a **joint** (no hip/gable face there) \u2014 this is how L,\nU, and closed-loop (courtyard) roofs are formed. Get the endpoints numerically\nequal or they won\'t join.\n\n## Shape \u2192 segment recipe\n\n| Want | Segments |\n|---|---|\n| Simple hip (one ridge, hipped ends) | 1, `pitched`, `closed` |\n| Simple gable (one ridge, gable ends) | 1, `pitched`, `open` (set `default_endpoint: "open"`) |\n| **L-shape** (two wings) | 2, `pitched`, coincident inner endpoints |\n| **Courtyard** (ridge loop) | 4, `pitched`, endpoints chained in a loop |\n| **Flat** roof/terrace | 1+, `flat` |\n| Shed / mono-pitch (lean-to, verandah) | 1, `shed`, `shed_high_side` set (derive from a `pitched` seg; add `shed_high_side` + slope) |\n\nFor a full worked hip roof, see `wadi-skill/architect/examples/coastal_konkan.wadi`; the DSL examples in `wadi-dsl/examples/` (for example `konkan_cottage.wdl` and `two_story.wdl`) show more roofs in context.\n\n## Rules & pitfalls\n\n- The roof **footprint must cover the plinth footprint** \u2014 segment lines +\n widths should span the walls they sit on. Mirror the example\'s relationship to\n its plinth.\n- `min_overhang` must be **> 0**.\n- Every segment needs an `id`; every truss references a real `segment_id`.\n- Shed segments **require** a slope (`ridge_h` or `angle_deg`) and a\n `shed_high_side`, or derivation throws.\n- After any roof edit: run `validate.mjs` \u2014 a zero-length segment, missing slope,\n or non-covering footprint fails there, not in the schema.\n'
|
|
434303
434395
|
},
|
|
434304
434396
|
"data-model": {
|
|
434305
434397
|
"title": "The underlying .wadi schema (generated from Zod)",
|
|
@@ -434322,8 +434414,12 @@ function stdResolveModule(ref) {
|
|
|
434322
434414
|
return MODULES[ref]?.source;
|
|
434323
434415
|
}
|
|
434324
434416
|
var ALL_VIEWS = ["plans", "elevations", "roof"];
|
|
434325
|
-
function
|
|
434326
|
-
|
|
434417
|
+
function makeResolver(modules) {
|
|
434418
|
+
if (!modules) return stdResolveModule;
|
|
434419
|
+
return (ref) => modules[ref] ?? stdResolveModule(ref);
|
|
434420
|
+
}
|
|
434421
|
+
function compileWithWarnings(wdl, modules) {
|
|
434422
|
+
const compiled = compileDsl(wdl, { resolveModule: makeResolver(modules) });
|
|
434327
434423
|
registerExposedComponents(compiled);
|
|
434328
434424
|
const { config: config3, warnings } = resolveParametric(compiled);
|
|
434329
434425
|
return { config: config3, warnings };
|
|
@@ -434358,11 +434454,11 @@ function scopeWdl(wdl) {
|
|
|
434358
434454
|
registerExposedComponents(compiled);
|
|
434359
434455
|
return buildRefsView(compiled);
|
|
434360
434456
|
}
|
|
434361
|
-
function checkWdl(wdl) {
|
|
434457
|
+
function checkWdl(wdl, modules) {
|
|
434362
434458
|
let config3;
|
|
434363
434459
|
let formulaWarnings;
|
|
434364
434460
|
try {
|
|
434365
|
-
({ config: config3, warnings: formulaWarnings } = compileWithWarnings(wdl));
|
|
434461
|
+
({ config: config3, warnings: formulaWarnings } = compileWithWarnings(wdl, modules));
|
|
434366
434462
|
} catch (e) {
|
|
434367
434463
|
return { ok: false, errors: [{ level: "error", message: e.message }], warnings: [] };
|
|
434368
434464
|
}
|
|
@@ -434861,18 +434957,32 @@ function createWadiMcpServer(opts = {}) {
|
|
|
434861
434957
|
if (sessionBaseUrl) {
|
|
434862
434958
|
const base = sessionBaseUrl.replace(/\/$/, "");
|
|
434863
434959
|
const sessionUrl = (code) => `${base}/session/${encodeURIComponent(code)}`;
|
|
434960
|
+
const getSession = async (code) => {
|
|
434961
|
+
const resp = await fetch(sessionUrl(code));
|
|
434962
|
+
const j = await resp.json().catch(() => ({}));
|
|
434963
|
+
return { wdl: j.wdl ?? "", modules: j.modules ?? {} };
|
|
434964
|
+
};
|
|
434965
|
+
const postSession = async (code, body) => {
|
|
434966
|
+
const resp = await fetch(sessionUrl(code), {
|
|
434967
|
+
method: "POST",
|
|
434968
|
+
headers: { "content-type": "application/json" },
|
|
434969
|
+
body: JSON.stringify(body)
|
|
434970
|
+
});
|
|
434971
|
+
return (await resp.json().catch(() => ({}))).clients ?? 0;
|
|
434972
|
+
};
|
|
434864
434973
|
server.registerTool(
|
|
434865
434974
|
"wadi_session_set",
|
|
434866
434975
|
{
|
|
434867
434976
|
title: "Push WDL to the live app session",
|
|
434868
|
-
description: "Compile-check a `.wdl` and, if it builds, push it to a LIVE session the user is watching in the Wadi app \u2014 they see it render (2D + 3D) as you edit. Use this INSTEAD of just returning WDL text whenever the user has given you a session code (they start one in the app via \u201CCo-edit with an agent\u201D). Returns the structural conventions check. Typical loop: wadi_session_get
|
|
434977
|
+
description: "Compile-check a `.wdl` and, if it builds, push it to a LIVE session the user is watching in the Wadi app \u2014 they see it render (2D + 3D) as you edit. Use this INSTEAD of just returning WDL text whenever the user has given you a session code (they start one in the app via \u201CCo-edit with an agent\u201D). If the `.wdl` imports a custom component module, register it first with wadi_session_add_module. Returns the structural conventions check. Typical loop: wadi_session_get \u2192 edit \u2192 wadi_session_set \u2192 read warnings \u2192 repeat.",
|
|
434869
434978
|
inputSchema: {
|
|
434870
434979
|
session: external_exports.string().describe("The session code the user started in the Wadi app."),
|
|
434871
434980
|
wdl: external_exports.string().describe("The full .wdl source to push.")
|
|
434872
434981
|
}
|
|
434873
434982
|
},
|
|
434874
434983
|
async ({ session, wdl }) => {
|
|
434875
|
-
const
|
|
434984
|
+
const { modules } = await getSession(session).catch(() => ({ modules: {} }));
|
|
434985
|
+
const r = checkWdl(wdl, modules);
|
|
434876
434986
|
if (!r.ok) {
|
|
434877
434987
|
const lines2 = [`\u274C Not pushed \u2014 ${r.errors.length} error(s). Fix and retry:`];
|
|
434878
434988
|
for (const e of r.errors) lines2.push(` \u2716 ${e.rule ? `[${e.rule}] ` : ""}${e.message}`);
|
|
@@ -434880,12 +434990,7 @@ function createWadiMcpServer(opts = {}) {
|
|
|
434880
434990
|
}
|
|
434881
434991
|
let clients = 0;
|
|
434882
434992
|
try {
|
|
434883
|
-
|
|
434884
|
-
method: "POST",
|
|
434885
|
-
headers: { "content-type": "application/json" },
|
|
434886
|
-
body: JSON.stringify({ wdl })
|
|
434887
|
-
});
|
|
434888
|
-
clients = (await resp.json().catch(() => ({}))).clients ?? 0;
|
|
434993
|
+
clients = await postSession(session, { wdl });
|
|
434889
434994
|
} catch (e) {
|
|
434890
434995
|
return { content: [{ type: "text", text: "\u274C Push failed: " + e.message }], isError: true };
|
|
434891
434996
|
}
|
|
@@ -434900,16 +435005,19 @@ function createWadiMcpServer(opts = {}) {
|
|
|
434900
435005
|
"wadi_session_get",
|
|
434901
435006
|
{
|
|
434902
435007
|
title: "Read the live app session's WDL",
|
|
434903
|
-
description: "Return the current `.wdl` in a live session \u2014 use it to pick up where the user's model is (or edits they made in the app) before you start changing it. `session` is the code from the app.",
|
|
435008
|
+
description: "Return the current `.wdl` in a live session (and the names of any custom component modules registered) \u2014 use it to pick up where the user's model is (or edits they made in the app) before you start changing it. `session` is the code from the app.",
|
|
434904
435009
|
inputSchema: { session: external_exports.string().describe("The session code from the Wadi app.") }
|
|
434905
435010
|
},
|
|
434906
435011
|
async ({ session }) => {
|
|
434907
435012
|
try {
|
|
434908
|
-
const
|
|
434909
|
-
const
|
|
435013
|
+
const { wdl, modules } = await getSession(session);
|
|
435014
|
+
const mods = Object.keys(modules);
|
|
435015
|
+
const suffix = mods.length ? `
|
|
435016
|
+
|
|
435017
|
+
(custom modules registered: ${mods.join(", ")})` : "";
|
|
434910
435018
|
return {
|
|
434911
435019
|
content: [
|
|
434912
|
-
{ type: "text", text: wdl ? wdl : "(the session is empty \u2014 no model yet; author one and wadi_session_set it)" }
|
|
435020
|
+
{ type: "text", text: (wdl ? wdl : "(the session is empty \u2014 no model yet; author one and wadi_session_set it)") + suffix }
|
|
434913
435021
|
]
|
|
434914
435022
|
};
|
|
434915
435023
|
} catch (e) {
|
|
@@ -434917,6 +435025,47 @@ function createWadiMcpServer(opts = {}) {
|
|
|
434917
435025
|
}
|
|
434918
435026
|
}
|
|
434919
435027
|
);
|
|
435028
|
+
server.registerTool(
|
|
435029
|
+
"wadi_session_add_module",
|
|
435030
|
+
{
|
|
435031
|
+
title: "Register a reusable component module in the session",
|
|
435032
|
+
description: 'Register (or replace) a reusable component module in a LIVE session, so the main `.wdl` can `import "ref" as ns` and place it with `use ns.Name at (x,y)`. The module is a `.wdl` holding `component Name { \u2026 }` definitions in LOCAL coords (no `house` block). The module is saved inside the user\'s `.wadi` so the design stays self-contained. Add it BEFORE the wadi_session_set that imports it. Compile-checks the module standalone first.',
|
|
435033
|
+
inputSchema: {
|
|
435034
|
+
session: external_exports.string().describe("The session code from the Wadi app."),
|
|
435035
|
+
ref: external_exports.string().describe('The import ref, e.g. "dining-set".'),
|
|
435036
|
+
wdl: external_exports.string().describe("The module's .wdl source (component definitions only, no house block).")
|
|
435037
|
+
}
|
|
435038
|
+
},
|
|
435039
|
+
async ({ session, ref, wdl }) => {
|
|
435040
|
+
const key = ref.trim();
|
|
435041
|
+
if (!key) return { content: [{ type: "text", text: "\u274C A module ref is required." }], isError: true };
|
|
435042
|
+
try {
|
|
435043
|
+
const clients = await postSession(session, { setModule: { ref: key, wdl } });
|
|
435044
|
+
return {
|
|
435045
|
+
content: [{ type: "text", text: `\u2705 Registered module "${key}" in session "${session}" (${clients} viewer(s) connected). Now \`import "${key}" as ns\` in the main WDL and wadi_session_set it.` }]
|
|
435046
|
+
};
|
|
435047
|
+
} catch (e) {
|
|
435048
|
+
return { content: [{ type: "text", text: "\u274C Add failed: " + e.message }], isError: true };
|
|
435049
|
+
}
|
|
435050
|
+
}
|
|
435051
|
+
);
|
|
435052
|
+
server.registerTool(
|
|
435053
|
+
"wadi_session_list_modules",
|
|
435054
|
+
{
|
|
435055
|
+
title: "List the session's component modules",
|
|
435056
|
+
description: "List the custom component modules registered in a live session (their import refs). Inbuilt packs (std-furniture, konkan/base) are always available and are not listed.",
|
|
435057
|
+
inputSchema: { session: external_exports.string().describe("The session code from the Wadi app.") }
|
|
435058
|
+
},
|
|
435059
|
+
async ({ session }) => {
|
|
435060
|
+
try {
|
|
435061
|
+
const { modules } = await getSession(session);
|
|
435062
|
+
const mods = Object.keys(modules);
|
|
435063
|
+
return { content: [{ type: "text", text: mods.length ? mods.join("\n") : "(no custom modules registered)" }] };
|
|
435064
|
+
} catch (e) {
|
|
435065
|
+
return { content: [{ type: "text", text: "\u274C Read failed: " + e.message }], isError: true };
|
|
435066
|
+
}
|
|
435067
|
+
}
|
|
435068
|
+
);
|
|
434920
435069
|
}
|
|
434921
435070
|
return server;
|
|
434922
435071
|
}
|