Skip to content

Commit 00e655f

Browse files
authored
Merge pull request #15 from plotflow/claude/artwork-sales-website-01JWP9tY4m4hfX8WyoEQZ1kU
feat: Command journal on the sheet + instant skip + drop Plot Again o…
2 parents 71a2989 + 15dc9c2 commit 00e655f

3 files changed

Lines changed: 83 additions & 3 deletions

File tree

index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ <h5>MACHINE STATE · <b>PLOTTING</b></h5>
9292
<h2 class="jpcap">マシンドロー</h2>
9393
<p>DRAWN BY MACHINE · EST. 2026</p>
9494
</div>
95-
<div class="replay" id="replay"><span>▶&#xFE0E; Plot again</span></div>
9695
</div>
9796

9897
<aside class="pf-card">
@@ -107,6 +106,12 @@ <h2 class="jpcap">マシンドロー</h2>
107106
</div>
108107
<div class="ft"><span>PEN-UP TRAVEL</span><span>SIMULATED ON SHEET</span></div>
109108
</aside>
109+
110+
<!-- command journal — translucent terminal over the sheet -->
111+
<div class="pf-term">
112+
<div class="hd"><span>COMMAND JOURNAL — pyaxidraw interactive</span><span class="sp"></span><span>67% · J</span></div>
113+
<pre id="pfTermBody"></pre>
114+
</div>
110115
</div>
111116
</section>
112117
<div class="pf-status">

scripts/plotter.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
// derived from the real plot time (≈ x45–x60)
3434
var INK = '#e8351f';
3535
var cur, len = 1, drawn = 0, painted = 0, playing = true, speed = 1, mmPerUnit = 1, totalMin = 1;
36+
var termBody = $('pfTermBody'); // command journal (homepage only)
37+
var polys = [], strokes = [], termIdx = -1; // parsed geometry + journal cursor
3638
var vb = { x: 0, y: 0, w: 1, h: 1 };
3739
var tf = { s: 1, ox: 0, oy: 0, dpr: 1, ready: false };
3840
var prevPt = null;
@@ -139,6 +141,26 @@
139141
len = ppath.getTotalLength(); if (!len || !isFinite(len)) len = 1;
140142
var md = Math.max(vb.w, vb.h);
141143
mmPerUnit = 420 / md; // longest side ≈ 420mm
144+
// parse the raw path once: polylines for fast full-draw, stroke table
145+
// (start/end/len/cumulative draw length) for the command journal
146+
polys = []; strokes = []; termIdx = -1;
147+
if (termBody) termBody.textContent = '';
148+
(function () {
149+
var re = /([ML])\s*(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g, m, curP = null;
150+
while ((m = re.exec(cur.d)) !== null) {
151+
var x = +m[2], y = +m[3];
152+
if (m[1] === 'M') { curP = [[x, y]]; polys.push(curP); }
153+
else if (curP) curP.push([x, y]);
154+
}
155+
var cum = 0;
156+
for (var i = 0; i < polys.length; i++) {
157+
var pl = polys[i], L = 0;
158+
for (var j = 0; j < pl.length - 1; j++)
159+
L += Math.hypot(pl[j+1][0]-pl[j][0], pl[j+1][1]-pl[j][1]);
160+
cum += L;
161+
strokes.push({ sx: pl[0][0], sy: pl[0][1], len: L, cum: cum });
162+
}
163+
})();
142164
var st = pathStats(cur.d);
143165
totalMin = (st.draw * mmPerUnit) / FEED
144166
+ (st.travel * mmPerUnit) / TRAVEL_FEED
@@ -161,6 +183,7 @@
161183
if (!tf.ready) resetCanvas(); // self-heal if the bed wasn't laid out yet
162184
var p = drawn / len;
163185
paintTo(drawn);
186+
updateJournal();
164187
if (pbar) pbar.style.width = (p * 100) + '%';
165188
if (pct) pct.textContent = Math.round(p * 100);
166189
if (ink) ink.textContent = (drawn * mmPerUnit / 1000).toFixed(2);
@@ -176,8 +199,48 @@
176199
}
177200
}
178201

202+
// Draw every stroke directly from the parsed polylines in one canvas pass —
203+
// used by Skip and end-state refits, where sampling the whole path with
204+
// getPointAtLength would stall the frame for ~a second.
205+
function fastPaintAll() {
206+
if (!tf.ready) resetCanvas();
207+
if (!tf.ready) return;
208+
ctx.beginPath();
209+
for (var i = 0; i < polys.length; i++) {
210+
var pl = polys[i];
211+
ctx.moveTo(mapX(pl[0][0]), mapY(pl[0][1]));
212+
for (var j = 1; j < pl.length; j++) ctx.lineTo(mapX(pl[j][0]), mapY(pl[j][1]));
213+
}
214+
ctx.stroke();
215+
painted = len;
216+
var lastP = polys.length ? polys[polys.length-1][polys[polys.length-1].length-1] : null;
217+
prevPt = lastP ? { x: lastP[0], y: lastP[1] } : null;
218+
}
219+
220+
// Command journal — emit pyaxidraw-style lines for the strokes just drawn.
221+
function pad4(n) { return String(n).padStart(4, '0'); }
222+
function updateJournal() {
223+
if (!termBody || !strokes.length) return;
224+
var lo = 0, hi = strokes.length - 1, idx = 0; // first stroke with cum >= drawn
225+
while (lo <= hi) { var mid = (lo + hi) >> 1;
226+
if (strokes[mid].cum < drawn) { lo = mid + 1; } else { idx = mid; hi = mid - 1; } }
227+
if (drawn >= len) idx = strokes.length - 1;
228+
if (idx === termIdx) return;
229+
termIdx = idx;
230+
var out = [], from = Math.max(0, idx - 2);
231+
for (var k = from; k <= idx; k++) {
232+
var st2 = strokes[k];
233+
out.push('ad.penup() z=+1');
234+
out.push('ad.moveto(' + (st2.sx * mmPerUnit).toFixed(2) + ', ' + (st2.sy * mmPerUnit).toFixed(2) + ') travel');
235+
out.push('ad.pendown() z=-1 · nib 0.30 mm');
236+
out.push('ad.lineto(…) stroke ' + pad4(k + 1) + '/' + strokes.length + ' · ' + (st2.len * mmPerUnit).toFixed(1) + ' mm');
237+
}
238+
out.push(drawn >= len ? 'ad.penup() z=+1 · job complete' : '_');
239+
termBody.textContent = out.join('\n');
240+
}
241+
179242
// Re-fit + repaint the current progress when the bed changes size.
180-
function refit() { resetCanvas(); paintTo(drawn); }
243+
function refit() { resetCanvas(); if (drawn >= len) fastPaintAll(); else paintTo(drawn); }
181244
window.addEventListener('resize', refit);
182245

183246
function restart() {
@@ -219,7 +282,7 @@
219282
}
220283
if (playBtn) playBtn.onclick = toggle;
221284
if ($('restart')) $('restart').onclick = restart;
222-
if ($('skip')) $('skip').onclick = function () { drawn = len; playing = false; render(); if (replay) replay.classList.add('show'); if (playBtn) playBtn.textContent = 'Replay'; };
285+
if ($('skip')) $('skip').onclick = function () { drawn = len; playing = false; fastPaintAll(); render(); if (replay) replay.classList.add('show'); if (playBtn) playBtn.textContent = 'Replay'; };
223286
if ($('speed')) $('speed').addEventListener('click', function (e) {
224287
var b = e.target.closest('button'); if (!b) return;
225288
[].forEach.call(e.currentTarget.children, function (x) { x.classList.remove('on'); });

styles/console.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,15 @@
133133

134134
/* JP caption lockup on the sheet (replaces the retired tagline) */
135135
.pf-cap h2.jpcap{font-family:var(--jp);font-weight:900;letter-spacing:.02em}
136+
137+
/* command journal — PlotGrid's translucent terminal, bottom-right of the sheet */
138+
.pf-term{position:absolute;right:34px;bottom:30px;z-index:8;width:470px;
139+
background:rgba(8,18,10,.62);border:1px solid rgba(123,224,138,.30);
140+
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
141+
color:#7be08a;font-family:'Liberation Mono','Courier New',monospace}
142+
.pf-term .hd{display:flex;gap:10px;padding:8px 12px;border-bottom:1px solid rgba(123,224,138,.24);
143+
font-size:9.5px;font-weight:700;letter-spacing:.12em}
144+
.pf-term .hd .sp{flex:1}
145+
.pf-term pre{margin:0;padding:9px 12px;font-size:10.5px;line-height:1.75;min-height:212px;
146+
white-space:pre-wrap;text-shadow:0 1px 2px rgba(0,0,0,.55)}
147+
@media(max-width:940px){.pf-term{display:none}}

0 commit comments

Comments
 (0)