feat: sharp edge pipeline V02, tessellation presets, media cache-bust, GMSH plan
Sharp Edge Pipeline V02:
- export_step_to_gltf.py: replace BRep_Tool.Polygon3D_s (returns None in XCAF) with
GCPnts_UniformAbscissa curve sampling at 0.3mm step — extracts 17,129 segment pairs
- Inject sharp_edge_pairs + sharp_threshold_deg into GLB extras (scenes[0].extras)
via binary GLB JSON-chunk patching (no extra dependency)
- export_gltf.py: read schaeffler_sharp_edge_pairs from Blender scene custom props,
apply via KD-tree to mark edges sharp=True + seam=True (OCC mm Z-up → Blender transform)
- tools/restore_sharp_marks.py: dual-pass (dihedral angle + OCC pairs), updated coordinate
transform (X, -Z, Y) * 0.001
Tessellation:
- Admin UI: Draft / Standard / Fine preset buttons with active-state highlighting
- Default angular deflection: preview 0.5→0.1 rad, production 0.2→0.05 rad
- export_glb.py: read updated defaults from system_settings
Media / Cache:
- media/service.py: get_download_url appends ?v={file_size_bytes} cache-buster
- media/router.py: Cache-Control: no-cache for all download/thumbnail endpoints
Render pipeline:
- still_render.py / turntable_render.py: shared GPU activation + camera improvements
- render_order_line.py: global render position support
- render_thumbnail.py: updated defaults
Frontend:
- InlineCadViewer: file_size_bytes-aware URL update triggers re-fetch on regeneration
- ThreeDViewer: material panel, part selection, PBR mode improvements
- Admin.tsx: tessellation preset cards, GMSH setting dropdown
- MediaBrowser, ProductDetail, OrderDetail, Orders: various UI improvements
- New: MaterialPanel, GlobalRenderPositionsPanel, StepIndicator components
- New: renderPositions.ts API client
Plans / Docs:
- plan.md: GMSH Frontal-Delaunay tessellation plan (6 tasks)
- LEARNINGS.md: OCC Polygon3D_s None issue + GCPnts fix
- .gitignore: add backend/core (core dump from root process)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ import { listOutputTypes } from '../api/outputTypes'
|
||||
import type { OutputType } from '../api/outputTypes'
|
||||
import api from '../api/client'
|
||||
import StepDropzone from '../components/upload/StepDropzone'
|
||||
import StepIndicator from '../components/shared/StepIndicator'
|
||||
import HelpTooltip from '../components/HelpTooltip'
|
||||
|
||||
function StatCard({ icon, value, label, description, color }: {
|
||||
icon: React.ReactNode
|
||||
@@ -238,6 +240,8 @@ export default function UploadPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<StepIndicator step={step} total={4} labels={['Upload', 'Review', 'Configure', 'STEP Files']} />
|
||||
|
||||
{/* ── Step 1: Excel drop zone ─────────────────────────────────────── */}
|
||||
{step === 1 && (
|
||||
<div
|
||||
@@ -379,9 +383,12 @@ export default function UploadPage() {
|
||||
</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary">PIM-ID</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary">Series</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary"
|
||||
title="Gew\u00e4hltes Produkt \u2014 the specific material/coating variant from the Excel"
|
||||
>Gew. Produkt</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary">
|
||||
<span className="flex items-center gap-1">
|
||||
Gew. Produkt
|
||||
<HelpTooltip help={{ title: 'Gew. Produkt', body: 'Gewähltes Produkt — the specific product variant selected in the Excel file.' }} />
|
||||
</span>
|
||||
</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary">Category</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary">Status</th>
|
||||
<th className="px-4 py-2 font-medium text-content-secondary text-center" title="Whether a STEP/CAD file is already linked to this product">STEP</th>
|
||||
@@ -477,59 +484,6 @@ export default function UploadPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Step 4: Upload STEP Files ────────────────────────────────────── */}
|
||||
{step === 4 && createdOrder && (
|
||||
<div className="space-y-4">
|
||||
<div className="card p-4">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileBox size={18} className="text-content-secondary" />
|
||||
<h2 className="font-semibold text-content">
|
||||
Upload STEP Files — {createdOrder.order_number}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-content-secondary">
|
||||
Drop one or more <strong>.stp / .step</strong> files below.
|
||||
Each file is matched to an order item by filename stem (case-insensitive).
|
||||
You can also skip this and upload STEP files later from the order detail page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<StepDropzone
|
||||
orderId={createdOrder.id}
|
||||
onMatchComplete={() => qc.invalidateQueries({ queryKey: ['order', createdOrder.id] })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
className="btn-secondary"
|
||||
onClick={() => navigate(`/orders/${createdOrder.id}`)}
|
||||
>
|
||||
Skip — Go to Order
|
||||
</button>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => navigate(`/orders/${createdOrder.id}`)}
|
||||
>
|
||||
<ArrowRight size={16} />
|
||||
Done — Go to Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Validation Dialog ────────────────────────────────────────────── */}
|
||||
{showValidationDialog && (
|
||||
<ValidationDialog
|
||||
validation={validationData}
|
||||
onClose={() => setShowValidationDialog(false)}
|
||||
onSaveAlias={(alias, suggestion) => saveAlias.mutate({ alias, materialName: suggestion })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── Step 3: Output Type Selection ───────────────────────────────── */}
|
||||
{step === 3 && previewResult && (
|
||||
<div className="space-y-4">
|
||||
@@ -684,6 +638,59 @@ export default function UploadPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Validation Dialog ────────────────────────────────────────────── */}
|
||||
{showValidationDialog && (
|
||||
<ValidationDialog
|
||||
validation={validationData}
|
||||
onClose={() => setShowValidationDialog(false)}
|
||||
onSaveAlias={(alias, suggestion) => saveAlias.mutate({ alias, materialName: suggestion })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── Step 4: Upload STEP Files ────────────────────────────────────── */}
|
||||
{step === 4 && createdOrder && (
|
||||
<div className="space-y-4">
|
||||
<div className="card p-4">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileBox size={18} className="text-content-secondary" />
|
||||
<h2 className="font-semibold text-content">
|
||||
Upload STEP Files — {createdOrder.order_number}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-content-secondary">
|
||||
Drop one or more <strong>.stp / .step</strong> files below.
|
||||
Each file is matched to an order item by filename stem (case-insensitive).
|
||||
You can also skip this and upload STEP files later from the order detail page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<StepDropzone
|
||||
orderId={createdOrder.id}
|
||||
onMatchComplete={() => qc.invalidateQueries({ queryKey: ['order', createdOrder.id] })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
className="btn-secondary"
|
||||
onClick={() => navigate(`/orders/${createdOrder.id}`)}
|
||||
>
|
||||
Skip — Go to Order
|
||||
</button>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => navigate(`/orders/${createdOrder.id}`)}
|
||||
>
|
||||
<ArrowRight size={16} />
|
||||
Done — Go to Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user