@invisra/printspec 0.1.2 → 0.1.4
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/browser.d.ts +7 -0
- package/dist/browser.js +7 -0
- package/dist/bundle.browser.d.ts +3 -0
- package/dist/bundle.browser.js +5 -0
- package/dist/bundle.core.d.ts +27 -0
- package/dist/bundle.core.js +81 -0
- package/dist/bundle.d.ts +2 -25
- package/dist/bundle.js +3 -80
- package/dist/forms.browser.d.ts +37 -0
- package/dist/forms.browser.js +68 -0
- package/dist/generated/schemas.generated.d.ts +2428 -0
- package/dist/generated/schemas.generated.js +3055 -0
- package/dist/schemas.browser.d.ts +3 -0
- package/dist/schemas.browser.js +5 -0
- package/dist/schemas.d.ts +1 -6
- package/dist/schemas.js +1 -54
- package/dist/schemas.node.d.ts +5 -0
- package/dist/schemas.node.js +29 -0
- package/dist/schemas.shared.d.ts +3 -0
- package/dist/schemas.shared.js +34 -0
- package/dist/validate.browser.d.ts +9 -0
- package/dist/validate.browser.js +36 -0
- package/dist/validate.js +1 -10
- package/package.json +8 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { validatePrintSpec, validatePartFamilySpec, validateComposablePartSpec, validateProjectSpec, } from './validate.browser.js';
|
|
2
|
+
export { normalizePrintSpec } from './normalize.js';
|
|
3
|
+
export { extractBom, bomToMarkdown, bomToCsv, bomToSupplierOrderList, } from './bom.js';
|
|
4
|
+
export { generateOpenScad } from './generators/openscad.js';
|
|
5
|
+
export { generateCadQuery } from './generators/cadquery.js';
|
|
6
|
+
export { createBundle } from './bundle.browser.js';
|
|
7
|
+
export { getPartFamilyFormMetadata, listPartFamilies } from './forms.browser.js';
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { validatePrintSpec, validatePartFamilySpec, validateComposablePartSpec, validateProjectSpec, } from './validate.browser.js';
|
|
2
|
+
export { normalizePrintSpec } from './normalize.js';
|
|
3
|
+
export { extractBom, bomToMarkdown, bomToCsv, bomToSupplierOrderList, } from './bom.js';
|
|
4
|
+
export { generateOpenScad } from './generators/openscad.js';
|
|
5
|
+
export { generateCadQuery } from './generators/cadquery.js';
|
|
6
|
+
export { createBundle } from './bundle.browser.js';
|
|
7
|
+
export { getPartFamilyFormMetadata, listPartFamilies } from './forms.browser.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ValidationResult } from './types.js';
|
|
2
|
+
export type BundleFile = {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
mediaType: string;
|
|
6
|
+
};
|
|
7
|
+
export type BundleWarning = {
|
|
8
|
+
path?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
export type BundleResult = {
|
|
12
|
+
supported: boolean;
|
|
13
|
+
files: BundleFile[];
|
|
14
|
+
warnings: BundleWarning[];
|
|
15
|
+
message?: string;
|
|
16
|
+
};
|
|
17
|
+
export type BundleOptions = {
|
|
18
|
+
includeOpenScad?: boolean;
|
|
19
|
+
includeCadQuery?: boolean;
|
|
20
|
+
includeBom?: boolean;
|
|
21
|
+
includePartCad?: boolean;
|
|
22
|
+
prettyJson?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type WriteBundleOptions = {
|
|
25
|
+
overwrite?: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function createBundleWithValidator(validatePrintSpec: (spec: unknown) => ValidationResult, input: unknown, options?: BundleOptions): BundleResult;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { normalizePrintSpec } from './normalize.js';
|
|
2
|
+
import { generateOpenScad } from './generators/openscad.js';
|
|
3
|
+
import { generateCadQuery } from './generators/cadquery.js';
|
|
4
|
+
import { extractBom, bomToMarkdown, bomToCsv, bomToSupplierOrderList } from './bom.js';
|
|
5
|
+
const VERSION = '0.1.0';
|
|
6
|
+
const jsonMedia = 'application/json';
|
|
7
|
+
function j(spec, pretty) { return JSON.stringify(spec, null, pretty ? 2 : 0) + '\n'; }
|
|
8
|
+
function add(files, path, content, mediaType, role) { files.push({ path, content, mediaType, role }); }
|
|
9
|
+
function warn(warnings, message, path) { warnings.push(path ? { path, message } : { message }); }
|
|
10
|
+
function safePartId(id) { return id.replace(/[^A-Za-z0-9._-]/g, '-'); }
|
|
11
|
+
function bomFiles(files, base, bom) { if (!bom.length)
|
|
12
|
+
return; add(files, `${base}bom.md`, bomToMarkdown(bom) + '\n', 'text/markdown', 'bom-markdown'); add(files, `${base}bom.csv`, bomToCsv(bom) + '\n', 'text/csv', 'bom-csv'); add(files, `${base}supplier-order-list.txt`, bomToSupplierOrderList(bom) + '\n', 'text/plain', 'supplier-order-list'); }
|
|
13
|
+
function cadForPart(spec, base, files, warnings, opts) { if (opts.includeOpenScad) {
|
|
14
|
+
const g = generateOpenScad(spec);
|
|
15
|
+
if (g.supported) {
|
|
16
|
+
add(files, `${base}cad/model.scad`, g.code, 'text/plain', 'openscad-source');
|
|
17
|
+
for (const m of g.warnings ?? [])
|
|
18
|
+
warn(warnings, m, `${base}cad/model.scad`);
|
|
19
|
+
}
|
|
20
|
+
else
|
|
21
|
+
warn(warnings, g.message ?? 'OpenSCAD generator unsupported', `${base}cad/model.scad`);
|
|
22
|
+
} if (opts.includeCadQuery) {
|
|
23
|
+
const g = generateCadQuery(spec);
|
|
24
|
+
if (g.supported) {
|
|
25
|
+
add(files, `${base}cad/model.py`, g.code, 'text/x-python', 'cadquery-source');
|
|
26
|
+
for (const m of g.warnings ?? [])
|
|
27
|
+
warn(warnings, m, `${base}cad/model.py`);
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
warn(warnings, g.message ?? 'CadQuery generator unsupported', `${base}cad/model.py`);
|
|
31
|
+
} }
|
|
32
|
+
function readme(kind, spec, files, warnings, bomCount) { const lines = []; lines.push(`# ${kind === 'project' ? (spec.project?.label ?? 'printspec project') : (spec.part?.label ?? 'printspec part')}`, ''); if (kind === 'part')
|
|
33
|
+
lines.push(`Part type: ${spec.part?.type ?? 'unknown'}`);
|
|
34
|
+
else {
|
|
35
|
+
if (spec.project?.description)
|
|
36
|
+
lines.push(spec.project.description, '');
|
|
37
|
+
lines.push('## Parts', ...(spec.project?.parts ?? []).map((p) => `- ${p.id}: ${p.label ?? p.id} (quantity ${p.quantity ?? 1})`), '');
|
|
38
|
+
} lines.push(`printspec version: ${spec.printspecVersion ?? VERSION}`, '', '## Generated files', ...files.map(f => `- ${f.path} (${f.role})`), ''); lines.push('## BOM', bomCount ? `${bomCount} BOM item(s) included.` : 'No hardware/BOM items were found.', ''); lines.push('## Warnings', ...(warnings.length ? warnings.map(w => `- ${w.path ? `${w.path}: ` : ''}${w.message}`) : ['- None']), ''); lines.push('Generated CAD source should be reviewed before manufacturing.'); return lines.join('\n') + '\n'; }
|
|
39
|
+
function partcad(spec, files) { const cq = files.filter(f => f.role === 'cadquery-source').sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); const name = spec.project?.partcad?.packageName ?? 'printspec-project'; const lines = ['package:', ` name: ${name}`, ` version: ${spec.project?.partcad?.packageVersion ?? VERSION}`, ' description: Experimental compatibility stub generated from printspec.', '', 'parts:']; for (const f of cq) {
|
|
40
|
+
const m = f.path.match(/^parts\/([^/]+)\//);
|
|
41
|
+
lines.push(` - name: ${m?.[1] ?? 'model'}`, ' type: cadquery', ` path: ${f.path}`);
|
|
42
|
+
} if (!cq.length)
|
|
43
|
+
lines.push(' []'); return lines.join('\n') + '\n'; }
|
|
44
|
+
function manifest(kind, files, warnings, spec) { const entries = files.map(f => ({ path: f.path, mediaType: f.mediaType, role: f.role })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); entries.push({ path: 'bundle-manifest.json', mediaType: jsonMedia, role: 'bundle-manifest' }); entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); return j({ bundleVersion: VERSION, createdBy: 'printspec', printspecVersion: spec.printspecVersion ?? VERSION, kind, files: entries, warnings }, true); }
|
|
45
|
+
export function createBundleWithValidator(validatePrintSpec, input, options = {}) {
|
|
46
|
+
const r = validatePrintSpec(input);
|
|
47
|
+
if (!r.valid)
|
|
48
|
+
return { supported: false, files: [], warnings: [], message: `Validation failed: ${r.errors.slice(0, 3).join('; ')}` };
|
|
49
|
+
const spec = normalizePrintSpec(input);
|
|
50
|
+
const opts = { includeOpenScad: options.includeOpenScad !== false, includeCadQuery: options.includeCadQuery !== false, includeBom: options.includeBom !== false, includePartCad: options.includePartCad === true, prettyJson: options.prettyJson !== false };
|
|
51
|
+
const files = [];
|
|
52
|
+
const warnings = [];
|
|
53
|
+
const kind = spec.project ? 'project' : 'part';
|
|
54
|
+
add(files, 'printspec.json', j(spec, opts.prettyJson), jsonMedia, 'source-spec');
|
|
55
|
+
if (kind === 'part') {
|
|
56
|
+
cadForPart(spec, '', files, warnings, opts);
|
|
57
|
+
if (opts.includeBom)
|
|
58
|
+
bomFiles(files, 'bom/', extractBom(spec));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
for (const p of spec.project.parts ?? []) {
|
|
62
|
+
const id = safePartId(p.id);
|
|
63
|
+
if (p.spec) {
|
|
64
|
+
const ps = normalizePrintSpec(p.spec);
|
|
65
|
+
add(files, `parts/${id}/printspec.json`, j(ps, opts.prettyJson), jsonMedia, 'part-source-spec');
|
|
66
|
+
cadForPart(ps, `parts/${id}/`, files, warnings, opts);
|
|
67
|
+
}
|
|
68
|
+
else if (p.specPath)
|
|
69
|
+
warn(warnings, `External specPath references are not bundled yet: ${p.specPath}`, `parts/${id}/printspec.json`);
|
|
70
|
+
}
|
|
71
|
+
if (opts.includeBom)
|
|
72
|
+
bomFiles(files, 'bom/', extractBom(spec));
|
|
73
|
+
if (opts.includePartCad)
|
|
74
|
+
add(files, 'partcad.yaml', partcad(spec, files), 'text/yaml', 'partcad-stub');
|
|
75
|
+
}
|
|
76
|
+
const bomCount = extractBom(spec).length;
|
|
77
|
+
add(files, 'README.md', readme(kind, spec, files, warnings, bomCount), 'text/markdown', 'readme');
|
|
78
|
+
add(files, 'bundle-manifest.json', manifest(kind, files, warnings, spec), jsonMedia, 'bundle-manifest');
|
|
79
|
+
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
80
|
+
return { supported: true, files: files.map(({ role, ...f }) => f), warnings };
|
|
81
|
+
}
|
package/dist/bundle.d.ts
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
export type BundleFile
|
|
2
|
-
|
|
3
|
-
content: string;
|
|
4
|
-
mediaType: string;
|
|
5
|
-
};
|
|
6
|
-
export type BundleWarning = {
|
|
7
|
-
path?: string;
|
|
8
|
-
message: string;
|
|
9
|
-
};
|
|
10
|
-
export type BundleResult = {
|
|
11
|
-
supported: boolean;
|
|
12
|
-
files: BundleFile[];
|
|
13
|
-
warnings: BundleWarning[];
|
|
14
|
-
message?: string;
|
|
15
|
-
};
|
|
16
|
-
export type BundleOptions = {
|
|
17
|
-
includeOpenScad?: boolean;
|
|
18
|
-
includeCadQuery?: boolean;
|
|
19
|
-
includeBom?: boolean;
|
|
20
|
-
includePartCad?: boolean;
|
|
21
|
-
prettyJson?: boolean;
|
|
22
|
-
};
|
|
23
|
-
export type WriteBundleOptions = {
|
|
24
|
-
overwrite?: boolean;
|
|
25
|
-
};
|
|
1
|
+
export type { BundleFile, BundleWarning, BundleResult, BundleOptions, WriteBundleOptions } from './bundle.core.js';
|
|
2
|
+
import type { BundleResult, BundleOptions, WriteBundleOptions } from './bundle.core.js';
|
|
26
3
|
export declare function createBundle(input: unknown, options?: BundleOptions): BundleResult;
|
|
27
4
|
export declare function writeBundleToDirectory(bundle: BundleResult, outputDir: string, options?: WriteBundleOptions): void;
|
package/dist/bundle.js
CHANGED
|
@@ -1,88 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { validatePrintSpec } from './validate.js';
|
|
4
|
-
import {
|
|
5
|
-
import { generateOpenScad } from './generators/openscad.js';
|
|
6
|
-
import { generateCadQuery } from './generators/cadquery.js';
|
|
7
|
-
import { extractBom, bomToMarkdown, bomToCsv, bomToSupplierOrderList } from './bom.js';
|
|
8
|
-
const VERSION = '0.1.0';
|
|
9
|
-
const jsonMedia = 'application/json';
|
|
10
|
-
function j(spec, pretty) { return JSON.stringify(spec, null, pretty ? 2 : 0) + '\n'; }
|
|
11
|
-
function add(files, path, content, mediaType, role) { files.push({ path, content, mediaType, role }); }
|
|
12
|
-
function warn(warnings, message, path) { warnings.push(path ? { path, message } : { message }); }
|
|
13
|
-
function safePartId(id) { return id.replace(/[^A-Za-z0-9._-]/g, '-'); }
|
|
14
|
-
function bomFiles(files, base, bom) { if (!bom.length)
|
|
15
|
-
return; add(files, `${base}bom.md`, bomToMarkdown(bom) + '\n', 'text/markdown', 'bom-markdown'); add(files, `${base}bom.csv`, bomToCsv(bom) + '\n', 'text/csv', 'bom-csv'); add(files, `${base}supplier-order-list.txt`, bomToSupplierOrderList(bom) + '\n', 'text/plain', 'supplier-order-list'); }
|
|
16
|
-
function cadForPart(spec, base, files, warnings, opts) { if (opts.includeOpenScad) {
|
|
17
|
-
const g = generateOpenScad(spec);
|
|
18
|
-
if (g.supported) {
|
|
19
|
-
add(files, `${base}cad/model.scad`, g.code, 'text/plain', 'openscad-source');
|
|
20
|
-
for (const m of g.warnings ?? [])
|
|
21
|
-
warn(warnings, m, `${base}cad/model.scad`);
|
|
22
|
-
}
|
|
23
|
-
else
|
|
24
|
-
warn(warnings, g.message ?? 'OpenSCAD generator unsupported', `${base}cad/model.scad`);
|
|
25
|
-
} if (opts.includeCadQuery) {
|
|
26
|
-
const g = generateCadQuery(spec);
|
|
27
|
-
if (g.supported) {
|
|
28
|
-
add(files, `${base}cad/model.py`, g.code, 'text/x-python', 'cadquery-source');
|
|
29
|
-
for (const m of g.warnings ?? [])
|
|
30
|
-
warn(warnings, m, `${base}cad/model.py`);
|
|
31
|
-
}
|
|
32
|
-
else
|
|
33
|
-
warn(warnings, g.message ?? 'CadQuery generator unsupported', `${base}cad/model.py`);
|
|
34
|
-
} }
|
|
35
|
-
function readme(kind, spec, files, warnings, bomCount) { const lines = []; lines.push(`# ${kind === 'project' ? (spec.project?.label ?? 'printspec project') : (spec.part?.label ?? 'printspec part')}`, ''); if (kind === 'part')
|
|
36
|
-
lines.push(`Part type: ${spec.part?.type ?? 'unknown'}`);
|
|
37
|
-
else {
|
|
38
|
-
if (spec.project?.description)
|
|
39
|
-
lines.push(spec.project.description, '');
|
|
40
|
-
lines.push('## Parts', ...(spec.project?.parts ?? []).map((p) => `- ${p.id}: ${p.label ?? p.id} (quantity ${p.quantity ?? 1})`), '');
|
|
41
|
-
} lines.push(`printspec version: ${spec.printspecVersion ?? VERSION}`, '', '## Generated files', ...files.map(f => `- ${f.path} (${f.role})`), ''); lines.push('## BOM', bomCount ? `${bomCount} BOM item(s) included.` : 'No hardware/BOM items were found.', ''); lines.push('## Warnings', ...(warnings.length ? warnings.map(w => `- ${w.path ? `${w.path}: ` : ''}${w.message}`) : ['- None']), ''); lines.push('Generated CAD source should be reviewed before manufacturing.'); return lines.join('\n') + '\n'; }
|
|
42
|
-
function partcad(spec, files) { const cq = files.filter(f => f.role === 'cadquery-source').sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); const name = spec.project?.partcad?.packageName ?? 'printspec-project'; const lines = ['package:', ` name: ${name}`, ` version: ${spec.project?.partcad?.packageVersion ?? VERSION}`, ' description: Experimental compatibility stub generated from printspec.', '', 'parts:']; for (const f of cq) {
|
|
43
|
-
const m = f.path.match(/^parts\/([^/]+)\//);
|
|
44
|
-
lines.push(` - name: ${m?.[1] ?? 'model'}`, ' type: cadquery', ` path: ${f.path}`);
|
|
45
|
-
} if (!cq.length)
|
|
46
|
-
lines.push(' []'); return lines.join('\n') + '\n'; }
|
|
47
|
-
function manifest(kind, files, warnings, spec) { const entries = files.map(f => ({ path: f.path, mediaType: f.mediaType, role: f.role })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); entries.push({ path: 'bundle-manifest.json', mediaType: jsonMedia, role: 'bundle-manifest' }); entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0); return j({ bundleVersion: VERSION, createdBy: 'printspec', printspecVersion: spec.printspecVersion ?? VERSION, kind, files: entries, warnings }, true); }
|
|
4
|
+
import { createBundleWithValidator } from './bundle.core.js';
|
|
48
5
|
export function createBundle(input, options = {}) {
|
|
49
|
-
|
|
50
|
-
if (!r.valid)
|
|
51
|
-
return { supported: false, files: [], warnings: [], message: `Validation failed: ${r.errors.slice(0, 3).join('; ')}` };
|
|
52
|
-
const spec = normalizePrintSpec(input);
|
|
53
|
-
const opts = { includeOpenScad: options.includeOpenScad !== false, includeCadQuery: options.includeCadQuery !== false, includeBom: options.includeBom !== false, includePartCad: options.includePartCad === true, prettyJson: options.prettyJson !== false };
|
|
54
|
-
const files = [];
|
|
55
|
-
const warnings = [];
|
|
56
|
-
const kind = spec.project ? 'project' : 'part';
|
|
57
|
-
add(files, 'printspec.json', j(spec, opts.prettyJson), jsonMedia, 'source-spec');
|
|
58
|
-
if (kind === 'part') {
|
|
59
|
-
cadForPart(spec, '', files, warnings, opts);
|
|
60
|
-
if (opts.includeBom)
|
|
61
|
-
bomFiles(files, 'bom/', extractBom(spec));
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
for (const p of spec.project.parts ?? []) {
|
|
65
|
-
const id = safePartId(p.id);
|
|
66
|
-
if (p.spec) {
|
|
67
|
-
const ps = normalizePrintSpec(p.spec);
|
|
68
|
-
add(files, `parts/${id}/printspec.json`, j(ps, opts.prettyJson), jsonMedia, 'part-source-spec');
|
|
69
|
-
cadForPart(ps, `parts/${id}/`, files, warnings, opts);
|
|
70
|
-
}
|
|
71
|
-
else if (p.specPath)
|
|
72
|
-
warn(warnings, `External specPath references are not bundled yet: ${p.specPath}`, `parts/${id}/printspec.json`);
|
|
73
|
-
}
|
|
74
|
-
if (opts.includeBom)
|
|
75
|
-
bomFiles(files, 'bom/', extractBom(spec));
|
|
76
|
-
if (opts.includePartCad)
|
|
77
|
-
add(files, 'partcad.yaml', partcad(spec, files), 'text/yaml', 'partcad-stub');
|
|
78
|
-
}
|
|
79
|
-
const bomCount = extractBom(spec).length;
|
|
80
|
-
add(files, 'README.md', readme(kind, spec, files, warnings, bomCount), 'text/markdown', 'readme');
|
|
81
|
-
add(files, 'bundle-manifest.json', manifest(kind, files, warnings, spec), jsonMedia, 'bundle-manifest');
|
|
82
|
-
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
83
|
-
return { supported: true, files: files.map(({ role, ...f }) => f), warnings };
|
|
6
|
+
return createBundleWithValidator(validatePrintSpec, input, options);
|
|
84
7
|
}
|
|
85
|
-
function assertSafe(p) { if (!p || path.isAbsolute(p) || p.split(/[
|
|
8
|
+
function assertSafe(p) { if (!p || path.isAbsolute(p) || p.split(/[\/]+/).includes('..'))
|
|
86
9
|
throw new Error(`Unsafe bundle path: ${p}`); }
|
|
87
10
|
export function writeBundleToDirectory(bundle, outputDir, options = {}) { if (!bundle.supported)
|
|
88
11
|
throw new Error(bundle.message ?? 'Unsupported bundle'); if (fs.existsSync(outputDir) && !options.overwrite)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type FormGroup = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
fields: string[];
|
|
5
|
+
};
|
|
6
|
+
export type FormField = {
|
|
7
|
+
name: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
required: boolean;
|
|
11
|
+
type: string;
|
|
12
|
+
control?: string;
|
|
13
|
+
unit?: string;
|
|
14
|
+
step?: number;
|
|
15
|
+
minimum?: number;
|
|
16
|
+
exclusiveMinimum?: number;
|
|
17
|
+
maximum?: number;
|
|
18
|
+
default?: unknown;
|
|
19
|
+
examples?: unknown[];
|
|
20
|
+
priority?: 'primary' | 'advanced';
|
|
21
|
+
};
|
|
22
|
+
export type FormMetadata = {
|
|
23
|
+
partType: string;
|
|
24
|
+
title: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
fields: FormField[];
|
|
27
|
+
groups: FormGroup[];
|
|
28
|
+
};
|
|
29
|
+
export type PartFamilySummary = {
|
|
30
|
+
type: string;
|
|
31
|
+
title: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
schemaFilename: string;
|
|
34
|
+
generatorSupported?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare function listPartFamilies(): PartFamilySummary[];
|
|
37
|
+
export declare function getPartFamilyFormMetadata(partType: string): FormMetadata;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { schemas } from './schemas.browser.js';
|
|
2
|
+
const generatorSupported = new Set(['rounded_rectangular_plate', 'spacer_block', 'round_spacer', 'electronics_standoff']);
|
|
3
|
+
function partFamilyEntries() {
|
|
4
|
+
return Object.entries(schemas)
|
|
5
|
+
.map(([filename, schema]) => ({ filename, schema, type: schema?.properties?.type?.const }))
|
|
6
|
+
.filter((entry) => typeof entry.type === 'string' && !!entry.schema?.properties?.parameters)
|
|
7
|
+
.sort((a, b) => a.type.localeCompare(b.type));
|
|
8
|
+
}
|
|
9
|
+
function schemaType(field) {
|
|
10
|
+
if (typeof field?.type === 'string')
|
|
11
|
+
return field.type;
|
|
12
|
+
if (field?.$ref)
|
|
13
|
+
return 'object';
|
|
14
|
+
if (Array.isArray(field?.type))
|
|
15
|
+
return field.type.join('|');
|
|
16
|
+
return 'unknown';
|
|
17
|
+
}
|
|
18
|
+
export function listPartFamilies() {
|
|
19
|
+
return partFamilyEntries().map(({ type, filename, schema }) => ({
|
|
20
|
+
type,
|
|
21
|
+
title: schema.title ?? type,
|
|
22
|
+
description: schema.description,
|
|
23
|
+
schemaFilename: filename,
|
|
24
|
+
generatorSupported: generatorSupported.has(type),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
export function getPartFamilyFormMetadata(partType) {
|
|
28
|
+
const entry = partFamilyEntries().find((candidate) => candidate.type === partType);
|
|
29
|
+
if (!entry)
|
|
30
|
+
throw new Error(`Unsupported printspec part family: ${partType}`);
|
|
31
|
+
const parameters = entry.schema?.properties?.parameters;
|
|
32
|
+
const properties = parameters?.properties ?? {};
|
|
33
|
+
const required = new Set(parameters?.required ?? []);
|
|
34
|
+
const metadata = parameters?.['x-printspec-ui'] ?? {};
|
|
35
|
+
const known = new Set(Object.keys(properties));
|
|
36
|
+
const ordered = Array.isArray(metadata.order) ? metadata.order.filter((name) => known.has(name)) : Object.keys(properties).sort();
|
|
37
|
+
for (const name of Object.keys(properties).sort())
|
|
38
|
+
if (!ordered.includes(name))
|
|
39
|
+
ordered.push(name);
|
|
40
|
+
const groups = Array.isArray(metadata.groups) && metadata.groups.length
|
|
41
|
+
? metadata.groups.map((group) => ({ id: String(group.id), title: String(group.title ?? group.id), fields: (group.fields ?? []).filter((name) => known.has(name)) }))
|
|
42
|
+
: [{ id: 'parameters', title: parameters?.title ?? 'Parameters', fields: ordered }];
|
|
43
|
+
return {
|
|
44
|
+
partType,
|
|
45
|
+
title: entry.schema.title ?? partType,
|
|
46
|
+
description: entry.schema.description,
|
|
47
|
+
fields: ordered.map((name) => {
|
|
48
|
+
const field = properties[name] ?? {};
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
title: field.title ?? name,
|
|
52
|
+
description: field.description,
|
|
53
|
+
required: required.has(name),
|
|
54
|
+
type: schemaType(field),
|
|
55
|
+
control: field['x-printspec-control'],
|
|
56
|
+
unit: field['x-printspec-unit'],
|
|
57
|
+
step: field['x-printspec-step'],
|
|
58
|
+
minimum: field.minimum,
|
|
59
|
+
exclusiveMinimum: field.exclusiveMinimum,
|
|
60
|
+
maximum: field.maximum,
|
|
61
|
+
default: field.default,
|
|
62
|
+
examples: field.examples,
|
|
63
|
+
priority: field['x-printspec-priority'],
|
|
64
|
+
};
|
|
65
|
+
}),
|
|
66
|
+
groups,
|
|
67
|
+
};
|
|
68
|
+
}
|