@field/cascade 0.3.1 → 0.5.1

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.
Files changed (129) hide show
  1. package/AGENTS.md +32 -9
  2. package/ARCHITECTURE.md +75 -13
  3. package/CHANGELOG.md +98 -1
  4. package/CONTRIBUTING.md +104 -0
  5. package/DESIGN.md +24 -15
  6. package/README.md +121 -14
  7. package/dist/assets/{CodeEditor-D22fVi-4.js → CodeEditor-dOrU3aQg.js} +7 -7
  8. package/dist/assets/{CodePanel-DPNEYfcR.js → CodePanel-Bqy85qen.js} +2 -2
  9. package/dist/assets/{CustomNodeDialog-DeHCAvgb.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
  10. package/dist/assets/{ProjectSettings-DWpVtWEx.js → ProjectSettings-D192mr6g.js} +2 -2
  11. package/dist/assets/{SettingsDialog-za00IS3L.js → SettingsDialog-BTeeIE3T.js} +1 -1
  12. package/dist/assets/{VersionHistoryDialog-DTMGhI0_.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
  13. package/dist/assets/{core-JsyKMO1k.js → core-DwbcGL0o.js} +2 -2
  14. package/dist/assets/{cssMode-emdRheFK.js → cssMode-Dcjn356x.js} +1 -1
  15. package/dist/assets/definitions-C_SY6Bhg.js +1 -0
  16. package/dist/assets/{editor.api-xHX-fMSo.js → editor.api-D7Q83606.js} +1 -1
  17. package/dist/assets/{freemarker2-C3M8_Gs3.js → freemarker2-DCCiHlV6.js} +1 -1
  18. package/dist/assets/geo-CT-wtvaE.js +1 -0
  19. package/dist/assets/{handlebars-CBMurtFk.js → handlebars-BCxC8xu1.js} +1 -1
  20. package/dist/assets/{html-xbLQwknw.js → html-COwepzSd.js} +1 -1
  21. package/dist/assets/{htmlMode-2wKdDTon.js → htmlMode-Dpg7H0rx.js} +1 -1
  22. package/dist/assets/{image-i1j6nWKe.js → image-CMfMMxlC.js} +3 -3
  23. package/dist/assets/index-De1cIZ9N.js +212 -0
  24. package/dist/assets/index-ilgbOu6G.css +1 -0
  25. package/dist/assets/{javascript-DJaOo_2I.js → javascript-CSwWxfLr.js} +1 -1
  26. package/dist/assets/{jsonMode-Bheas8_0.js → jsonMode-BXRriTpj.js} +1 -1
  27. package/dist/assets/{liquid-BCj_BGbM.js → liquid-Cmkfyogc.js} +1 -1
  28. package/dist/assets/{lspLanguageFeatures-BfIsx_xz.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
  29. package/dist/assets/{mdx-DwiQDiVO.js → mdx-BzrNjNvK.js} +1 -1
  30. package/dist/assets/nodeTypeUtils-BDJsc2Ks.js +1 -0
  31. package/dist/assets/pop-BdKEHslJ.js +1 -0
  32. package/dist/assets/{python-BjrB5nqj.js → python-D4E9jbx7.js} +1 -1
  33. package/dist/assets/{razor-CfoBmGDs.js → razor-B3x9cIEM.js} +1 -1
  34. package/dist/assets/{tsMode-BcEoMSxp.js → tsMode-dhQYPNMZ.js} +1 -1
  35. package/dist/assets/{typescript-DIg7TEmQ.js → typescript-CITkDxAE.js} +1 -1
  36. package/dist/assets/{workers-vXYuwtPw.js → workers-BQvF9bbO.js} +1 -1
  37. package/dist/assets/{xml-DexLQIQy.js → xml-DvHlU3A8.js} +1 -1
  38. package/dist/assets/{yaml-DUnD_1dE.js → yaml-uznsLgs8.js} +1 -1
  39. package/dist/cli/index.js +16465 -12096
  40. package/dist/cli/runtime/gpu.ts +71 -0
  41. package/dist/cli/runtime/io.ts +31 -25
  42. package/dist/contracts/camera.d.ts +70 -0
  43. package/dist/contracts/camera.js +27 -0
  44. package/dist/contracts/capabilities.d.ts +6 -6
  45. package/dist/contracts/definition.d.ts +62 -4
  46. package/dist/contracts/index.d.ts +4 -0
  47. package/dist/contracts/index.js +4 -0
  48. package/dist/contracts/particles.d.ts +54 -0
  49. package/dist/contracts/particles.js +167 -0
  50. package/dist/contracts/scene.d.ts +129 -0
  51. package/dist/contracts/scene.js +73 -0
  52. package/dist/contracts/schema.js +26 -3
  53. package/dist/contracts/series.d.ts +75 -0
  54. package/dist/contracts/series.js +2 -0
  55. package/dist/contracts/validate.js +23 -5
  56. package/dist/contracts/values.d.ts +4 -2
  57. package/dist/contracts/values.js +10 -0
  58. package/dist/gpu.d.ts +17 -0
  59. package/dist/gpu.js +61 -1
  60. package/dist/index.html +3 -3
  61. package/dist/io.d.ts +4 -16
  62. package/dist/io.js +26 -8
  63. package/dist/player/app.js +810 -0
  64. package/dist/player/embed.d.ts +4 -0
  65. package/dist/player/embed.js +77 -0
  66. package/dist/player/types.d.ts +35 -0
  67. package/dist/runtime/builtins/core/camera.d.ts +252 -0
  68. package/dist/runtime/builtins/core/camera.js +149 -0
  69. package/dist/runtime/builtins/core/index.d.ts +4 -2
  70. package/dist/runtime/builtins/core/index.js +8 -2
  71. package/dist/runtime/builtins/core/structural.d.ts +168 -0
  72. package/dist/runtime/builtins/core/structural.js +175 -0
  73. package/dist/runtime/builtins/core/time.d.ts +165 -0
  74. package/dist/runtime/builtins/core/time.js +110 -0
  75. package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
  76. package/dist/runtime/builtins/geo/definitions.js +70 -0
  77. package/dist/runtime/builtins/geo/index.d.ts +2 -1
  78. package/dist/runtime/builtins/geo/index.js +3 -1
  79. package/dist/runtime/builtins/geo/render.d.ts +87 -0
  80. package/dist/runtime/builtins/geo/render.js +87 -0
  81. package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
  82. package/dist/runtime/builtins/geo/svgExport.js +10 -0
  83. package/dist/runtime/builtins/index.d.ts +5 -2
  84. package/dist/runtime/builtins/index.js +13 -3
  85. package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
  86. package/dist/runtime/builtins/pop/definitions.js +815 -0
  87. package/dist/runtime/builtins/pop/index.d.ts +2 -0
  88. package/dist/runtime/builtins/pop/index.js +2 -0
  89. package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
  90. package/dist/runtime/builtins/scene/definitions.js +224 -0
  91. package/dist/runtime/builtins/scene/index.d.ts +2 -0
  92. package/dist/runtime/builtins/scene/index.js +2 -0
  93. package/dist/runtime/camera/index.d.ts +60 -0
  94. package/dist/runtime/camera/index.js +234 -0
  95. package/dist/runtime/definition/architecture.js +36 -1
  96. package/dist/runtime/geometry/render.d.ts +109 -0
  97. package/dist/runtime/geometry/render.js +145 -0
  98. package/dist/runtime/geometry/svg.d.ts +29 -0
  99. package/dist/runtime/geometry/svg.js +11 -5
  100. package/dist/runtime/gpu.d.ts +33 -0
  101. package/dist/runtime/gpu.js +218 -0
  102. package/dist/runtime/index.d.ts +4 -0
  103. package/dist/runtime/index.js +4 -0
  104. package/dist/runtime/pop/cache.d.ts +27 -0
  105. package/dist/runtime/pop/cache.js +123 -0
  106. package/dist/runtime/pop/forces.d.ts +202 -0
  107. package/dist/runtime/pop/forces.js +519 -0
  108. package/dist/runtime/pop/index.d.ts +5 -0
  109. package/dist/runtime/pop/index.js +5 -0
  110. package/dist/runtime/pop/state.d.ts +136 -0
  111. package/dist/runtime/pop/state.js +283 -0
  112. package/dist/runtime/pop/trail.d.ts +62 -0
  113. package/dist/runtime/pop/trail.js +142 -0
  114. package/dist/runtime/runtime.js +284 -9
  115. package/dist/runtime/scene/index.d.ts +50 -0
  116. package/dist/runtime/scene/index.js +94 -0
  117. package/dist/runtime/series/index.d.ts +41 -0
  118. package/dist/runtime/series/index.js +167 -0
  119. package/dist/runtime/types.d.ts +1 -1
  120. package/dist/studio/panel.d.ts +68 -1
  121. package/doc/HEADLESS_GPU.md +160 -0
  122. package/doc/NODE_REFERENCE.md +408 -1
  123. package/doc/PROJECT_AUTHORING.md +118 -20
  124. package/doc/WEB_PLAYER.md +131 -0
  125. package/package.json +25 -3
  126. package/dist/assets/geo-BAR6lQp5.js +0 -1
  127. package/dist/assets/index-BDV5xGBZ.css +0 -1
  128. package/dist/assets/index-CMcH8AgP.js +0 -212
  129. package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
@@ -210,6 +210,26 @@ export declare const svgExportDefinition: {
210
210
  readonly default: 0;
211
211
  readonly label: "Margin";
212
212
  };
213
+ /**
214
+ * An explicit world rectangle, `[minX, minY, maxX, maxY]`, instead of the
215
+ * geometry's own bounds. All zero derives them as before.
216
+ *
217
+ * A frame sequence needs it: derived bounds give every frame its own
218
+ * viewBox, so a growing drawing appears to zoom out over the animation.
219
+ */
220
+ readonly bounds: {
221
+ readonly type: "vec4";
222
+ readonly default: readonly [0, 0, 0, 0];
223
+ readonly label: "Bounds";
224
+ };
225
+ /** A ground behind the drawing. Fully transparent leaves the document
226
+ * transparent, which is what plotter work wants. */
227
+ readonly background: {
228
+ readonly type: "color";
229
+ readonly default: readonly [0, 0, 0, 0];
230
+ readonly label: "Background";
231
+ readonly description: "A ground behind the drawing. Transparent by default, because a plotter wants paper.";
232
+ };
213
233
  readonly stroke: {
214
234
  readonly type: "color";
215
235
  readonly default: readonly [0, 0, 0, 1];
@@ -263,6 +283,110 @@ export declare const svgExportDefinition: {
263
283
  };
264
284
  };
265
285
  };
286
+ export declare const renderDefinition: {
287
+ readonly apiVersion: 1;
288
+ readonly label: "Render";
289
+ readonly description: "Draw a scene through a camera, as a raster.";
290
+ readonly icon: "Camera";
291
+ readonly runsOn: "portable";
292
+ readonly capabilities: readonly ["assets"];
293
+ readonly inputs: {
294
+ /**
295
+ * A scene, or a geometry — `geometry` widens to `scene`, so wiring a
296
+ * geometry straight in still works and arrives through `asScene`.
297
+ *
298
+ * Taking a scene rather than a geometry is what makes the viewport and this
299
+ * node incapable of disagreeing about framing: both read the same object,
300
+ * so the picture on screen and the picture in the PNG come from one camera
301
+ * and one list of geometry rather than from two paths that have to be kept
302
+ * in step by hand.
303
+ */
304
+ readonly scene: {
305
+ readonly kind: "data";
306
+ readonly type: "scene";
307
+ };
308
+ /**
309
+ * The camera, and required in the sense that matters: it may come from the
310
+ * scene instead, but it may not be invented. A render with an implicit
311
+ * camera is a render nobody can reproduce, and `cascade.core.Camera` is one
312
+ * node away.
313
+ *
314
+ * Wired here it **overrides** the scene's own camera, which is what lets one
315
+ * scene be rendered from several viewpoints without rebuilding it — and is
316
+ * also why the viewport's navigated camera never reaches this node. A view
317
+ * you dragged is not a document.
318
+ */
319
+ readonly camera: {
320
+ readonly kind: "data";
321
+ readonly type: "camera";
322
+ };
323
+ };
324
+ readonly outputs: {
325
+ readonly image: {
326
+ readonly kind: "data";
327
+ readonly type: "image";
328
+ };
329
+ readonly asset: {
330
+ readonly kind: "data";
331
+ readonly type: "asset";
332
+ };
333
+ };
334
+ readonly props: {
335
+ /** Pixels. Separate from the camera's own `resolution`, which describes
336
+ * the frame the projection assumes — so a preview renders small and frames
337
+ * identically to the full-size render. */
338
+ readonly size: {
339
+ readonly type: "vec2i";
340
+ readonly default: readonly [1280, 720];
341
+ readonly min: 16;
342
+ readonly max: 8192;
343
+ };
344
+ readonly filename: {
345
+ readonly type: "string";
346
+ readonly default: "render.png";
347
+ readonly label: "Filename";
348
+ };
349
+ readonly background: {
350
+ readonly type: "color";
351
+ readonly default: readonly [0, 0, 0, 0];
352
+ readonly label: "Background";
353
+ };
354
+ /** Fallback for a primitive with no `Cd`, exactly as SvgExport's is. */
355
+ readonly stroke: {
356
+ readonly type: "color";
357
+ readonly default: readonly [0, 0, 0, 1];
358
+ readonly label: "Stroke";
359
+ };
360
+ readonly strokeWidth: {
361
+ readonly type: "float";
362
+ readonly default: 1;
363
+ readonly min: 0;
364
+ readonly max: 200;
365
+ readonly step: 0.05;
366
+ };
367
+ readonly opacity: {
368
+ readonly type: "float";
369
+ readonly default: 1;
370
+ readonly min: 0;
371
+ readonly max: 1;
372
+ readonly step: 0.01;
373
+ };
374
+ /** Points no primitive claimed. Off by default: a trail system has
375
+ * thousands of vertices and a dot on every one buries the strokes. */
376
+ readonly drawPoints: {
377
+ readonly type: "bool";
378
+ readonly default: false;
379
+ readonly label: "Draw Loose Points";
380
+ };
381
+ readonly pointRadius: {
382
+ readonly type: "float";
383
+ readonly default: 1.5;
384
+ readonly min: 0;
385
+ readonly max: 50;
386
+ readonly step: 0.1;
387
+ };
388
+ };
389
+ };
266
390
  export declare const geoNodeDefinitions: readonly [readonly ["cascade.geo.Rectangle", {
267
391
  readonly apiVersion: 1;
268
392
  readonly label: "Rectangle";
@@ -470,6 +594,26 @@ export declare const geoNodeDefinitions: readonly [readonly ["cascade.geo.Rectan
470
594
  readonly default: 0;
471
595
  readonly label: "Margin";
472
596
  };
597
+ /**
598
+ * An explicit world rectangle, `[minX, minY, maxX, maxY]`, instead of the
599
+ * geometry's own bounds. All zero derives them as before.
600
+ *
601
+ * A frame sequence needs it: derived bounds give every frame its own
602
+ * viewBox, so a growing drawing appears to zoom out over the animation.
603
+ */
604
+ readonly bounds: {
605
+ readonly type: "vec4";
606
+ readonly default: readonly [0, 0, 0, 0];
607
+ readonly label: "Bounds";
608
+ };
609
+ /** A ground behind the drawing. Fully transparent leaves the document
610
+ * transparent, which is what plotter work wants. */
611
+ readonly background: {
612
+ readonly type: "color";
613
+ readonly default: readonly [0, 0, 0, 0];
614
+ readonly label: "Background";
615
+ readonly description: "A ground behind the drawing. Transparent by default, because a plotter wants paper.";
616
+ };
473
617
  readonly stroke: {
474
618
  readonly type: "color";
475
619
  readonly default: readonly [0, 0, 0, 1];
@@ -522,5 +666,108 @@ export declare const geoNodeDefinitions: readonly [readonly ["cascade.geo.Rectan
522
666
  readonly description: "Decimal places on emitted coordinates.";
523
667
  };
524
668
  };
669
+ }], readonly ["cascade.geo.Render", {
670
+ readonly apiVersion: 1;
671
+ readonly label: "Render";
672
+ readonly description: "Draw a scene through a camera, as a raster.";
673
+ readonly icon: "Camera";
674
+ readonly runsOn: "portable";
675
+ readonly capabilities: readonly ["assets"];
676
+ readonly inputs: {
677
+ /**
678
+ * A scene, or a geometry — `geometry` widens to `scene`, so wiring a
679
+ * geometry straight in still works and arrives through `asScene`.
680
+ *
681
+ * Taking a scene rather than a geometry is what makes the viewport and this
682
+ * node incapable of disagreeing about framing: both read the same object,
683
+ * so the picture on screen and the picture in the PNG come from one camera
684
+ * and one list of geometry rather than from two paths that have to be kept
685
+ * in step by hand.
686
+ */
687
+ readonly scene: {
688
+ readonly kind: "data";
689
+ readonly type: "scene";
690
+ };
691
+ /**
692
+ * The camera, and required in the sense that matters: it may come from the
693
+ * scene instead, but it may not be invented. A render with an implicit
694
+ * camera is a render nobody can reproduce, and `cascade.core.Camera` is one
695
+ * node away.
696
+ *
697
+ * Wired here it **overrides** the scene's own camera, which is what lets one
698
+ * scene be rendered from several viewpoints without rebuilding it — and is
699
+ * also why the viewport's navigated camera never reaches this node. A view
700
+ * you dragged is not a document.
701
+ */
702
+ readonly camera: {
703
+ readonly kind: "data";
704
+ readonly type: "camera";
705
+ };
706
+ };
707
+ readonly outputs: {
708
+ readonly image: {
709
+ readonly kind: "data";
710
+ readonly type: "image";
711
+ };
712
+ readonly asset: {
713
+ readonly kind: "data";
714
+ readonly type: "asset";
715
+ };
716
+ };
717
+ readonly props: {
718
+ /** Pixels. Separate from the camera's own `resolution`, which describes
719
+ * the frame the projection assumes — so a preview renders small and frames
720
+ * identically to the full-size render. */
721
+ readonly size: {
722
+ readonly type: "vec2i";
723
+ readonly default: readonly [1280, 720];
724
+ readonly min: 16;
725
+ readonly max: 8192;
726
+ };
727
+ readonly filename: {
728
+ readonly type: "string";
729
+ readonly default: "render.png";
730
+ readonly label: "Filename";
731
+ };
732
+ readonly background: {
733
+ readonly type: "color";
734
+ readonly default: readonly [0, 0, 0, 0];
735
+ readonly label: "Background";
736
+ };
737
+ /** Fallback for a primitive with no `Cd`, exactly as SvgExport's is. */
738
+ readonly stroke: {
739
+ readonly type: "color";
740
+ readonly default: readonly [0, 0, 0, 1];
741
+ readonly label: "Stroke";
742
+ };
743
+ readonly strokeWidth: {
744
+ readonly type: "float";
745
+ readonly default: 1;
746
+ readonly min: 0;
747
+ readonly max: 200;
748
+ readonly step: 0.05;
749
+ };
750
+ readonly opacity: {
751
+ readonly type: "float";
752
+ readonly default: 1;
753
+ readonly min: 0;
754
+ readonly max: 1;
755
+ readonly step: 0.01;
756
+ };
757
+ /** Points no primitive claimed. Off by default: a trail system has
758
+ * thousands of vertices and a dot on every one buries the strokes. */
759
+ readonly drawPoints: {
760
+ readonly type: "bool";
761
+ readonly default: false;
762
+ readonly label: "Draw Loose Points";
763
+ };
764
+ readonly pointRadius: {
765
+ readonly type: "float";
766
+ readonly default: 1.5;
767
+ readonly min: 0;
768
+ readonly max: 50;
769
+ readonly step: 0.1;
770
+ };
771
+ };
525
772
  }]];
526
773
  //# sourceMappingURL=definitions.d.ts.map
@@ -118,6 +118,22 @@ export const svgExportDefinition = {
118
118
  },
119
119
  height: { type: "float", default: 0, min: 0, label: "Height" },
120
120
  margin: { type: "float", default: 0, label: "Margin" },
121
+ /**
122
+ * An explicit world rectangle, `[minX, minY, maxX, maxY]`, instead of the
123
+ * geometry's own bounds. All zero derives them as before.
124
+ *
125
+ * A frame sequence needs it: derived bounds give every frame its own
126
+ * viewBox, so a growing drawing appears to zoom out over the animation.
127
+ */
128
+ bounds: { type: "vec4", default: [0, 0, 0, 0], label: "Bounds" },
129
+ /** A ground behind the drawing. Fully transparent leaves the document
130
+ * transparent, which is what plotter work wants. */
131
+ background: {
132
+ type: "color",
133
+ default: [0, 0, 0, 0],
134
+ label: "Background",
135
+ description: "A ground behind the drawing. Transparent by default, because a plotter wants paper.",
136
+ },
121
137
  stroke: {
122
138
  type: "color",
123
139
  default: [0, 0, 0, 1],
@@ -171,6 +187,59 @@ export const svgExportDefinition = {
171
187
  },
172
188
  },
173
189
  };
190
+ export const renderDefinition = {
191
+ apiVersion: 1,
192
+ label: "Render",
193
+ description: "Draw a scene through a camera, as a raster.",
194
+ icon: "Camera",
195
+ runsOn: "portable",
196
+ capabilities: ["assets"],
197
+ inputs: {
198
+ /**
199
+ * A scene, or a geometry — `geometry` widens to `scene`, so wiring a
200
+ * geometry straight in still works and arrives through `asScene`.
201
+ *
202
+ * Taking a scene rather than a geometry is what makes the viewport and this
203
+ * node incapable of disagreeing about framing: both read the same object,
204
+ * so the picture on screen and the picture in the PNG come from one camera
205
+ * and one list of geometry rather than from two paths that have to be kept
206
+ * in step by hand.
207
+ */
208
+ scene: { kind: "data", type: "scene" },
209
+ /**
210
+ * The camera, and required in the sense that matters: it may come from the
211
+ * scene instead, but it may not be invented. A render with an implicit
212
+ * camera is a render nobody can reproduce, and `cascade.core.Camera` is one
213
+ * node away.
214
+ *
215
+ * Wired here it **overrides** the scene's own camera, which is what lets one
216
+ * scene be rendered from several viewpoints without rebuilding it — and is
217
+ * also why the viewport's navigated camera never reaches this node. A view
218
+ * you dragged is not a document.
219
+ */
220
+ camera: { kind: "data", type: "camera" },
221
+ },
222
+ outputs: {
223
+ image: { kind: "data", type: "image" },
224
+ asset: { kind: "data", type: "asset" },
225
+ },
226
+ props: {
227
+ /** Pixels. Separate from the camera's own `resolution`, which describes
228
+ * the frame the projection assumes — so a preview renders small and frames
229
+ * identically to the full-size render. */
230
+ size: { type: "vec2i", default: [1280, 720], min: 16, max: 8192 },
231
+ filename: { type: "string", default: "render.png", label: "Filename" },
232
+ background: { type: "color", default: [0, 0, 0, 0], label: "Background" },
233
+ /** Fallback for a primitive with no `Cd`, exactly as SvgExport's is. */
234
+ stroke: { type: "color", default: [0, 0, 0, 1], label: "Stroke" },
235
+ strokeWidth: { type: "float", default: 1, min: 0, max: 200, step: 0.05 },
236
+ opacity: { type: "float", default: 1, min: 0, max: 1, step: 0.01 },
237
+ /** Points no primitive claimed. Off by default: a trail system has
238
+ * thousands of vertices and a dot on every one buries the strokes. */
239
+ drawPoints: { type: "bool", default: false, label: "Draw Loose Points" },
240
+ pointRadius: { type: "float", default: 1.5, min: 0, max: 50, step: 0.1 },
241
+ },
242
+ };
174
243
  export const geoNodeDefinitions = Object.freeze([
175
244
  ["cascade.geo.Rectangle", rectangleDefinition],
176
245
  ["cascade.geo.Circle", circleDefinition],
@@ -178,5 +247,6 @@ export const geoNodeDefinitions = Object.freeze([
178
247
  ["cascade.geo.Merge", mergeDefinition],
179
248
  ["cascade.geo.CopyToPoints", copyToPointsDefinition],
180
249
  ["cascade.geo.SvgExport", svgExportDefinition],
250
+ ["cascade.geo.Render", renderDefinition],
181
251
  ]);
182
252
  //# sourceMappingURL=definitions.js.map
@@ -2,6 +2,7 @@ import type { DefinitionNodeRegistration } from "../../types.js";
2
2
  import { copyToPointsRegistration } from "./copyToPoints.js";
3
3
  import { circleRegistration, rectangleRegistration } from "./generators.js";
4
4
  import { mergeRegistration } from "./merge.js";
5
+ import { renderRegistration } from "./render.js";
5
6
  import { svgExportRegistration } from "./svgExport.js";
6
7
  import { transformRegistration } from "./transform.js";
7
8
  /**
@@ -16,7 +17,7 @@ import { transformRegistration } from "./transform.js";
16
17
  *
17
18
  * The namespace lives in `namespace.ts`, once.
18
19
  */
19
- export { circleRegistration, copyToPointsRegistration, mergeRegistration, rectangleRegistration, svgExportRegistration, transformRegistration, };
20
+ export { circleRegistration, copyToPointsRegistration, mergeRegistration, rectangleRegistration, renderRegistration, svgExportRegistration, transformRegistration, };
20
21
  export { GEO_NAMESPACE, geoModuleId } from "./namespace.js";
21
22
  export declare const geoNodeRegistrations: readonly DefinitionNodeRegistration[];
22
23
  export declare function geoNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
@@ -1,6 +1,7 @@
1
1
  import { copyToPointsRegistration } from "./copyToPoints.js";
2
2
  import { circleRegistration, rectangleRegistration } from "./generators.js";
3
3
  import { mergeRegistration } from "./merge.js";
4
+ import { renderRegistration } from "./render.js";
4
5
  import { svgExportRegistration } from "./svgExport.js";
5
6
  import { transformRegistration } from "./transform.js";
6
7
  /**
@@ -15,7 +16,7 @@ import { transformRegistration } from "./transform.js";
15
16
  *
16
17
  * The namespace lives in `namespace.ts`, once.
17
18
  */
18
- export { circleRegistration, copyToPointsRegistration, mergeRegistration, rectangleRegistration, svgExportRegistration, transformRegistration, };
19
+ export { circleRegistration, copyToPointsRegistration, mergeRegistration, rectangleRegistration, renderRegistration, svgExportRegistration, transformRegistration, };
19
20
  export { GEO_NAMESPACE, geoModuleId } from "./namespace.js";
20
21
  export const geoNodeRegistrations = Object.freeze([
21
22
  rectangleRegistration,
@@ -23,6 +24,7 @@ export const geoNodeRegistrations = Object.freeze([
23
24
  transformRegistration,
24
25
  mergeRegistration,
25
26
  copyToPointsRegistration,
27
+ renderRegistration,
26
28
  svgExportRegistration,
27
29
  ]);
28
30
  const registrationsById = new Map(geoNodeRegistrations.map((registration) => [
@@ -0,0 +1,87 @@
1
+ import type { NodeExecutionContext } from "@field/cascade/contracts";
2
+ import { renderDefinition } from "./definitions.js";
3
+ export declare function executeRender(context: NodeExecutionContext<typeof renderDefinition>): Promise<void>;
4
+ export declare const renderRegistration: {
5
+ kind: "definition-v1";
6
+ moduleId: string;
7
+ definition: {
8
+ readonly apiVersion: 1;
9
+ readonly label: "Render";
10
+ readonly description: "Draw a scene through a camera, as a raster.";
11
+ readonly icon: "Camera";
12
+ readonly runsOn: "portable";
13
+ readonly capabilities: readonly ["assets"];
14
+ readonly inputs: {
15
+ readonly scene: {
16
+ readonly kind: "data";
17
+ readonly type: "scene";
18
+ };
19
+ readonly camera: {
20
+ readonly kind: "data";
21
+ readonly type: "camera";
22
+ };
23
+ };
24
+ readonly outputs: {
25
+ readonly image: {
26
+ readonly kind: "data";
27
+ readonly type: "image";
28
+ };
29
+ readonly asset: {
30
+ readonly kind: "data";
31
+ readonly type: "asset";
32
+ };
33
+ };
34
+ readonly props: {
35
+ readonly size: {
36
+ readonly type: "vec2i";
37
+ readonly default: readonly [1280, 720];
38
+ readonly min: 16;
39
+ readonly max: 8192;
40
+ };
41
+ readonly filename: {
42
+ readonly type: "string";
43
+ readonly default: "render.png";
44
+ readonly label: "Filename";
45
+ };
46
+ readonly background: {
47
+ readonly type: "color";
48
+ readonly default: readonly [0, 0, 0, 0];
49
+ readonly label: "Background";
50
+ };
51
+ readonly stroke: {
52
+ readonly type: "color";
53
+ readonly default: readonly [0, 0, 0, 1];
54
+ readonly label: "Stroke";
55
+ };
56
+ readonly strokeWidth: {
57
+ readonly type: "float";
58
+ readonly default: 1;
59
+ readonly min: 0;
60
+ readonly max: 200;
61
+ readonly step: 0.05;
62
+ };
63
+ readonly opacity: {
64
+ readonly type: "float";
65
+ readonly default: 1;
66
+ readonly min: 0;
67
+ readonly max: 1;
68
+ readonly step: 0.01;
69
+ };
70
+ readonly drawPoints: {
71
+ readonly type: "bool";
72
+ readonly default: false;
73
+ readonly label: "Draw Loose Points";
74
+ };
75
+ readonly pointRadius: {
76
+ readonly type: "float";
77
+ readonly default: 1.5;
78
+ readonly min: 0;
79
+ readonly max: 50;
80
+ readonly step: 0.1;
81
+ };
82
+ };
83
+ };
84
+ loadExecute: () => Promise<typeof executeRender>;
85
+ };
86
+ export { renderDefinition } from "./definitions.js";
87
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1,87 @@
1
+ import { asScene } from "@field/cascade/contracts";
2
+ import { renderScene, renderToPng, } from "../../geometry/render.js";
3
+ import { renderDefinition } from "./definitions.js";
4
+ import { geoModuleId } from "./namespace.js";
5
+ /**
6
+ * `cascade.geo.Render` — the 3D-to-2D half of `PLAN viewport.md`.
7
+ *
8
+ * Beside `SvgExport` rather than in a namespace of its own, and the naming
9
+ * argument is in the plan: `cascade.geo.Render` puts it next to the node it
10
+ * shares a style vocabulary with, while `cascade.render.Scene` would start a
11
+ * namespace holding one node — a promise rather than a structure.
12
+ *
13
+ * **It is a node and not a viewport button** because a render is part of the
14
+ * document: it has a resolution, it is what a series renders per instance, and
15
+ * `cascade run --frames` has to produce it without a browser. A button produces
16
+ * a file; a node produces a value other nodes consume, and the second is the
17
+ * whole point of the graph.
18
+ *
19
+ * `portable` with only `assets`, like `SvgExport`: both hosts supply
20
+ * `OffscreenCanvas`, so nothing here needs a GPU. The plan argues for the CPU
21
+ * path first on the grounds that this vault's work is lines and a node that
22
+ * runs in every host — including CI — is worth more than one that is faster in
23
+ * one.
24
+ */
25
+ /**
26
+ * The one host assumption, and it lives here rather than in the renderer.
27
+ *
28
+ * Both hosts supply `OffscreenCanvas` — the browser natively, the headless host
29
+ * through @napi-rs/canvas — so a `portable` node can draw. `packages/runtime`
30
+ * itself compiles without a DOM lib and must not reach for a global, which is
31
+ * why the renderer takes a factory and this is where it comes from.
32
+ */
33
+ function hostSurface(width, height) {
34
+ const factory = globalThis
35
+ .OffscreenCanvas;
36
+ if (!factory) {
37
+ throw new Error("cascade.geo.Render needs OffscreenCanvas, which this host does not provide");
38
+ }
39
+ return new factory(width, height);
40
+ }
41
+ export async function executeRender(context) {
42
+ const { props } = context;
43
+ const scene = asScene(context.inputs.scene);
44
+ /**
45
+ * The camera input wins over the scene's own, so one scene can be rendered
46
+ * from several viewpoints without being rebuilt.
47
+ *
48
+ * And a render still refuses to invent one. The viewport may fall back to
49
+ * whatever camera you have navigated to, because a view is a way of looking;
50
+ * a render is a document, and a document with an implicit camera is one
51
+ * nobody can reproduce — including `cascade run --frames`, which has no
52
+ * viewport to borrow from.
53
+ */
54
+ const camera = context.inputs.camera ?? scene.camera;
55
+ if (!camera) {
56
+ throw new Error("cascade.geo.Render has no camera — wire a cascade.core.Camera to its camera input, or into the scene");
57
+ }
58
+ const surface = renderScene(scene.geometry, {
59
+ camera,
60
+ size: [props.size[0], props.size[1]],
61
+ ...(props.background[3] > 0 ? { background: props.background } : {}),
62
+ stroke: props.stroke,
63
+ strokeWidth: props.strokeWidth,
64
+ opacity: props.opacity,
65
+ pointRadius: props.pointRadius,
66
+ drawPoints: props.drawPoints,
67
+ surface: hostSurface,
68
+ });
69
+ const bytes = await renderToPng(surface);
70
+ const asset = await context.capabilities.assets.write(bytes, { mediaType: "image/png", suggestedName: props.filename }, { signal: context.signal });
71
+ context.outputs.asset.set(asset);
72
+ context.outputs.image.set({
73
+ path: asset.path ?? props.filename,
74
+ size: [surface.width, surface.height],
75
+ channels: "rgba",
76
+ depth: "u8",
77
+ space: "srgb",
78
+ });
79
+ }
80
+ export const renderRegistration = {
81
+ kind: "definition-v1",
82
+ moduleId: geoModuleId("Render"),
83
+ definition: renderDefinition,
84
+ loadExecute: async () => executeRender,
85
+ };
86
+ export { renderDefinition } from "./definitions.js";
87
+ //# sourceMappingURL=render.js.map
@@ -71,6 +71,17 @@ export declare const svgExportRegistration: {
71
71
  readonly default: 0;
72
72
  readonly label: "Margin";
73
73
  };
74
+ readonly bounds: {
75
+ readonly type: "vec4";
76
+ readonly default: readonly [0, 0, 0, 0];
77
+ readonly label: "Bounds";
78
+ };
79
+ readonly background: {
80
+ readonly type: "color";
81
+ readonly default: readonly [0, 0, 0, 0];
82
+ readonly label: "Background";
83
+ readonly description: "A ground behind the drawing. Transparent by default, because a plotter wants paper.";
84
+ };
74
85
  readonly stroke: {
75
86
  readonly type: "color";
76
87
  readonly default: readonly [0, 0, 0, 1];
@@ -28,6 +28,16 @@ export async function executeSvgExport(context) {
28
28
  width: props.width,
29
29
  height: props.height,
30
30
  margin: props.margin,
31
+ // All-zero means "derive", because a zero-area rectangle is not a frame
32
+ // anyone would ask for and a separate boolean for it would be a second
33
+ // way to say the same thing.
34
+ ...(props.bounds.some((value) => value !== 0)
35
+ ? { bounds: [props.bounds[0], props.bounds[1], props.bounds[2], props.bounds[3]] }
36
+ : {}),
37
+ // Only when it would show: a fully transparent ground and no ground at all
38
+ // are the same document, and emitting a rect for it would put a shape in
39
+ // the file that a plotter would try to draw.
40
+ ...(props.background[3] > 0 ? { background: props.background } : {}),
31
41
  stroke: props.stroke,
32
42
  strokeWidth: props.strokeWidth,
33
43
  opacity: props.opacity,
@@ -1,8 +1,11 @@
1
1
  import type { DefinitionNodeRegistration } from "../types.js";
2
2
  /**
3
3
  * Every reserved built-in, in one list, so that a caller does not have to know
4
- * how many namespaces there are. `cascade.core.*` is structural and routing;
5
- * `cascade.geo.*` is the geometry set.
4
+ * how many namespaces there are. `cascade.core.*` is structural and routing,
5
+ * `cascade.geo.*` is the geometry set, and `cascade.pop.*` is the particle
6
+ * set — which speaks the same `geometry` type as `geo`, so any of its outputs
7
+ * fits any geometry node's input. `cascade.scene.*` is the assembly: geometry
8
+ * plus a viewpoint plus lights, which is what the Viewer's 3D mode renders.
6
9
  */
7
10
  export declare const builtinNodeRegistrations: readonly DefinitionNodeRegistration[];
8
11
  export declare function builtinNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
@@ -1,11 +1,21 @@
1
1
  import { coreNodeRegistrations } from "./core/index.js";
2
2
  import { geoNodeRegistrations } from "./geo/index.js";
3
+ import { popNodeRegistrations } from "./pop/index.js";
4
+ import { sceneNodeRegistrations } from "./scene/index.js";
3
5
  /**
4
6
  * Every reserved built-in, in one list, so that a caller does not have to know
5
- * how many namespaces there are. `cascade.core.*` is structural and routing;
6
- * `cascade.geo.*` is the geometry set.
7
+ * how many namespaces there are. `cascade.core.*` is structural and routing,
8
+ * `cascade.geo.*` is the geometry set, and `cascade.pop.*` is the particle
9
+ * set — which speaks the same `geometry` type as `geo`, so any of its outputs
10
+ * fits any geometry node's input. `cascade.scene.*` is the assembly: geometry
11
+ * plus a viewpoint plus lights, which is what the Viewer's 3D mode renders.
7
12
  */
8
- export const builtinNodeRegistrations = Object.freeze([...coreNodeRegistrations, ...geoNodeRegistrations]);
13
+ export const builtinNodeRegistrations = Object.freeze([
14
+ ...coreNodeRegistrations,
15
+ ...geoNodeRegistrations,
16
+ ...popNodeRegistrations,
17
+ ...sceneNodeRegistrations,
18
+ ]);
9
19
  const registrationsById = new Map(builtinNodeRegistrations.map((registration) => [
10
20
  registration.moduleId,
11
21
  registration,