The live connection reads tray_now (unit × 4 + slot, 254 external, 255 none) and works out the unit by id or, failing that, by position — so the A2L's AMS lite numbered 16 is handled. Cards say 'Using' (or 'Loaded' when not printing) with the filament's name and slot, mark the slot in the list, and draw a tube in its colour from that spool to the toolhead, with a moving flow while printing. The external spool is drawn when it's in use. Debug reports include tray_now and tray_tar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
169 lines
7.9 KiB
JavaScript
169 lines
7.9 KiB
JavaScript
// Printer models, and simple line drawings of their shapes, drawn for
|
|
// Printhive. The drawings suggest a model's form — open frame, enclosed, large
|
|
// format — rather than depict any product exactly.
|
|
(function () {
|
|
const svg = (inner, width = 150) =>
|
|
`<svg viewBox="0 0 ${width} 120" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
|
|
stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" role="img">${inner}</svg>`;
|
|
|
|
// One spool in a drawing, filled with what's loaded there: a colour for a
|
|
// spool, dashed for an empty slot, plain when nothing is known.
|
|
const HEX = /^#[0-9a-f]{6}$/i;
|
|
const colourOf = (slot) => (slot && HEX.test(slot.colour || '') ? slot.colour : null);
|
|
const spool = (cx, cy, r, slot, active) => {
|
|
if (slot && slot.empty) return `<circle cx="${cx}" cy="${cy}" r="${r}" stroke-dasharray="2 3" opacity="0.6"/>`;
|
|
const fill = colourOf(slot);
|
|
const ring = active ? ' stroke-width="3.4"' : '';
|
|
return fill
|
|
? `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${fill}"${ring}/><circle cx="${cx}" cy="${cy}" r="${r / 3}"/>`
|
|
: `<circle cx="${cx}" cy="${cy}" r="${r}"${ring}/>`;
|
|
};
|
|
|
|
const toolhead = (x, y) => `<rect x="${x}" y="${y}" width="14" height="15" rx="2"/>
|
|
<line x1="${x + 7}" y1="${y + 15}" x2="${x + 7}" y2="${y + 20}"/>`;
|
|
|
|
// Where each shape's spools sit, where its toolhead takes filament in, and
|
|
// where an external spool is drawn when it's the one in use.
|
|
const amsLiteAt = (x) => [[x - 9, 40, 7], [x + 9, 40, 7], [x - 9, 58, 7], [x + 9, 58, 7]];
|
|
const amsTopAt = (x, width) => [0, 1, 2, 3].map((i) => [x + width / 8 + (i * width) / 4, 23, 5]);
|
|
|
|
const layouts = {
|
|
// A1: a bed on a base, an upright gantry, an AMS lite alongside.
|
|
open: {
|
|
spools: amsLiteAt(126), head: [50, 49], external: [104, 12, 6],
|
|
body: () => `
|
|
<rect x="12" y="94" width="92" height="10" rx="3"/>
|
|
<rect x="24" y="84" width="68" height="6" rx="2"/>
|
|
<line x1="20" y1="94" x2="20" y2="26"/><line x1="96" y1="94" x2="96" y2="26"/>
|
|
<line x1="16" y1="26" x2="100" y2="26"/><line x1="20" y1="56" x2="96" y2="56"/>`,
|
|
stand: (x = 126) => `<line x1="${x}" y1="104" x2="${x}" y2="52"/><line x1="${x - 10}" y1="104" x2="${x + 10}" y2="104"/>`,
|
|
},
|
|
// A1 mini: a single column holding the gantry out over the bed.
|
|
openMini: {
|
|
spools: amsLiteAt(122), head: [58, 47], external: [29, 18, 6],
|
|
body: () => `
|
|
<rect x="22" y="94" width="72" height="10" rx="3"/>
|
|
<rect x="36" y="84" width="50" height="6" rx="2"/>
|
|
<rect x="24" y="30" width="10" height="64" rx="2"/>
|
|
<line x1="34" y1="54" x2="90" y2="54"/>`,
|
|
stand: () => `<line x1="122" y1="104" x2="122" y2="52"/><line x1="112" y1="104" x2="132" y2="104"/>`,
|
|
},
|
|
// A2L: the A1's form, stretched to a larger bed.
|
|
openLarge: {
|
|
spools: amsLiteAt(132), head: [52, 47], external: [112, 10, 6],
|
|
body: () => `
|
|
<rect x="6" y="94" width="104" height="10" rx="3"/>
|
|
<rect x="14" y="84" width="88" height="6" rx="2"/>
|
|
<line x1="10" y1="94" x2="10" y2="22"/><line x1="106" y1="94" x2="106" y2="22"/>
|
|
<line x1="6" y1="22" x2="110" y2="22"/><line x1="10" y1="54" x2="106" y2="54"/>`,
|
|
stand: () => `<line x1="132" y1="104" x2="132" y2="52"/><line x1="122" y1="104" x2="142" y2="104"/>`,
|
|
},
|
|
// P1P: a bare cube frame.
|
|
frame: {
|
|
width: 140, spools: [], head: [56, 53], external: [124, 18, 6],
|
|
body: () => `
|
|
<rect x="30" y="38" width="66" height="66" rx="3"/>
|
|
<path d="M30 38 L44 28 L110 28 L96 38 M110 28 L110 94 L96 104"/>
|
|
<line x1="30" y1="60" x2="96" y2="60"/>
|
|
<rect x="40" y="90" width="46" height="5" rx="2"/>`,
|
|
},
|
|
// P1S, P2S, X1C, X1E, X2D: an enclosed box with an AMS on top.
|
|
box: {
|
|
width: 126, spools: amsTopAt(30, 66), head: [56, 52], external: [112, 60, 6],
|
|
body: () => `
|
|
<rect x="30" y="14" width="66" height="18" rx="3"/>
|
|
<rect x="28" y="36" width="70" height="70" rx="5"/>
|
|
<rect x="36" y="44" width="54" height="46" rx="3"/>
|
|
<line x1="36" y1="98" x2="90" y2="98"/>`,
|
|
},
|
|
// H2S, H2D, H2C: a wide enclosed box with an AMS on top.
|
|
large: {
|
|
spools: amsTopAt(28, 94), head: [58, 52], secondHead: [78, 52], external: [142, 60, 6],
|
|
body: () => `
|
|
<rect x="28" y="14" width="94" height="18" rx="3"/>
|
|
<rect x="18" y="36" width="114" height="70" rx="5"/>
|
|
<rect x="26" y="44" width="98" height="46" rx="3"/>
|
|
<line x1="26" y1="98" x2="124" y2="98"/>`,
|
|
},
|
|
// Anything else: a plain box with a nozzle.
|
|
generic: {
|
|
width: 140, spools: [], head: [63, 50], external: [122, 22, 6],
|
|
body: () => `
|
|
<rect x="34" y="30" width="72" height="72" rx="8"/>
|
|
<line x1="44" y1="92" x2="96" y2="92"/>`,
|
|
},
|
|
};
|
|
|
|
// The filament's path from a spool into the toolhead: a tube curving over
|
|
// the frame, outlined so a dark filament still shows on a dark page.
|
|
const feed = (from, head, colour, moving) => {
|
|
const [sx, sy] = from;
|
|
const tx = head[0] + 7;
|
|
const ty = head[1];
|
|
const lift = Math.max(2, Math.min(sy, ty) - 30);
|
|
const d = `M${sx} ${sy} C${sx} ${lift}, ${tx} ${lift}, ${tx} ${ty}`;
|
|
return `<g class="feed${moving ? ' is-moving' : ''}">
|
|
<path d="${d}" stroke-width="4.6" opacity="0.55"/>
|
|
<path d="${d}" stroke="${colour}" stroke-width="2.6"/>
|
|
${moving ? `<path class="feed-flow" d="${d}" stroke="#ffffff" stroke-width="1.2" stroke-dasharray="1 7" opacity="0.8"/>` : ''}
|
|
</g>`;
|
|
};
|
|
|
|
// `slots`: the first AMS unit's four slots, as { empty, colour }.
|
|
// `use`: which is feeding — { slot } for one of those, { external, colour }
|
|
// for the external spool — and `moving` while it's printing.
|
|
function draw(shapeName, slots = [], use = null, moving = false) {
|
|
const L = layouts[shapeName] || layouts.generic;
|
|
let parts = L.body();
|
|
let feedPart = '';
|
|
if (use && use.external && L.external) {
|
|
const [x, y, r] = L.external;
|
|
parts += spool(x, y, r, { colour: use.colour }, true);
|
|
feedPart = feed([x, y], L.head, colourOf(use) || 'currentColor', moving);
|
|
} else if (use && use.slot != null && L.spools[use.slot]) {
|
|
const [x, y] = L.spools[use.slot];
|
|
const colour = colourOf(slots[use.slot]);
|
|
if (colour) feedPart = feed([x, y], L.head, colour, moving);
|
|
}
|
|
if (L.stand) parts += L.stand();
|
|
parts += L.spools.map(([x, y, r], i) => spool(x, y, r, slots[i], use && use.slot === i)).join('');
|
|
// The tube is drawn behind the toolhead, so it disappears into it.
|
|
parts += feedPart + toolhead(...L.head);
|
|
if (L.secondHead) parts += toolhead(...L.secondHead);
|
|
return svg(parts, L.width);
|
|
}
|
|
|
|
// ams is only a suggestion — a P1S might have an AMS, an AMS 2 Pro or an AMS HT.
|
|
const models = [
|
|
{ id: 'A1 mini', shape: 'openMini', ams: 'ams_lite' },
|
|
{ id: 'A1', shape: 'open', ams: 'ams_lite' },
|
|
{ id: 'A2L', shape: 'openLarge', ams: 'ams_lite' },
|
|
{ id: 'P1P', shape: 'frame', ams: 'ams' },
|
|
{ id: 'P1S', shape: 'box', ams: 'ams' },
|
|
{ id: 'P2S', shape: 'box', ams: 'ams_2_pro' },
|
|
{ id: 'X1C', shape: 'box', ams: 'ams' },
|
|
{ id: 'X1E', shape: 'box', ams: 'ams' },
|
|
{ id: 'X2D', shape: 'box', ams: 'ams_2_pro' },
|
|
{ id: 'H2S', shape: 'large', ams: 'ams_2_pro' },
|
|
{ id: 'H2D', shape: 'large', ams: 'ams_2_pro' },
|
|
{ id: 'H2C', shape: 'large', ams: 'ams_2_pro' },
|
|
{ id: 'Other', shape: 'generic', ams: 'none' },
|
|
];
|
|
|
|
const amsTypes = [
|
|
{ id: 'none', label: 'No AMS (external spool)' },
|
|
{ id: 'ams_lite', label: 'AMS lite (full or empty only)' },
|
|
{ id: 'ams', label: 'AMS' },
|
|
{ id: 'ams_2_pro', label: 'AMS 2 Pro' },
|
|
{ id: 'ams_ht', label: 'AMS HT' },
|
|
];
|
|
|
|
const find = (id) => models.find((m) => m.id === id) || null;
|
|
|
|
window.PRINTER_MODELS = {
|
|
models,
|
|
amsTypes,
|
|
find,
|
|
art: (id, slots = [], use = null, moving = false) => draw((find(id) || {}).shape, slots, use, moving),
|
|
};
|
|
}());
|