This commit is contained in:
@@ -244,8 +244,13 @@ router.get('/admin/models/:id/edit', requireAdmin, (req: Request, res: Response)
|
||||
|
||||
const categories = q<Category>(`SELECT * FROM categories ORDER BY sort_order, name`).all()
|
||||
const pdfs = q<ModelPdf>(`SELECT * FROM model_pdfs WHERE model_id = ? ORDER BY sort_order`).all(model.id)
|
||||
const absModelPath = path.join(UPLOADS_DIR, model.file_path)
|
||||
const hasGeometry = (model.file_type === 'step' || model.file_type === 'stp')
|
||||
? fs.existsSync(geometryOutputPath(absModelPath))
|
||||
: true
|
||||
|
||||
res.render('admin/edit', {
|
||||
model, categories, pdfs, error: null,
|
||||
model, categories, pdfs, error: null, hasGeometry,
|
||||
baseUrl: process.env.BASE_URL ?? `http://localhost:${process.env.PORT ?? 3000}`,
|
||||
})
|
||||
})
|
||||
@@ -277,6 +282,44 @@ router.post('/admin/pdfs/:id/delete', requireAdmin, (req: Request, res: Response
|
||||
res.redirect(`/admin/models/${pdf.model_id}/edit`)
|
||||
})
|
||||
|
||||
// ---- Reconvert STEP/STP geometry -----------------------------------------
|
||||
|
||||
router.post('/admin/models/:id/reconvert', requireAdmin, async (req: Request, res: Response) => {
|
||||
const model = q<Model>(`SELECT * FROM models WHERE id = ?`).get(req.params.id)
|
||||
if (!model) { res.status(404).json({ error: 'Model not found' }); return }
|
||||
|
||||
if (model.file_type !== 'step' && model.file_type !== 'stp') {
|
||||
res.status(400).json({ error: 'Only STEP/STP models require geometry conversion' }); return
|
||||
}
|
||||
|
||||
const absModelPath = path.join(UPLOADS_DIR, model.file_path)
|
||||
if (!fs.existsSync(absModelPath)) {
|
||||
res.status(404).json({ error: 'Source model file not found on disk' }); return
|
||||
}
|
||||
|
||||
const geoOutPath = geometryOutputPath(absModelPath)
|
||||
|
||||
try {
|
||||
await convertStepFile(absModelPath, geoOutPath)
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: `Conversion failed: ${(err as Error).message}` }); return
|
||||
}
|
||||
|
||||
// Regenerate thumbnail from new geometry (non-fatal)
|
||||
try {
|
||||
const geo = JSON.parse(fs.readFileSync(geoOutPath, 'utf8')) as GeometryFile
|
||||
const tris = geometryToTriangles(geo)
|
||||
const thumbPath = thumbnailOutputPath(UPLOADS_DIR, model.id)
|
||||
await renderThumbnail(tris, thumbPath)
|
||||
const thumbRel = path.relative(UPLOADS_DIR, thumbPath).replace(/\\/g, '/')
|
||||
db.prepare(`UPDATE models SET thumbnail_path = ? WHERE id = ?`).run(thumbRel, model.id)
|
||||
} catch (thumbErr) {
|
||||
console.error('[thumbnail] reconvert thumbnail failed:', (thumbErr as Error).message)
|
||||
}
|
||||
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
// ---- JSON list -----------------------------------------------------------
|
||||
|
||||
router.get('/api/admin/models', requireAdmin, (_req: Request, res: Response) => {
|
||||
|
||||
@@ -22,7 +22,11 @@ let _init: Promise<Awaited<ReturnType<typeof occtimport>>> | null = null
|
||||
|
||||
async function getOcct() {
|
||||
if (_occt) return _occt
|
||||
if (!_init) _init = occtimport().then(m => { _occt = m; return m })
|
||||
if (!_init) {
|
||||
_init = occtimport().then(m => { _occt = m; return m })
|
||||
// Reset on failure so the next upload attempt retries rather than replaying a cached rejection
|
||||
_init.catch(() => { _init = null })
|
||||
}
|
||||
return _init
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,31 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Geometry status (STEP/STP only) -->
|
||||
<% if (model.file_type === 'step' || model.file_type === 'stp') { %>
|
||||
<div class="bg-surface-900 border border-gray-800 rounded-2xl p-6 mb-6">
|
||||
<h2 class="text-sm font-semibold text-white mb-3">3D Geometry Processing</h2>
|
||||
<% if (hasGeometry) { %>
|
||||
<div class="flex items-center gap-2 text-sm text-green-400">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
Geometry processed successfully
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2 text-sm text-amber-400">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>
|
||||
Geometry processing failed — model cannot be viewed
|
||||
</div>
|
||||
<button type="button" id="reconvert-btn"
|
||||
class="shrink-0 bg-surface-700 hover:bg-surface-600 border border-gray-700 text-sm text-gray-300 rounded-lg px-4 py-2 transition-colors">
|
||||
Retry Processing
|
||||
</button>
|
||||
</div>
|
||||
<pre id="reconvert-error" class="mt-3 text-xs text-red-400 whitespace-pre-wrap break-all hidden"></pre>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Attached PDFs -->
|
||||
<div class="bg-surface-900 border border-gray-800 rounded-2xl p-6 mb-6">
|
||||
<h2 class="text-sm font-semibold text-white mb-4">Shop Diagrams / PDFs</h2>
|
||||
@@ -115,5 +140,34 @@
|
||||
</div>
|
||||
|
||||
<script src="/admin.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const btn = document.getElementById('reconvert-btn')
|
||||
if (!btn) return
|
||||
const errEl = document.getElementById('reconvert-error')
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true
|
||||
btn.textContent = 'Processing…'
|
||||
errEl.classList.add('hidden')
|
||||
try {
|
||||
const res = await fetch('/admin/models/<%= model.id %>/reconvert', { method: 'POST' })
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
window.location.reload()
|
||||
} else {
|
||||
errEl.textContent = data.error ?? 'Unknown error'
|
||||
errEl.classList.remove('hidden')
|
||||
btn.disabled = false
|
||||
btn.textContent = 'Retry Processing'
|
||||
}
|
||||
} catch (err) {
|
||||
errEl.textContent = String(err)
|
||||
errEl.classList.remove('hidden')
|
||||
btn.disabled = false
|
||||
btn.textContent = 'Retry Processing'
|
||||
}
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user