topotopo 0.2.0 → 0.3.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/workspace.js CHANGED
@@ -11,7 +11,7 @@ import { didYouMean, renameExprRefs } from './graph/params.js';
11
11
  import { migrateGraph } from './graph/migrate.js';
12
12
  const NAME_RE = /^[A-Za-z_][\w.-]*$/;
13
13
  const GEOMETRY_EXTS = new Set(['obj', 'stl', 'ply', 'glb', 'gltf', 'geo', 'bgeo', 'blend', 'csv', 'json', 'points.json']);
14
- const IMAGE_EXTS = new Set(['png']);
14
+ const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'webp']);
15
15
  /** Lower-case extension of a file name without the dot; ".points.json" is one extension, dot-files have none. */
16
16
  export function inputExt(file) {
17
17
  const b = file.toLowerCase().replace(/^.*[\\/]/, '');
@@ -41,15 +41,17 @@ export class Workspace {
41
41
  listeners = new Set();
42
42
  /** the inputs folder given to the constructor (WorkspaceOptions.inputs), if any */
43
43
  inputsAt;
44
+ exportsAt;
44
45
  constructor(dir, engine, opts = {}) {
45
46
  this.dir = path.resolve(dir);
46
47
  this.engine = engine;
47
48
  this.inputsAt = opts.inputs === undefined ? undefined : path.resolve(opts.inputs);
49
+ this.exportsAt = opts.exports === undefined ? undefined : path.resolve(opts.exports);
48
50
  engine.graphResolver = (n) => this.graphs.get(n);
49
51
  }
50
52
  get graphDir() { return path.join(this.dir, 'graphs'); }
51
53
  get assetDir() { return path.join(this.dir, 'assets'); }
52
- get exportDir() { return path.join(this.dir, 'exports'); }
54
+ get exportDir() { return this.exportsAt ?? path.join(this.dir, 'exports'); }
53
55
  /**
54
56
  * files people drop in: meshes, guide curves and points, PNG masks and heightmaps (read as "inputs/<path>");
55
57
  * <dir>/inputs unless the constructor was given another folder
@@ -168,6 +170,10 @@ export class Workspace {
168
170
  base.params = { ...(base.params ?? {}), ...patch.params };
169
171
  if (patch.output)
170
172
  base.output = patch.output;
173
+ if (patch.outputs)
174
+ base.outputs = { ...base.outputs, ...patch.outputs };
175
+ if (patch.export !== undefined)
176
+ base.export = patch.export;
171
177
  if (patch.description)
172
178
  base.description = patch.description;
173
179
  return base;
@@ -253,6 +259,9 @@ export class Workspace {
253
259
  delete g.nodes[n];
254
260
  if (g.output === n)
255
261
  delete g.output;
262
+ for (const [id, out] of Object.entries(g.outputs ?? {}))
263
+ if (out.node === n)
264
+ delete g.outputs[id];
256
265
  break;
257
266
  }
258
267
  case 'connect': {
@@ -282,6 +291,9 @@ export class Workspace {
282
291
  setRef(r.node, r.idx, op.to);
283
292
  if (g.output === n)
284
293
  g.output = op.to;
294
+ for (const out of Object.values(g.outputs ?? {}))
295
+ if (out.node === n)
296
+ out.node = op.to;
285
297
  // expression references: npoints("old"), ch("old/param") …
286
298
  const touched = [];
287
299
  for (const [k, spec] of Object.entries(g.nodes)) {
@@ -312,6 +324,28 @@ export class Workspace {
312
324
  case 'bypass':
313
325
  g.nodes[need(op.node, where)].bypass = op.value ?? true;
314
326
  break;
327
+ case 'export': {
328
+ // the target of the default output, or of a named one; null removes it (validated with the graph)
329
+ if (op.export === undefined)
330
+ throw new Error(`${where}: give "export": a format ("glb"), {format, path?, ...options}, or null`);
331
+ if (op.output === undefined) {
332
+ if (op.export === null)
333
+ delete g.export;
334
+ else
335
+ g.export = op.export;
336
+ break;
337
+ }
338
+ const out = g.outputs?.[op.output];
339
+ if (!out) {
340
+ const s = didYouMean(op.output, Object.keys(g.outputs ?? {}));
341
+ throw new Error(`${where}: no output "${op.output}"${s.length ? ` — did you mean ${s.join(', ')}?` : ''} (publish it in "outputs" first)`);
342
+ }
343
+ if (op.export === null)
344
+ delete out.export;
345
+ else
346
+ out.export = op.export;
347
+ break;
348
+ }
315
349
  case 'insert': {
316
350
  const after = need(op.after, `${where} after`);
317
351
  if (!op.node || !NAME_RE.test(op.node) || g.nodes[op.node])
@@ -327,7 +361,7 @@ export class Workspace {
327
361
  notes.push(`inserted "${op.node}" after "${after}"${consumers.length ? `; now feeds ${consumers.map((c) => c.node).join(', ')}` : ''}`);
328
362
  break;
329
363
  }
330
- default: throw new Error(`${where}: unknown op. Use set, add, remove, connect, rename, output, vars, bypass, replace, insert`);
364
+ default: throw new Error(`${where}: unknown op. Use set, add, remove, connect, rename, output, vars, bypass, replace, insert, export`);
331
365
  }
332
366
  }
333
367
  return { graph: g, notes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "topotopo",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "An open-source procedural geometry engine: node graphs in JSON, cooked headless and deterministic, exported to Unreal, Blender and Houdini. Built for LLM agents through MCP, and for people through the CLI.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,7 @@
38
38
  "mcp": "node src/mcp/server.ts",
39
39
  "preview": "node scripts/preview.ts --examples",
40
40
  "cook": "node src/cli.ts",
41
- "test": "node test/smoke.ts && node test/golden.ts && node test/property.ts && node test/fuzz.ts && node test/docs.ts && node test/portability.ts && node test/examples.ts && node scripts/gen-notice.ts --check && node test/io.ts && node test/import.ts && node test/interop.ts && node test/mcp.ts && node test/engine.ts && node test/viewer.ts && node test/viewer-logic.ts && node test/topology.ts && node test/deform.ts && node test/cloth.ts && node test/arrays.ts && node test/bugs.ts && node test/uv.ts && node test/polyedit.ts && node test/bevel.ts && node test/measure.ts && node test/render.ts && node test/packed.ts && node test/heightfield.ts && node test/remesh.ts && node test/pcg.ts && node test/scatterkernel.ts && node test/splines.ts && node test/shelf.ts && node test/textures.ts && node test/pool.ts && node test/kernelsloader.ts && node test/kernels.ts && node test/sdfgrid.ts && node test/remeshquad.ts && node test/erosionkernel.ts && node test/erosionsimd.ts && node test/particles.ts && node test/rbd.ts && node test/limits.ts",
41
+ "test": "node test/smoke.ts && node test/golden.ts && node test/property.ts && node test/fuzz.ts && node test/docs.ts && node test/portability.ts && node test/examples.ts && node scripts/gen-notice.ts --check && node test/io.ts && node test/import.ts && node test/interop.ts && node test/mcp.ts && node test/engine.ts && node test/viewer.ts && node test/viewer-logic.ts && node test/topology.ts && node test/deform.ts && node test/cloth.ts && node test/arrays.ts && node test/bugs.ts && node test/uv.ts && node test/polyedit.ts && node test/bevel.ts && node test/measure.ts && node test/render.ts && node test/packed.ts && node test/heightfield.ts && node test/remesh.ts && node test/pcg.ts && node test/scatterkernel.ts && node test/splines.ts && node test/shelf.ts && node test/textures.ts && node test/pool.ts && node test/kernelsloader.ts && node test/kernels.ts && node test/sdfgrid.ts && node test/remeshquad.ts && node test/erosionkernel.ts && node test/erosionsimd.ts && node test/particles.ts && node test/rbd.ts && node test/limits.ts && node test/viewer-outputs.ts",
42
42
  "test:limits": "node test/limits.ts",
43
43
  "kernels:build": "node scripts/kernels-build.ts",
44
44
  "test:kernels": "node test/scatterkernel.ts && node test/kernelsloader.ts && node test/kernels.ts && node test/sdfgrid.ts && node test/remeshquad.ts && node test/erosionkernel.ts && node test/erosionsimd.ts",
@@ -63,7 +63,7 @@
63
63
  "test:portability": "node test/portability.ts",
64
64
  "test:examples": "node test/examples.ts",
65
65
  "test:interop": "node test/interop.ts",
66
- "test:viewer": "node test/viewer.ts && node test/viewer-logic.ts",
66
+ "test:viewer": "node test/viewer.ts && node test/viewer-logic.ts && node test/viewer-outputs.ts",
67
67
  "showcase": "node test/showcase.ts",
68
68
  "export:demo": "node scripts/export-demo.ts",
69
69
  "demo:footage": "node scripts/demo-footage.ts",
@@ -99,6 +99,7 @@
99
99
  "@modelcontextprotocol/sdk": "^1.32.0",
100
100
  "d3-delaunay": "^6.0.4",
101
101
  "earcut": "^3.2.4",
102
+ "fflate": "0.8.3",
102
103
  "manifold-3d": "^3.5.4",
103
104
  "meshoptimizer": "^1.3.0",
104
105
  "opentype.js": "^2.0.0",