mcp-3d-printer-server 1.2.20 → 1.2.22
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/CHANGELOG.md +27 -0
- package/README.md +112 -9
- package/dist/blender-mcp-bridge.d.ts +21 -0
- package/dist/blender-mcp-bridge.js +3 -3
- package/dist/counterpart/contract.d.ts +395 -0
- package/dist/counterpart/contract.js +220 -0
- package/dist/counterpart/progress.d.ts +32 -0
- package/dist/counterpart/progress.js +443 -0
- package/dist/counterpart/service.d.ts +56 -0
- package/dist/counterpart/service.js +232 -0
- package/dist/fusion-mcp-bridge.d.ts +166 -0
- package/dist/fusion-mcp-bridge.js +529 -0
- package/dist/index.js +272 -8
- package/dist/printers/creality.d.ts +26 -1
- package/dist/printers/creality.js +75 -2
- package/dist/printers/duet.d.ts +16 -0
- package/dist/printers/duet.js +64 -0
- package/dist/printers/generic-base.d.ts +52 -6
- package/dist/printers/generic-base.js +134 -7
- package/dist/printers/klipper.d.ts +33 -2
- package/dist/printers/klipper.js +106 -0
- package/dist/printers/octoprint.d.ts +23 -1
- package/dist/printers/octoprint.js +81 -0
- package/dist/printers/prusa.d.ts +13 -0
- package/dist/printers/prusa.js +59 -0
- package/dist/printers/repetier.d.ts +17 -1
- package/dist/printers/repetier.js +55 -0
- package/dist/slicer/bambu-cli.d.ts +2 -0
- package/dist/slicer/bambu-cli.js +11 -0
- package/dist/slicer/filament-profiles.d.ts +25 -0
- package/dist/slicer/filament-profiles.js +249 -0
- package/dist/stl/stl-manipulator.d.ts +21 -1
- package/dist/stl/stl-manipulator.js +86 -6
- package/package.json +7 -2
- package/server.json +2 -2
- package/src/blender-mcp-bridge.ts +3 -3
- package/src/counterpart/contract.ts +326 -0
- package/src/counterpart/progress.ts +470 -0
- package/src/counterpart/service.ts +253 -0
- package/src/fusion-mcp-bridge.ts +512 -0
- package/src/index.ts +314 -10
- package/src/printers/creality.ts +84 -1
- package/src/printers/duet.ts +67 -0
- package/src/printers/generic-base.ts +172 -1
- package/src/printers/klipper.ts +118 -2
- package/src/printers/octoprint.ts +86 -1
- package/src/printers/prusa.ts +60 -0
- package/src/printers/repetier.ts +63 -1
- package/src/slicer/bambu-cli.ts +13 -0
- package/src/slicer/filament-profiles.ts +249 -0
- package/src/stl/stl-manipulator.ts +100 -6
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* counterpart/1 tools for every printer type this server drives. Generic
|
|
3
|
+
* adapters supply firmware reads; this module turns them into the shared
|
|
4
|
+
* results, detects capabilities, and fetches camera snapshots.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { isAxiosError } from "axios";
|
|
9
|
+
import { GenericPrinterImplementation } from "../printers/generic-base.js";
|
|
10
|
+
import { BambuImplementation } from "../printers/bambu.js";
|
|
11
|
+
import { COUNTERPART_CONTRACT, PRINTER_KINDS, compact, progressResult, } from "./contract.js";
|
|
12
|
+
import { mapBambu } from "./progress.js";
|
|
13
|
+
export const DEFAULT_SNAPSHOT_TIMEOUT_MS = 8000;
|
|
14
|
+
const MAX_SNAPSHOT_BYTES = 16 * 1024 * 1024;
|
|
15
|
+
const LOOPBACK = new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
|
|
16
|
+
/** No HTTP response at all (refused, timed out, unreachable): the printer is offline, not erroring. */
|
|
17
|
+
function unreachable(error) {
|
|
18
|
+
return isAxiosError(error) && !error.response;
|
|
19
|
+
}
|
|
20
|
+
function kindOf(type) {
|
|
21
|
+
const kind = type.trim().toLowerCase();
|
|
22
|
+
if (!PRINTER_KINDS.includes(kind))
|
|
23
|
+
throw new Error(`Unsupported printer type: ${type}`);
|
|
24
|
+
return kind;
|
|
25
|
+
}
|
|
26
|
+
function overrideSnapshotUrl() {
|
|
27
|
+
return process.env.PRINTER_CAMERA_SNAPSHOT_URL?.trim() || undefined;
|
|
28
|
+
}
|
|
29
|
+
export class CounterpartService {
|
|
30
|
+
constructor(implementation, http, server,
|
|
31
|
+
/** The slicer filament profile for a slot's material (undefined: the configured default). */
|
|
32
|
+
filamentProfile) {
|
|
33
|
+
this.implementation = implementation;
|
|
34
|
+
this.http = http;
|
|
35
|
+
this.server = server;
|
|
36
|
+
this.filamentProfile = filamentProfile;
|
|
37
|
+
}
|
|
38
|
+
generic(target) {
|
|
39
|
+
const implementation = this.implementation(target.type);
|
|
40
|
+
return implementation instanceof GenericPrinterImplementation ? implementation : undefined;
|
|
41
|
+
}
|
|
42
|
+
bambu(target) {
|
|
43
|
+
const implementation = this.implementation(target.type);
|
|
44
|
+
if (!(implementation instanceof BambuImplementation))
|
|
45
|
+
throw new Error(`Unsupported printer type: ${target.type}`);
|
|
46
|
+
return implementation;
|
|
47
|
+
}
|
|
48
|
+
async printProgress(target) {
|
|
49
|
+
const generic = this.generic(target);
|
|
50
|
+
if (!generic) {
|
|
51
|
+
const status = await this.bambu(target).getStatus(target.host, target.port, target.apiKey);
|
|
52
|
+
return mapBambu(status?.raw, new Date().toISOString(), { connected: status?.connected === true, model: process.env.BAMBU_MODEL });
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return await generic.observedProgress(target.host, target.port, target.apiKey);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (unreachable(error))
|
|
59
|
+
return progressResult(new Date().toISOString(), "offline", { problems: [] });
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async printerInfo(target) {
|
|
64
|
+
const kind = kindOf(target.type);
|
|
65
|
+
const generic = this.generic(target);
|
|
66
|
+
let identity = {};
|
|
67
|
+
let capabilities;
|
|
68
|
+
if (!generic) {
|
|
69
|
+
const status = await this.bambu(target).getStatus(target.host, target.port, target.apiKey).catch(() => undefined);
|
|
70
|
+
const model = typeof status?.model === "string" && status.model !== "Unknown" ? status.model : process.env.BAMBU_MODEL?.trim();
|
|
71
|
+
identity = { model: model || undefined, serial: typeof status?.serial === "string" && status.serial ? status.serial : undefined };
|
|
72
|
+
// This server has no Bambu camera, pause or resume path; bambu-printer-mcp does.
|
|
73
|
+
capabilities = {
|
|
74
|
+
progress: true, layers: true, camera: overrideSnapshotUrl() ? "http-snapshot" : "none", filaments: "none",
|
|
75
|
+
pause: false, resume: false, cancel: true, print3mf: true, gcode: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const { host, port, apiKey } = target;
|
|
80
|
+
identity = await generic.counterpartIdentity(host, port, apiKey).catch(() => ({}));
|
|
81
|
+
const [layers, pauseResume, camera] = await Promise.all([
|
|
82
|
+
generic.reportsLayers(host, port, apiKey).catch(() => false),
|
|
83
|
+
generic.supportsPauseResume(host, port, apiKey).catch(() => false),
|
|
84
|
+
this.cameraCapability(generic, target),
|
|
85
|
+
]);
|
|
86
|
+
capabilities = {
|
|
87
|
+
progress: true, layers, camera, filaments: "single",
|
|
88
|
+
pause: pauseResume, resume: pauseResume, cancel: true, print3mf: false, gcode: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return compact({
|
|
92
|
+
contract: COUNTERPART_CONTRACT,
|
|
93
|
+
server: this.server,
|
|
94
|
+
printer: { kind, host: target.host, ...identity },
|
|
95
|
+
capabilities,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** http-snapshot when the firmware names a snapshot URL, or a conventional fallback answers with a JPEG. */
|
|
99
|
+
async cameraCapability(generic, target) {
|
|
100
|
+
if (overrideSnapshotUrl())
|
|
101
|
+
return "http-snapshot";
|
|
102
|
+
const candidates = await generic.snapshotCandidates(target.host, target.port, target.apiKey).catch(() => []);
|
|
103
|
+
if (candidates.some((candidate) => candidate.configured))
|
|
104
|
+
return "http-snapshot";
|
|
105
|
+
for (const candidate of candidates) {
|
|
106
|
+
try {
|
|
107
|
+
await this.fetchJpeg(this.resolve(candidate.url, target)[0], target, 3000);
|
|
108
|
+
return "http-snapshot";
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Not a camera.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return "none";
|
|
115
|
+
}
|
|
116
|
+
async filaments(target) {
|
|
117
|
+
const generic = this.generic(target);
|
|
118
|
+
if (!generic) {
|
|
119
|
+
throw new Error("Unsupported: Bambu AMS and external-spool inventory is read by bambu-printer-mcp's get_printer_filaments.");
|
|
120
|
+
}
|
|
121
|
+
const slots = await generic.counterpartFilaments(target.host, target.port, target.apiKey);
|
|
122
|
+
const profiled = await Promise.all(slots.map(async (slot) => {
|
|
123
|
+
const profile = await this.filamentProfile?.(slot.material).catch(() => undefined);
|
|
124
|
+
return profile ? { ...slot, profile } : slot;
|
|
125
|
+
}));
|
|
126
|
+
return compact({ slots: profiled });
|
|
127
|
+
}
|
|
128
|
+
async pause(target) {
|
|
129
|
+
const generic = this.generic(target);
|
|
130
|
+
if (!generic)
|
|
131
|
+
throw new Error("Unsupported: pause_print for Bambu printers is provided by bambu-printer-mcp.");
|
|
132
|
+
return generic.pauseJob(target.host, target.port, target.apiKey);
|
|
133
|
+
}
|
|
134
|
+
async resume(target) {
|
|
135
|
+
const generic = this.generic(target);
|
|
136
|
+
if (!generic)
|
|
137
|
+
throw new Error("Unsupported: resume_print for Bambu printers is provided by bambu-printer-mcp.");
|
|
138
|
+
return generic.resumeJob(target.host, target.port, target.apiKey);
|
|
139
|
+
}
|
|
140
|
+
async cameraSnapshot(target, options = {}) {
|
|
141
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SNAPSHOT_TIMEOUT_MS;
|
|
142
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 100 || timeoutMs > 120000)
|
|
143
|
+
throw new Error("Invalid timeout_ms: expected 100 to 120000.");
|
|
144
|
+
if (options.savePath !== undefined && (!path.isAbsolute(options.savePath) || options.savePath.includes("\0"))) {
|
|
145
|
+
throw new Error("Invalid save_path: expected an absolute file path.");
|
|
146
|
+
}
|
|
147
|
+
const deadline = Date.now() + timeoutMs;
|
|
148
|
+
const override = overrideSnapshotUrl();
|
|
149
|
+
let candidates;
|
|
150
|
+
if (override) {
|
|
151
|
+
candidates = [{ url: override, source: "env-override", configured: true }];
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const generic = this.generic(target);
|
|
155
|
+
if (!generic)
|
|
156
|
+
throw new Error("Unsupported: this server has no Bambu camera path. Use bambu-printer-mcp's camera_snapshot, or set PRINTER_CAMERA_SNAPSHOT_URL.");
|
|
157
|
+
candidates = await generic.snapshotCandidates(target.host, target.port, target.apiKey);
|
|
158
|
+
if (!candidates.length) {
|
|
159
|
+
throw new Error(`Unsupported: ${kindOf(target.type)} exposes no camera snapshot URL. Set PRINTER_CAMERA_SNAPSHOT_URL to a JPEG snapshot URL for this printer.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const urls = [...new Set(candidates.flatMap((candidate) => this.resolve(candidate.url, target).map((url) => `${candidate.source}\n${url}`)))];
|
|
163
|
+
const failures = [];
|
|
164
|
+
for (const entry of urls) {
|
|
165
|
+
const [source, url] = entry.split("\n");
|
|
166
|
+
const remaining = deadline - Date.now();
|
|
167
|
+
if (remaining <= 0)
|
|
168
|
+
break;
|
|
169
|
+
try {
|
|
170
|
+
const jpeg = await this.fetchJpeg(url, target, remaining);
|
|
171
|
+
const capturedAt = new Date().toISOString();
|
|
172
|
+
let savedTo;
|
|
173
|
+
if (options.savePath) {
|
|
174
|
+
await fs.writeFile(options.savePath, jpeg);
|
|
175
|
+
savedTo = options.savePath;
|
|
176
|
+
}
|
|
177
|
+
return compact({ status: "success", format: "image/jpeg", sizeBytes: jpeg.length, base64: jpeg.toString("base64"), savedTo, capturedAt, source });
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
failures.push(`${new URL(url).origin}${new URL(url).pathname}: ${error.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw new Error(`No camera snapshot within ${timeoutMs} ms. Tried ${failures.length ? failures.join("; ") : "no URL before the deadline"}.`);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Snapshot URLs are written from the printer host's point of view: relative
|
|
187
|
+
* paths and loopback hosts mean the printer itself.
|
|
188
|
+
*/
|
|
189
|
+
resolve(raw, target) {
|
|
190
|
+
const host = target.host.includes(":") && !target.host.startsWith("[") ? `[${target.host}]` : target.host;
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = new URL(raw);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Relative to the printer host: the web front end on port 80, then the API port.
|
|
197
|
+
const pathPart = raw.startsWith("/") ? raw : `/${raw}`;
|
|
198
|
+
return [...new Set([`http://${host}${pathPart}`, `http://${host}:${target.port}${pathPart}`])];
|
|
199
|
+
}
|
|
200
|
+
if (!["http:", "https:"].includes(parsed.protocol))
|
|
201
|
+
throw new Error(`Unsupported snapshot URL scheme: ${parsed.protocol}`);
|
|
202
|
+
if (LOOPBACK.has(parsed.hostname) && !LOOPBACK.has(target.host))
|
|
203
|
+
parsed.hostname = target.host;
|
|
204
|
+
// "localhost" can resolve to ::1 first, and Node 18 has no IPv4 fallback for it: try both loopbacks.
|
|
205
|
+
if (parsed.hostname === "localhost") {
|
|
206
|
+
const v4 = new URL(parsed.toString());
|
|
207
|
+
v4.hostname = "127.0.0.1";
|
|
208
|
+
const v6 = new URL(parsed.toString());
|
|
209
|
+
v6.hostname = "[::1]";
|
|
210
|
+
return [v4.toString(), v6.toString()];
|
|
211
|
+
}
|
|
212
|
+
return [parsed.toString()];
|
|
213
|
+
}
|
|
214
|
+
async fetchJpeg(url, target, timeout) {
|
|
215
|
+
const parsed = new URL(url);
|
|
216
|
+
// The API key goes only to the printer's own API origin, never to another host.
|
|
217
|
+
const sameOrigin = parsed.origin === new URL(`http://${target.host}:${target.port}`).origin;
|
|
218
|
+
const headers = sameOrigin && target.apiKey && target.type.toLowerCase() !== "bambu" ? { "X-Api-Key": target.apiKey } : {};
|
|
219
|
+
const response = await this.http.get(url, {
|
|
220
|
+
headers,
|
|
221
|
+
responseType: "arraybuffer",
|
|
222
|
+
timeout: Math.max(1, Math.round(timeout)),
|
|
223
|
+
maxContentLength: MAX_SNAPSHOT_BYTES,
|
|
224
|
+
maxRedirects: Object.keys(headers).length ? 0 : 3,
|
|
225
|
+
});
|
|
226
|
+
const bytes = Buffer.from(response.data);
|
|
227
|
+
if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) {
|
|
228
|
+
throw new Error(`not a JPEG (${String(response.headers?.["content-type"] ?? "unknown type")}, ${bytes.length} bytes)`);
|
|
229
|
+
}
|
|
230
|
+
return bytes;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
/** The pinned faust-machines commit (PRD §8.9). The PyPI 0.1.0 release crashes under mcp 2.x (issue #30). */
|
|
3
|
+
export declare const FUSION_MCP_PINNED_COMMIT = "19b9f1e95192";
|
|
4
|
+
export declare const FUSION_MCP_DEFAULT_ARGS: string[];
|
|
5
|
+
/** PRD §8.9: Blender's MCP addon holds the add-in's own default, 9876, so Fusion uses 9877. */
|
|
6
|
+
export declare const FUSION_ADDIN_DEFAULT_PORT = 9877;
|
|
7
|
+
type Arguments = Record<string, unknown>;
|
|
8
|
+
/** `open --env` applies only when it starts the app, which is the only time this runs. */
|
|
9
|
+
export declare function fusionLaunchArguments(port: number): string[];
|
|
10
|
+
export declare class FusionMcpBridge {
|
|
11
|
+
private session?;
|
|
12
|
+
private connecting?;
|
|
13
|
+
/** One client for the life of this server; reconnects on the next call after the transport closes. */
|
|
14
|
+
private connect;
|
|
15
|
+
private open;
|
|
16
|
+
/** Call a discovered tool once; a failure after dispatch is reported, never replayed. */
|
|
17
|
+
private invoke;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* {configured, connected, addinReachable, fusionRunning, tools[]}. connected
|
|
21
|
+
* means the server started and the add-in answered its ping. With launch:
|
|
22
|
+
* true and Fusion not running on this Mac, opens Fusion and waits up to 90 s
|
|
23
|
+
* for the add-in port.
|
|
24
|
+
*/
|
|
25
|
+
status(args: Arguments, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
26
|
+
/** Pass a call through to a discovered faust tool with its full content and errors (the blender_mcp_call contract). */
|
|
27
|
+
call(args: Arguments, signal?: AbortSignal): Promise<CallToolResult>;
|
|
28
|
+
/** A new parametric design document, so faust tools have an active design. */
|
|
29
|
+
newDesign(args: Arguments, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
30
|
+
/**
|
|
31
|
+
* Write <name>.stl (export_stl), <name>.f3d (export_f3d) and
|
|
32
|
+
* <name>.parameters.json into output_dir. Files are staged, verified and
|
|
33
|
+
* then published without ever replacing an existing file.
|
|
34
|
+
*/
|
|
35
|
+
export(args: Arguments, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
36
|
+
}
|
|
37
|
+
export declare const FUSION_STATUS_OUTPUT_SCHEMA: {
|
|
38
|
+
type: "object";
|
|
39
|
+
properties: {
|
|
40
|
+
configured: {
|
|
41
|
+
type: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
connected: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
addinReachable: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
fusionRunning: {
|
|
53
|
+
type: string;
|
|
54
|
+
};
|
|
55
|
+
tools: {
|
|
56
|
+
type: string;
|
|
57
|
+
items: {
|
|
58
|
+
type: string;
|
|
59
|
+
properties: {
|
|
60
|
+
name: {
|
|
61
|
+
type: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
required: string[];
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
launched: {
|
|
68
|
+
type: string;
|
|
69
|
+
description: string;
|
|
70
|
+
};
|
|
71
|
+
transport: {
|
|
72
|
+
type: string;
|
|
73
|
+
};
|
|
74
|
+
pin: {
|
|
75
|
+
type: string;
|
|
76
|
+
description: string;
|
|
77
|
+
};
|
|
78
|
+
server: {
|
|
79
|
+
type: string;
|
|
80
|
+
};
|
|
81
|
+
error: {
|
|
82
|
+
type: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
required: string[];
|
|
86
|
+
};
|
|
87
|
+
/** fusion_mcp_call passes the remote result through: its structuredContent, else an empty object. */
|
|
88
|
+
export declare const FUSION_CALL_OUTPUT_SCHEMA: {
|
|
89
|
+
type: "object";
|
|
90
|
+
};
|
|
91
|
+
export declare const FUSION_NEW_DESIGN_OUTPUT_SCHEMA: {
|
|
92
|
+
type: "object";
|
|
93
|
+
properties: {
|
|
94
|
+
status: {
|
|
95
|
+
type: string;
|
|
96
|
+
const: string;
|
|
97
|
+
};
|
|
98
|
+
document: {
|
|
99
|
+
type: string;
|
|
100
|
+
};
|
|
101
|
+
parametric: {
|
|
102
|
+
type: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
required: string[];
|
|
106
|
+
};
|
|
107
|
+
export declare const FUSION_EXPORT_OUTPUT_SCHEMA: {
|
|
108
|
+
type: "object";
|
|
109
|
+
properties: {
|
|
110
|
+
status: {
|
|
111
|
+
type: string;
|
|
112
|
+
const: string;
|
|
113
|
+
};
|
|
114
|
+
stl: {
|
|
115
|
+
type: string;
|
|
116
|
+
description: string;
|
|
117
|
+
};
|
|
118
|
+
f3d: {
|
|
119
|
+
type: string;
|
|
120
|
+
description: string;
|
|
121
|
+
};
|
|
122
|
+
parameters: {
|
|
123
|
+
type: string;
|
|
124
|
+
description: string;
|
|
125
|
+
};
|
|
126
|
+
body: {
|
|
127
|
+
type: string;
|
|
128
|
+
};
|
|
129
|
+
triangles: {
|
|
130
|
+
type: string;
|
|
131
|
+
minimum: number;
|
|
132
|
+
};
|
|
133
|
+
bytes: {
|
|
134
|
+
type: string;
|
|
135
|
+
minimum: number;
|
|
136
|
+
};
|
|
137
|
+
parameterCount: {
|
|
138
|
+
type: string;
|
|
139
|
+
minimum: number;
|
|
140
|
+
};
|
|
141
|
+
boundingBox: {
|
|
142
|
+
type: string;
|
|
143
|
+
description: string;
|
|
144
|
+
};
|
|
145
|
+
stlUnits: {
|
|
146
|
+
type: string;
|
|
147
|
+
enum: string[];
|
|
148
|
+
description: string;
|
|
149
|
+
};
|
|
150
|
+
fusionSizeMm: {
|
|
151
|
+
type: string;
|
|
152
|
+
items: {
|
|
153
|
+
type: string;
|
|
154
|
+
};
|
|
155
|
+
description: string;
|
|
156
|
+
};
|
|
157
|
+
warnings: {
|
|
158
|
+
type: string;
|
|
159
|
+
items: {
|
|
160
|
+
type: string;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
required: string[];
|
|
165
|
+
};
|
|
166
|
+
export {};
|