@genart-dev/plugin-construction 0.1.0 → 0.2.0

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/index.d.cts CHANGED
@@ -10,8 +10,6 @@ declare const envelopeLayerType: LayerTypeDefinition;
10
10
 
11
11
  declare const intersectionLayerType: LayerTypeDefinition;
12
12
 
13
- declare const constructionMcpTools: McpToolDefinition[];
14
-
15
13
  /** 3D vector. */
16
14
  interface Vec3 {
17
15
  x: number;
@@ -72,6 +70,32 @@ declare function dot3(a: Vec3, b: Vec3): number;
72
70
  /** Cross product of two Vec3. */
73
71
  declare function cross3(a: Vec3, b: Vec3): Vec3;
74
72
 
73
+ type FormType = "box" | "cylinder" | "sphere" | "cone" | "wedge" | "egg";
74
+ interface FormDefinition {
75
+ type: FormType;
76
+ position: Vec3;
77
+ size: Vec3;
78
+ rotation: Vec3;
79
+ }
80
+
81
+ /** A single component in a compound form. */
82
+ interface CompoundComponent {
83
+ type: FormType;
84
+ /** Offset from parent center (in form-size units). */
85
+ offsetX: number;
86
+ offsetY: number;
87
+ offsetZ: number;
88
+ /** Scale relative to parent form size. */
89
+ scaleX: number;
90
+ scaleY: number;
91
+ scaleZ: number;
92
+ }
93
+ /** Named compound form presets. */
94
+ declare const COMPOUND_PRESETS: Record<string, CompoundComponent[]>;
95
+ declare const compoundFormLayerType: LayerTypeDefinition;
96
+
97
+ declare const constructionMcpTools: McpToolDefinition[];
98
+
75
99
  interface EllipseParams {
76
100
  cx: number;
77
101
  cy: number;
@@ -190,14 +214,6 @@ declare function comparativeMeasure(segment1: [Vec2, Vec2], segment2: [Vec2, Vec
190
214
  label: string;
191
215
  };
192
216
 
193
- type FormType = "box" | "cylinder" | "sphere" | "cone" | "wedge" | "egg";
194
- interface FormDefinition {
195
- type: FormType;
196
- position: Vec3;
197
- size: Vec3;
198
- rotation: Vec3;
199
- }
200
-
201
217
  /**
202
218
  * Approximate form intersection by sampling both surfaces and finding
203
219
  * points where they are equidistant from the camera (same Z depth).
@@ -208,4 +224,4 @@ declare function approximateIntersection(form1: FormDefinition, form2: FormDefin
208
224
 
209
225
  declare const constructionPlugin: DesignPlugin;
210
226
 
211
- export { approximateIntersection, castShadow, clamp, comparativeMeasure, computeEnvelope, constructionMcpTools, cross3, crossContourLayerType, constructionPlugin as default, dot3, drawEllipse, drawEllipseWithHidden, ellipsePoints, envelopeAngles, envelopeLayerType, formLayerType, identityMatrix, intersectionLayerType, levelLine, lightDirection, lightDirection2D, multiplyMat3, normalize3, plumbLine, project, projectedEllipse, rotate3D, rotationMatrix, sphereTerminator, sphereValueZones, transformPoint, transformedNormalZ, valueShapesLayerType };
227
+ export { COMPOUND_PRESETS, type CompoundComponent, approximateIntersection, castShadow, clamp, comparativeMeasure, compoundFormLayerType, computeEnvelope, constructionMcpTools, cross3, crossContourLayerType, constructionPlugin as default, dot3, drawEllipse, drawEllipseWithHidden, ellipsePoints, envelopeAngles, envelopeLayerType, formLayerType, identityMatrix, intersectionLayerType, levelLine, lightDirection, lightDirection2D, multiplyMat3, normalize3, plumbLine, project, projectedEllipse, rotate3D, rotationMatrix, sphereTerminator, sphereValueZones, transformPoint, transformedNormalZ, valueShapesLayerType };
package/dist/index.d.ts CHANGED
@@ -10,8 +10,6 @@ declare const envelopeLayerType: LayerTypeDefinition;
10
10
 
11
11
  declare const intersectionLayerType: LayerTypeDefinition;
12
12
 
13
- declare const constructionMcpTools: McpToolDefinition[];
14
-
15
13
  /** 3D vector. */
16
14
  interface Vec3 {
17
15
  x: number;
@@ -72,6 +70,32 @@ declare function dot3(a: Vec3, b: Vec3): number;
72
70
  /** Cross product of two Vec3. */
73
71
  declare function cross3(a: Vec3, b: Vec3): Vec3;
74
72
 
73
+ type FormType = "box" | "cylinder" | "sphere" | "cone" | "wedge" | "egg";
74
+ interface FormDefinition {
75
+ type: FormType;
76
+ position: Vec3;
77
+ size: Vec3;
78
+ rotation: Vec3;
79
+ }
80
+
81
+ /** A single component in a compound form. */
82
+ interface CompoundComponent {
83
+ type: FormType;
84
+ /** Offset from parent center (in form-size units). */
85
+ offsetX: number;
86
+ offsetY: number;
87
+ offsetZ: number;
88
+ /** Scale relative to parent form size. */
89
+ scaleX: number;
90
+ scaleY: number;
91
+ scaleZ: number;
92
+ }
93
+ /** Named compound form presets. */
94
+ declare const COMPOUND_PRESETS: Record<string, CompoundComponent[]>;
95
+ declare const compoundFormLayerType: LayerTypeDefinition;
96
+
97
+ declare const constructionMcpTools: McpToolDefinition[];
98
+
75
99
  interface EllipseParams {
76
100
  cx: number;
77
101
  cy: number;
@@ -190,14 +214,6 @@ declare function comparativeMeasure(segment1: [Vec2, Vec2], segment2: [Vec2, Vec
190
214
  label: string;
191
215
  };
192
216
 
193
- type FormType = "box" | "cylinder" | "sphere" | "cone" | "wedge" | "egg";
194
- interface FormDefinition {
195
- type: FormType;
196
- position: Vec3;
197
- size: Vec3;
198
- rotation: Vec3;
199
- }
200
-
201
217
  /**
202
218
  * Approximate form intersection by sampling both surfaces and finding
203
219
  * points where they are equidistant from the camera (same Z depth).
@@ -208,4 +224,4 @@ declare function approximateIntersection(form1: FormDefinition, form2: FormDefin
208
224
 
209
225
  declare const constructionPlugin: DesignPlugin;
210
226
 
211
- export { approximateIntersection, castShadow, clamp, comparativeMeasure, computeEnvelope, constructionMcpTools, cross3, crossContourLayerType, constructionPlugin as default, dot3, drawEllipse, drawEllipseWithHidden, ellipsePoints, envelopeAngles, envelopeLayerType, formLayerType, identityMatrix, intersectionLayerType, levelLine, lightDirection, lightDirection2D, multiplyMat3, normalize3, plumbLine, project, projectedEllipse, rotate3D, rotationMatrix, sphereTerminator, sphereValueZones, transformPoint, transformedNormalZ, valueShapesLayerType };
227
+ export { COMPOUND_PRESETS, type CompoundComponent, approximateIntersection, castShadow, clamp, comparativeMeasure, compoundFormLayerType, computeEnvelope, constructionMcpTools, cross3, crossContourLayerType, constructionPlugin as default, dot3, drawEllipse, drawEllipseWithHidden, ellipsePoints, envelopeAngles, envelopeLayerType, formLayerType, identityMatrix, intersectionLayerType, levelLine, lightDirection, lightDirection2D, multiplyMat3, normalize3, plumbLine, project, projectedEllipse, rotate3D, rotationMatrix, sphereTerminator, sphereValueZones, transformPoint, transformedNormalZ, valueShapesLayerType };
package/dist/index.js CHANGED
@@ -2346,6 +2346,169 @@ var intersectionLayerType = {
2346
2346
  }
2347
2347
  };
2348
2348
 
2349
+ // src/compound-form-layer.ts
2350
+ var COMPOUND_PRESETS = {
2351
+ snowman: [
2352
+ { type: "sphere", offsetX: 0, offsetY: 0.35, offsetZ: 0, scaleX: 1, scaleY: 1, scaleZ: 1 },
2353
+ { type: "sphere", offsetX: 0, offsetY: -0.05, offsetZ: 0, scaleX: 0.75, scaleY: 0.75, scaleZ: 0.75 },
2354
+ { type: "sphere", offsetX: 0, offsetY: -0.35, offsetZ: 0, scaleX: 0.5, scaleY: 0.5, scaleZ: 0.5 }
2355
+ ],
2356
+ bottle: [
2357
+ { type: "cylinder", offsetX: 0, offsetY: 0.15, offsetZ: 0, scaleX: 1, scaleY: 0.7, scaleZ: 1 },
2358
+ { type: "cone", offsetX: 0, offsetY: -0.25, offsetZ: 0, scaleX: 0.6, scaleY: 0.3, scaleZ: 0.6 },
2359
+ { type: "cylinder", offsetX: 0, offsetY: -0.42, offsetZ: 0, scaleX: 0.3, scaleY: 0.15, scaleZ: 0.3 }
2360
+ ],
2361
+ mushroom: [
2362
+ { type: "sphere", offsetX: 0, offsetY: -0.2, offsetZ: 0, scaleX: 1.2, scaleY: 0.5, scaleZ: 1.2 },
2363
+ { type: "cylinder", offsetX: 0, offsetY: 0.2, offsetZ: 0, scaleX: 0.3, scaleY: 0.5, scaleZ: 0.3 }
2364
+ ],
2365
+ lamp: [
2366
+ { type: "cone", offsetX: 0, offsetY: -0.25, offsetZ: 0, scaleX: 1, scaleY: 0.5, scaleZ: 1 },
2367
+ { type: "cylinder", offsetX: 0, offsetY: 0.1, offsetZ: 0, scaleX: 0.15, scaleY: 0.5, scaleZ: 0.15 },
2368
+ { type: "cylinder", offsetX: 0, offsetY: 0.4, offsetZ: 0, scaleX: 0.5, scaleY: 0.05, scaleZ: 0.5 }
2369
+ ],
2370
+ tree: [
2371
+ { type: "cylinder", offsetX: 0, offsetY: 0.25, offsetZ: 0, scaleX: 0.2, scaleY: 0.5, scaleZ: 0.2 },
2372
+ { type: "egg", offsetX: 0, offsetY: -0.2, offsetZ: 0, scaleX: 0.8, scaleY: 0.6, scaleZ: 0.8 }
2373
+ ]
2374
+ };
2375
+ var COMPOUND_PROPERTIES = [
2376
+ {
2377
+ key: "preset",
2378
+ label: "Preset",
2379
+ type: "select",
2380
+ default: "snowman",
2381
+ options: Object.keys(COMPOUND_PRESETS).map((k) => ({ value: k, label: k.charAt(0).toUpperCase() + k.slice(1) })),
2382
+ group: "compound"
2383
+ },
2384
+ {
2385
+ key: "components",
2386
+ label: "Components (JSON)",
2387
+ type: "string",
2388
+ default: "",
2389
+ group: "compound"
2390
+ },
2391
+ {
2392
+ key: "position",
2393
+ label: "Position",
2394
+ type: "point",
2395
+ default: { x: 0.5, y: 0.5 },
2396
+ group: "compound"
2397
+ },
2398
+ {
2399
+ key: "formSize",
2400
+ label: "Size",
2401
+ type: "number",
2402
+ default: 0.25,
2403
+ min: 0.05,
2404
+ max: 0.6,
2405
+ step: 0.01,
2406
+ group: "compound"
2407
+ },
2408
+ {
2409
+ key: "rotX",
2410
+ label: "Rotation X (deg)",
2411
+ type: "number",
2412
+ default: 15,
2413
+ min: -90,
2414
+ max: 90,
2415
+ step: 5,
2416
+ group: "compound"
2417
+ },
2418
+ {
2419
+ key: "rotY",
2420
+ label: "Rotation Y (deg)",
2421
+ type: "number",
2422
+ default: 25,
2423
+ min: -180,
2424
+ max: 180,
2425
+ step: 5,
2426
+ group: "compound"
2427
+ },
2428
+ ...COMMON_GUIDE_PROPERTIES
2429
+ ];
2430
+ var compoundFormLayerType = {
2431
+ typeId: "construction:compound-form",
2432
+ displayName: "Compound Form",
2433
+ icon: "compound",
2434
+ category: "guide",
2435
+ properties: COMPOUND_PROPERTIES,
2436
+ propertyEditorId: "construction:compound-form-editor",
2437
+ createDefault() {
2438
+ const props = {};
2439
+ for (const schema of COMPOUND_PROPERTIES) {
2440
+ props[schema.key] = schema.default;
2441
+ }
2442
+ return props;
2443
+ },
2444
+ render(properties, ctx, bounds, _resources) {
2445
+ const preset = properties.preset ?? "snowman";
2446
+ const componentsJson = properties.components ?? "";
2447
+ const position = properties.position ?? { x: 0.5, y: 0.5 };
2448
+ const formSize = properties.formSize ?? 0.25;
2449
+ const rotX = properties.rotX ?? 15;
2450
+ const rotY = properties.rotY ?? 25;
2451
+ let components;
2452
+ if (componentsJson.trim()) {
2453
+ try {
2454
+ components = JSON.parse(componentsJson);
2455
+ } catch {
2456
+ components = COMPOUND_PRESETS[preset] ?? COMPOUND_PRESETS["snowman"];
2457
+ }
2458
+ } else {
2459
+ components = COMPOUND_PRESETS[preset] ?? COMPOUND_PRESETS["snowman"];
2460
+ }
2461
+ if (components.length === 0) return;
2462
+ const w = bounds.width;
2463
+ const h = bounds.height;
2464
+ const baseSize = Math.min(w, h) * formSize;
2465
+ const cx = bounds.x + w * position.x;
2466
+ const cy = bounds.y + h * position.y;
2467
+ ctx.save();
2468
+ const guideColor = properties.guideColor ?? "rgba(0,200,255,0.5)";
2469
+ const lineWidth = properties.lineWidth ?? 1;
2470
+ const dashPattern = properties.dashPattern ?? "";
2471
+ setupGuideStyle(ctx, guideColor, lineWidth, dashPattern);
2472
+ const mat = rotationMatrix(rotX, rotY, 0);
2473
+ const sorted = components.map((comp, i) => {
2474
+ const p3d = rotate3D({ x: comp.offsetX * baseSize, y: comp.offsetY * baseSize, z: comp.offsetZ * baseSize }, mat);
2475
+ return { comp, z: p3d.z, index: i };
2476
+ }).sort((a, b) => b.z - a.z);
2477
+ for (const { comp } of sorted) {
2478
+ const offset = rotate3D(
2479
+ { x: comp.offsetX * baseSize, y: comp.offsetY * baseSize, z: comp.offsetZ * baseSize },
2480
+ mat
2481
+ );
2482
+ const compCx = cx + offset.x;
2483
+ const compCy = cy + offset.y;
2484
+ const compSize = baseSize * Math.max(comp.scaleX, comp.scaleY);
2485
+ ctx.beginPath();
2486
+ const rx = compSize * comp.scaleX * 0.5;
2487
+ const ry = compSize * comp.scaleY * 0.5;
2488
+ ctx.ellipse(compCx, compCy, Math.max(1, rx), Math.max(1, ry), 0, 0, Math.PI * 2);
2489
+ ctx.stroke();
2490
+ ctx.beginPath();
2491
+ ctx.arc(compCx, compCy, 2, 0, Math.PI * 2);
2492
+ ctx.fill();
2493
+ }
2494
+ ctx.restore();
2495
+ },
2496
+ validate(properties) {
2497
+ const componentsJson = properties.components;
2498
+ if (componentsJson && componentsJson.trim()) {
2499
+ try {
2500
+ const parsed = JSON.parse(componentsJson);
2501
+ if (!Array.isArray(parsed)) {
2502
+ return [{ property: "components", message: "Components must be a JSON array" }];
2503
+ }
2504
+ } catch {
2505
+ return [{ property: "components", message: "Invalid JSON for components" }];
2506
+ }
2507
+ }
2508
+ return null;
2509
+ }
2510
+ };
2511
+
2349
2512
  // src/construction-tools.ts
2350
2513
  function textResult(text) {
2351
2514
  return { content: [{ type: "text", text }] };
@@ -2785,7 +2948,7 @@ var constructionMcpTools = [
2785
2948
  var constructionPlugin = {
2786
2949
  id: "construction",
2787
2950
  name: "Construction Guides",
2788
- version: "0.1.0",
2951
+ version: "0.2.0",
2789
2952
  tier: "free",
2790
2953
  description: "Drawing construction guides: 3D form primitives, cross-contour lines, value/shadow studies, envelope block-ins, and form intersections.",
2791
2954
  layerTypes: [
@@ -2793,7 +2956,8 @@ var constructionPlugin = {
2793
2956
  crossContourLayerType,
2794
2957
  valueShapesLayerType,
2795
2958
  envelopeLayerType,
2796
- intersectionLayerType
2959
+ intersectionLayerType,
2960
+ compoundFormLayerType
2797
2961
  ],
2798
2962
  tools: [],
2799
2963
  exportHandlers: [],
@@ -2805,10 +2969,12 @@ var constructionPlugin = {
2805
2969
  };
2806
2970
  var index_default = constructionPlugin;
2807
2971
  export {
2972
+ COMPOUND_PRESETS,
2808
2973
  approximateIntersection,
2809
2974
  castShadow,
2810
2975
  clamp,
2811
2976
  comparativeMeasure,
2977
+ compoundFormLayerType,
2812
2978
  computeEnvelope,
2813
2979
  constructionMcpTools,
2814
2980
  cross3,