@es3d/es3d 1.0.1 → 1.0.2

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/README.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # ES3D
2
2
 
3
+ <p align="center">
4
+ <a href="https://www.jsdelivr.com/package/npm/@es3d/es3d"><img src="https://img.shields.io/jsdelivr/npm/hm/@es3d/es3d?label=jsDelivr" alt="jsDelivr"></a>
5
+ <a href="https://www.npmjs.com/package/@es3d/es3d"><img src="https://img.shields.io/npm/v/@es3d/es3d" alt="npm version"></a>
6
+ <a href="https://www.npmjs.com/package/@es3d/es3d"><img src="https://img.shields.io/npm/dm/@es3d/es3d" alt="npm downloads"></a>
7
+ <a href="https://github.com/bitilia/es3d/stargazers"><img src="https://img.shields.io/github/stars/bitilia/es3d" alt="GitHub stars"></a>
8
+ <a href="https://github.com/bitilia/es3d/blob/main/LICENSE"><img src="https://img.shields.io/github/license/bitilia/es3d" alt="License"></a>
9
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/@es3d/es3d" alt="Node.js"></a>
10
+ </p>
11
+
3
12
  **A file format for 3D scenes you can write by hand, the way people write SVG.** The file describes objects, not triangles: a rounded box, an outline extruded into a solid, two thousand trees placed by a rule. The viewer builds the triangles, at the detail the screen needs.
4
13
 
5
14
  This package is the reference implementation:
@@ -59,12 +68,12 @@ const { text } = await importFiles([{ name: 'logo.svg', bytes }]); // SVG, OBJ,
59
68
  const { files } = exportFormat(text, 'glb'); // ES3D → glTF, OBJ, STL, STEP
60
69
  ```
61
70
 
62
- Every error has a line, a column, a code and, where it can, a "did you mean" suggestion.
71
+ Every error has a line, a column, a code and, where it can, a "did you mean" suggestion. Repeated warnings that share a code, an element name and a message are printed once, with a count. `validate --json` still lists every warning.
63
72
 
64
73
  ## The command
65
74
 
66
75
  ```bash
67
- npx @es3d/es3d validate scene.es3d # every diagnostic; exit code 1 on errors
76
+ npx @es3d/es3d validate scene.es3d # diagnostics, repeated warnings grouped; exit code 1 on errors
68
77
  npx @es3d/es3d compact scene.es3d # convert to the compact syntax (also: markup, json, fmt, minify)
69
78
  npx @es3d/es3d glb scene.es3d -o scene.glb # export (also: obj, stl, step)
70
79
  npx @es3d/es3d import logo.svg -o logo.es3d # SVG, OBJ, STL, STEP or glTF to ES3D
package/docs/SPEC.md CHANGED
@@ -403,11 +403,20 @@ The title names the phase (`Parse`, `Reference`, `Geometry`, `Render`, `File`) a
403
403
 
404
404
  Unknown names get a suggestion when a known name is within a small edit distance or shares a prefix (`raduis` → `radius`, `spotlight` → `spot`, `sphear` → `sphere`).
405
405
 
406
+ The reference reader's human-readable report (`formatDiagnostics`, and the text from `validate`) shows a repeated warning once. Warnings group when they share a code, the element name written in the message (its first `<name>`), and the same message. The report keeps the first location and then says how many times that warning occurred:
407
+
408
+ ```
409
+ Reported 906 times. Showing the first.
410
+ ```
411
+
412
+ Every warning remains its own entry in the diagnostic list. `validate --json` prints that list. Errors are listed one by one.
413
+
406
414
  ## 16. Extensibility and versioning
407
415
 
408
416
  - Elements whose names start with `x-` (and their subtrees) are vendor extensions: readers that do not understand them skip them with `W_EXTENSION`. Unknown elements without the prefix are errors, so typos are never mistaken for extensions.
409
417
  - Unknown attributes are always errors. (A future version may reserve an `x-` attribute prefix as well.)
410
418
  - `version="1.x"` documents must remain readable by version-1 readers; incompatible changes require version 2, which version-1 readers reject with `E_VERSION`.
419
+ - A processor that rewrites a document keeps `x-` elements, and keeps any element whose id is named by a token in an `x-` attribute. The reference optimizer treats a whitespace-separated token (an optional leading `#` is ignored) as a reference when that id exists, and updates the token when it merges a duplicate material.
411
420
 
412
421
  ## 17. File names and media types
413
422
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@es3d/es3d",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "ES3D (Extensible Scalable 3D): a declarative, SVG-inspired 3D scene format with a WebGPU/WebGL2 reference renderer, editor and converter.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -4,7 +4,7 @@
4
4
  // npm run es3d -- validate scene.es3d
5
5
  // npm run es3d -- fmt scene.es3d -o pretty.es3d
6
6
  // npm run es3d -- stats scene.es3d
7
- // npm run es3d -- glb scene.es3d --instancing -o scene.glb
7
+ // npm run es3d -- glb scene.es3d -o scene.glb
8
8
  // npm run es3d -- import drawing.svg -o drawing.es3d
9
9
  //
10
10
  // Run without arguments for the full command list. Input "-" reads stdin;
@@ -13,11 +13,10 @@
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import zlib from 'node:zlib';
16
- import { parse, detectSyntax } from '../src/format/parse.js';
16
+ import { parse, detectSyntax, formatDiagnostics } from '../src/format/parse.js';
17
17
  import { serialize } from '../src/format/serializer.js';
18
18
  import { compile } from '../src/pipeline.js';
19
19
  import { exportOBJ } from '../src/io/obj.js';
20
- import { exportGLB } from '../src/io/gltf.js';
21
20
  import { exportSTL } from '../src/io/stl.js';
22
21
  import { exportSTEP } from '../src/io/step.js';
23
22
  import { detectKind, importFiles } from '../src/io/convert.js';
@@ -27,7 +26,8 @@ import { optimize, expandProcedural } from '../src/optimize/optimizer.js';
27
26
  const HELP = `es3d <command> [options] <input>
28
27
 
29
28
  Commands
30
- validate parse, validate and build the scene; print every diagnostic (exit 1 on errors)
29
+ validate parse, validate and build the scene; print diagnostics (exit 1 on errors).
30
+ Repeated warnings are grouped; --json lists every one
31
31
  fmt pretty-print, keeping the syntax (markup or compact)
32
32
  minify minified markup (short attribute aliases, no comments)
33
33
  compact convert to the compact syntax (--min for one line)
@@ -36,7 +36,7 @@ Commands
36
36
  optimize run the optimizer and print its report to stderr
37
37
  expand replace <repeat>/<scatter> with explicit elements
38
38
  obj export Wavefront OBJ (+ .mtl next to -o) (--lod <0-3>)
39
- glb export glTF binary (--lod <0-3>, --instancing, --quantize 16)
39
+ glb packed glTF binary (instances, LODs, lights, cameras, animation)
40
40
  stl export STL for 3D printing, Z-up (--lod <0-3>, --ascii)
41
41
  step export tessellated STEP (AP242), Z-up (--lod <0-3>)
42
42
  import convert SVG, OBJ (+MTL), STL, STEP or glTF (.gltf + .bin, .glb) to ES3D:
@@ -48,9 +48,9 @@ Commands
48
48
  Options
49
49
  -o, --out <file> output file (default: stdout for text)
50
50
  --precision <n> round numbers to n decimals when writing text
51
- --lod <n> level of detail for mesh exports (default 0 = finest)
52
- --instancing GLB: use EXT_mesh_gpu_instancing
53
- --quantize GLB: KHR_mesh_quantization (int16 positions, int8 normals)
51
+ --lod <n> level of detail for obj, stl and step (default 0 = finest)
52
+ --instancing glb always writes EXT_mesh_gpu_instancing
53
+ --quantize glb always writes KHR_mesh_quantization
54
54
  --min / --pretty force minified / pretty output
55
55
  --json validate/stats: machine readable output
56
56
  --ascii STL: write ASCII instead of binary
@@ -132,9 +132,7 @@ function write(opts, data) {
132
132
  }
133
133
 
134
134
  function report(doc) {
135
- const out = [];
136
- for (const d of doc.diagnostics) out.push(d.format(doc.lineIndex));
137
- return out.join('\n\n');
135
+ return formatDiagnostics(doc);
138
136
  }
139
137
 
140
138
  /** Parse and validate, or exit with the diagnostics. */
@@ -144,7 +142,8 @@ function parsed(input) {
144
142
  process.stderr.write(report(doc) + '\n');
145
143
  process.exit(1);
146
144
  }
147
- for (const w of doc.warnings || []) process.stderr.write(w.format(doc.lineIndex) + '\n\n');
145
+ const warnings = formatDiagnostics(doc.warnings || [], doc.lineIndex);
146
+ if (warnings) process.stderr.write(warnings + '\n\n');
148
147
  return doc;
149
148
  }
150
149
 
@@ -152,7 +151,7 @@ function compiled(input) {
152
151
  const r = compile(input);
153
152
  const errors = r.diagnostics.filter((d) => d.severity === 'error');
154
153
  if (errors.length) {
155
- process.stderr.write(r.diagnostics.map((d) => d.format(r.doc.lineIndex)).join('\n\n') + '\n');
154
+ process.stderr.write(formatDiagnostics(r) + '\n');
156
155
  process.exit(1);
157
156
  }
158
157
  return r;
@@ -264,7 +263,8 @@ async function main() {
264
263
  const warnings = r.diagnostics.length - errors;
265
264
  if (opts.json) console.log(JSON.stringify({ ok: errors === 0, diagnostics: r.diagnostics.map((d) => d.toJSON()) }, null, 2));
266
265
  else {
267
- if (r.diagnostics.length) process.stderr.write(r.diagnostics.map((d) => d.format(r.doc.lineIndex)).join('\n\n') + '\n\n');
266
+ const text = formatDiagnostics(r);
267
+ if (text) process.stderr.write(text + '\n\n');
268
268
  console.log(`${opts.input}: ${errors} error${errors === 1 ? '' : 's'}, ${warnings} warning${warnings === 1 ? '' : 's'}${errors ? '' : `, ${r.scene.count.toLocaleString('en-US')} instances`}`);
269
269
  }
270
270
  process.exit(errors ? 1 : 0);
@@ -312,8 +312,21 @@ async function main() {
312
312
  break;
313
313
  }
314
314
  case 'glb': {
315
- const r = compiled(es3dInput(bytes));
316
- write(opts, exportGLB(r.scene, (e, l) => r.cache.mesh(e, l), { lod: opts.lod, instancing: opts.instancing, quantize: opts.quantize > 0 }));
315
+ let exportPackedGLB;
316
+ try {
317
+ ({ exportPackedGLB } = await import('../src/io/gltf-pack.js'));
318
+ } catch (e) {
319
+ fail(`glb could not load the packer (${e.message})`, 1);
320
+ }
321
+ const baseDir = opts.input === '-' ? '' : path.dirname(path.resolve(opts.input));
322
+ const sourceName = opts.input === '-' ? 'stdin' : `${path.basename(path.dirname(opts.input))}/${path.basename(opts.input)}`;
323
+ let file;
324
+ try {
325
+ file = await exportPackedGLB(es3dInput(bytes), { baseDir, sourceName });
326
+ } catch (e) {
327
+ fail(e.message, 1);
328
+ }
329
+ write(opts, file);
317
330
  break;
318
331
  }
319
332
  case 'stl': {
@@ -86,7 +86,47 @@ export function parseStrict(input, opts = {}) {
86
86
  return doc;
87
87
  }
88
88
 
89
- /** Human readable report of all diagnostics. */
90
- export function formatDiagnostics(doc) {
91
- return doc.diagnostics.map((d) => d.format(doc.lineIndex)).join('\n\n');
89
+ /** Element name written in a message, if it has one (`<x-anchors>`). */
90
+ function mentionedElement(message) {
91
+ const m = /<([^<>\s]+)>/.exec(message || '');
92
+ return m ? m[1] : '';
93
+ }
94
+
95
+ /**
96
+ * Human-readable report of diagnostics.
97
+ * Repeated warnings that share a code, the element name in the message, and
98
+ * the same message are shown once, at the first location, with a count.
99
+ * Errors are listed one by one. The diagnostic objects are not changed.
100
+ * @param {object|Array} source a document, a compile result, or a diagnostic list
101
+ * @param {import('./errors.js').LineIndex} [lineIndex]
102
+ */
103
+ export function formatDiagnostics(source, lineIndex) {
104
+ if (!source) return '';
105
+ const list = Array.isArray(source) ? source : source.diagnostics || [];
106
+ const index = lineIndex || source.lineIndex || (source.doc && source.doc.lineIndex) || null;
107
+ const seen = new Map();
108
+ const items = [];
109
+ for (const d of list) {
110
+ if (!d || d.severity !== 'warning') {
111
+ items.push(d);
112
+ continue;
113
+ }
114
+ const key = `${d.code}\0${mentionedElement(d.message)}\0${d.message}`;
115
+ const prev = seen.get(key);
116
+ if (prev) {
117
+ prev.count++;
118
+ continue;
119
+ }
120
+ const slot = { d, count: 1 };
121
+ seen.set(key, slot);
122
+ items.push(slot);
123
+ }
124
+ return items
125
+ .map((item) => {
126
+ if (!item || !item.d) return item.format(index);
127
+ const text = item.d.format(index);
128
+ if (item.count < 2) return text;
129
+ return `${text}\n\nReported ${item.count} times. Showing the first.`;
130
+ })
131
+ .join('\n\n');
92
132
  }
package/src/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Public API of the ES3D reference implementation.
2
2
  //
3
- // import { compile, createRenderer, Camera } from '@es3d/es3d';
3
+ // import { compile, createRenderer, Camera, formatDiagnostics } from '@es3d/es3d';
4
4
  //
5
5
  // const result = compile(text); // parse + validate + build + flatten
6
- // if (!result.ok) console.error(result.diagnostics.map((d) => d.format()).join('\n\n'));
6
+ // if (!result.ok) console.error(formatDiagnostics(result));
7
7
  // const renderer = await createRenderer(canvas); // WebGPU, else WebGL2
8
8
  // renderer.setScene(result.scene, (entry, lod) => result.cache.mesh(entry, lod));
9
9
  // const camera = new Camera().frame(result.scene.bounds);