kylebrodeur commited on
Commit
d6e2ebf
·
verified ·
1 Parent(s): 4353ad4

deploy: update Space from deploy_preflight --push

Browse files
Files changed (3) hide show
  1. app.py +5 -6
  2. core/theme.py +10 -2
  3. core/widgets.py +34 -12
app.py CHANGED
@@ -913,13 +913,12 @@ def build() -> gr.Blocks:
913
  gr.HTML(tab_intro("The pre-flight check, <b>before it prints</b>: slice the part, read "
914
  "precedent, flag failures, and get a second opinion. Then → PRINT."))
915
  with gr.Group(visible=False, elem_classes=["ce-part-card"]) as build_results:
916
- # slice + motion preview side by side — grouped like LOAD's PART card
917
- gr.HTML(rule("SLICE · CROSS-SECTION"))
918
- with gr.Row(equal_height=False, elem_classes=["ce-part-row"]):
919
- with gr.Column(scale=3):
920
  vp_layer = gr.Image(label="", height=360, show_label=False)
921
- with gr.Column(scale=1, elem_classes=["ce-vp"]):
922
- gr.HTML(rule("MOTION PREVIEW"))
923
  vprint = gr.HTML()
924
  with gr.Column(elem_classes=["ce-hslider"]):
925
  vp_slider = gr.Slider(1, SCRUB_LAYERS, value=1, step=1,
 
913
  gr.HTML(tab_intro("The pre-flight check, <b>before it prints</b>: slice the part, read "
914
  "precedent, flag failures, and get a second opinion. Then → PRINT."))
915
  with gr.Group(visible=False, elem_classes=["ce-part-card"]) as build_results:
916
+ # slice + motion preview side by side — equal width columns
917
+ gr.HTML(rule("SLICE · CROSS-SECTION + MOTION PREVIEW"))
918
+ with gr.Row(equal_height=False, elem_classes=["ce-part-row", "ce-slice-viz"]):
919
+ with gr.Column(scale=1, elem_classes=["ce-slice-col"]):
920
  vp_layer = gr.Image(label="", height=360, show_label=False)
921
+ with gr.Column(scale=1, elem_classes=["ce-slice-col", "ce-vp"]):
 
922
  vprint = gr.HTML()
923
  with gr.Column(elem_classes=["ce-hslider"]):
924
  vp_slider = gr.Slider(1, SCRUB_LAYERS, value=1, step=1,
core/theme.py CHANGED
@@ -563,11 +563,19 @@ CSS += """
563
 
564
  /* Virtual print preview — bigger, framed, motion-aware */
565
  .ce-vp { padding:8px !important; border:1px solid var(--ao-outline-dim) !important;
566
- background:var(--ao-void) !important; min-height:340px !important;
567
- display:flex !important; flex-direction:column !important; gap:6px !important; }
 
568
  .ce-vp svg { width:100% !important; height:auto !important; max-height:300px !important; }
569
  .ce-vp .ce-vp-caption { color:var(--ao-outline); font-size:11px;
570
  letter-spacing:1px; text-transform:uppercase; padding:4px 2px; }
 
 
 
 
 
 
 
571
 
572
  /* Mini loader (Second Opinion in-flight — smaller than the full preflight loader).
573
  Same scanning-bar look but condensed. */
 
563
 
564
  /* Virtual print preview — bigger, framed, motion-aware */
565
  .ce-vp { padding:8px !important; border:1px solid var(--ao-outline-dim) !important;
566
+ background:var(--ao-void) !important; display:flex !important; flex-direction:column !important; gap:6px !important; }
567
+ .ce-vp-wrap { border:1px solid var(--ao-outline-dim); background:#0a0c14; display:inline-block; }
568
+ .ce-vp canvas { display:block; max-width:100%; height:auto; }
569
  .ce-vp svg { width:100% !important; height:auto !important; max-height:300px !important; }
570
  .ce-vp .ce-vp-caption { color:var(--ao-outline); font-size:11px;
571
  letter-spacing:1px; text-transform:uppercase; padding:4px 2px; }
572
+ .ce-vp-replay { align-self:flex-start; }
573
+ /* side-by-side slice visualizers: equal-width columns */
574
+ .ce-slice-viz { gap:16px !important; }
575
+ .ce-slice-viz .ce-slice-col { flex:1 1 0 !important; min-width:0 !important; }
576
+ .ce-slice-viz .ce-slice-col .gr-image,
577
+ .ce-slice-viz .ce-slice-col .gr-image > div,
578
+ .ce-slice-viz .ce-slice-col .gr-image img { width:100% !important; height:auto !important; display:block !important; }
579
 
580
  /* Mini loader (Second Opinion in-flight — smaller than the full preflight loader).
581
  Same scanning-bar look but condensed. */
core/widgets.py CHANGED
@@ -26,9 +26,7 @@ def _projected_layers(mesh_path: str | Path, layer_height_mm: float = 0.2,
26
 
27
  {bounds:[xmin,xmax,ymin,ymax], layers:[[[x0,y0,x1,y1],...], ...]} — projected
28
  oblique screen space, rounded to keep the data-attribute payload small. Segments
29
- per layer are capped (down-sampled) so a high-res mesh doesn't bloat the page
30
- the rising animation only needs enough segments to read; the layer SCRUBBER
31
- (layer_image) renders full fidelity server-side.
32
  """
33
  from sim.virtual_printer import slice_segments
34
 
@@ -140,7 +138,8 @@ def virtual_printer_html(mesh_path: str | Path | None,
140
  """A live virtual-printer canvas for the cockpit (animated by VP_JS).
141
 
142
  Uses the layer height from the proposed PrintSettings so the preview is tied
143
- to the actual recommendation rather than a hard-coded default.
 
144
  """
145
  if not mesh_path or not Path(str(mesh_path)).exists():
146
  return ("<div class='ce-rule'>VIRTUAL PRINT</div>"
@@ -152,13 +151,28 @@ def virtual_printer_html(mesh_path: str | Path | None,
152
  "<div class='ce-sub'>mesh too thin to slice.</div>")
153
  payload = json.dumps(data).replace("'", "&#39;")
154
  lh_note = f"{layer_height:.2f} mm layers"
 
 
 
 
 
 
 
 
 
 
 
155
  return (
156
- "<div class='ce-rule'>VIRTUAL PRINT</div>"
157
- "<div style='border:1px solid var(--ao-outline-dim);background:#0a0c14;'>"
158
- f"<canvas class='ce-vp' width='520' height='300' data-vp='{payload}' "
159
- "style='width:100%;height:auto;display:block;'></canvas></div>"
160
- f"<div class='ce-sub' style='margin-top:2px;'>{caption} · {lh_note} · real cross-sections of "
161
- "this part, layer by layer (motion preview not a slicer)</div>"
 
 
 
 
162
  )
163
 
164
 
@@ -178,6 +192,12 @@ VP_HEAD = r"""
178
 
179
  // --- virtual-printer animator ---
180
  const DONE='#46627f', CUR='#ff9c00', NOZ='#ffe6b4', BG='#0a0c14';
 
 
 
 
 
 
181
  function animate(cv){
182
  let data; try { data = JSON.parse(cv.getAttribute('data-vp')); } catch(e){ return; }
183
  if(!data || !data.layers || !data.layers.length) return;
@@ -188,6 +208,8 @@ VP_HEAD = r"""
188
  const X = x => pad + (x-xmin)*s, Y = y => H - (pad + (y-ymin)*s);
189
  const N = data.layers.length;
190
  let upto = 0, hold = 0;
 
 
191
  function frame(){
192
  ctx.fillStyle=BG; ctx.fillRect(0,0,W,H);
193
  for(let li=0; li<=upto && li<N; li++){
@@ -204,8 +226,8 @@ VP_HEAD = r"""
204
  // HUD
205
  ctx.fillStyle='#9fb0c8'; ctx.font='10px ui-monospace,monospace';
206
  ctx.fillText('LAYER '+(upto+1)+'/'+N+' · '+Math.round(100*(upto+1)/N)+'%', pad, 14);
207
- if(upto < N-1){ if(++hold>=3){ hold=0; upto++; } cv._raf = requestAnimationFrame(frame); }
208
- // else: the full part is drawn — hold the final frame and STOP (no perpetual loop)
209
  }
210
  frame();
211
  }
 
26
 
27
  {bounds:[xmin,xmax,ymin,ymax], layers:[[[x0,y0,x1,y1],...], ...]} — projected
28
  oblique screen space, rounded to keep the data-attribute payload small. Segments
29
+ per layer are capped (down-sampled) so a high-res mesh doesn't bloat the page.
 
 
30
  """
31
  from sim.virtual_printer import slice_segments
32
 
 
138
  """A live virtual-printer canvas for the cockpit (animated by VP_JS).
139
 
140
  Uses the layer height from the proposed PrintSettings so the preview is tied
141
+ to the actual recommendation rather than a hard-coded default. Canvas size is
142
+ chosen from the projected mesh aspect ratio so the preview is not squished.
143
  """
144
  if not mesh_path or not Path(str(mesh_path)).exists():
145
  return ("<div class='ce-rule'>VIRTUAL PRINT</div>"
 
151
  "<div class='ce-sub'>mesh too thin to slice.</div>")
152
  payload = json.dumps(data).replace("'", "&#39;")
153
  lh_note = f"{layer_height:.2f} mm layers"
154
+
155
+ # Canvas size derived from projected bounds so the preview is not squished.
156
+ # Max width is clamped to the column; height follows aspect ratio.
157
+ xmin, xmax, ymin, ymax = data["bounds"]
158
+ proj_w = max(xmax - xmin, 1)
159
+ proj_h = max(ymax - ymin, 1)
160
+ aspect = proj_h / proj_w
161
+ max_css_w = 560
162
+ css_h = min(round(max_css_w * aspect), 340)
163
+ canvas_w, canvas_h = max_css_w, css_h
164
+
165
  return (
166
+ "<div class='ce-rule'>VIRTUAL PRINT · MOTION PREVIEW</div>"
167
+ "<div class='ce-vp-wrap'>"
168
+ f"<canvas class='ce-vp' width='{canvas_w}' height='{canvas_h}' data-vp='{payload}' "
169
+ f"data-aspect='{aspect:.4f}' style='width:{max_css_w}px;height:{css_h}px;display:block;'></canvas>"
170
+ "</div>"
171
+ f"<div class='ce-sub ce-vp-caption'>{caption} · {lh_note} · real cross-sections of "
172
+ "this part, rising layer by layer (motion preview — not a slicer). Click REPLAY to restart.</div>"
173
+ "<button class='ce-pillbtn ce-vp-replay' type='button' "
174
+ "onclick=\"const cv=this.parentNode.querySelector('canvas.ce-vp');"
175
+ "if(window.__vp_replay) window.__vp_replay(cv);\">REPLAY</button>"
176
  )
177
 
178
 
 
192
 
193
  // --- virtual-printer animator ---
194
  const DONE='#46627f', CUR='#ff9c00', NOZ='#ffe6b4', BG='#0a0c14';
195
+ window.__vp_replay = function(cv){
196
+ if(cv._raf){ cancelAnimationFrame(cv._raf); cv._raf=null; }
197
+ cv.removeAttribute('data-init');
198
+ const ctx = cv.getContext('2d'); ctx.fillStyle=BG; ctx.fillRect(0,0,cv.width,cv.height);
199
+ scan();
200
+ };
201
  function animate(cv){
202
  let data; try { data = JSON.parse(cv.getAttribute('data-vp')); } catch(e){ return; }
203
  if(!data || !data.layers || !data.layers.length) return;
 
208
  const X = x => pad + (x-xmin)*s, Y = y => H - (pad + (y-ymin)*s);
209
  const N = data.layers.length;
210
  let upto = 0, hold = 0;
211
+ // bigger hold count = slower animation. default 6; override with window.__vp_speed.
212
+ const speed = Math.max(1, Math.min(20, Number(window.__vp_speed || 6)));
213
  function frame(){
214
  ctx.fillStyle=BG; ctx.fillRect(0,0,W,H);
215
  for(let li=0; li<=upto && li<N; li++){
 
226
  // HUD
227
  ctx.fillStyle='#9fb0c8'; ctx.font='10px ui-monospace,monospace';
228
  ctx.fillText('LAYER '+(upto+1)+'/'+N+' · '+Math.round(100*(upto+1)/N)+'%', pad, 14);
229
+ if(upto < N-1){ if(++hold>=speed){ hold=0; upto++; } cv._raf = requestAnimationFrame(frame); }
230
+ // else: full part drawn — hold final frame
231
  }
232
  frame();
233
  }