577dd1ca7e
- Rewrite CLAUDE.md to match current 8-service architecture (was 11, 5 deleted) - Remove all as-any casts in OrderDetail.tsx (9 casts → 0) - Add cad_parsed_objects/cad_part_materials to OrderItem interface - Rename require_admin → require_global_admin across 6 router files (22 calls) - Remove EXPORT_GLB_PRODUCTION enum + generate_gltf_production_task (dead code) - Remove worker-thumbnail from ALLOWED_SERVICES, replace Flamenco link - Delete obsolete PLAN.md (1455 lines) and PLAN_REFACTOR.md (1174 lines) - Fix digit-only USD prim names with p_ prefix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
177 lines
6.0 KiB
TypeScript
177 lines
6.0 KiB
TypeScript
import api from './client'
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Types
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export interface CadObjects {
|
||
cad_file_id: string
|
||
original_name: string
|
||
processing_status: 'pending' | 'processing' | 'completed' | 'failed'
|
||
parsed_objects: Record<string, unknown> | null
|
||
}
|
||
|
||
export interface RegenerateThumbnailResponse {
|
||
cad_file_id: string
|
||
original_name: string
|
||
status: 'queued'
|
||
task_id: string | null
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// API functions
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Returns the URL to the thumbnail PNG for a CAD file.
|
||
* Use directly in <img src={getCadThumbnailUrl(id)} /> – the browser will
|
||
* handle the authenticated request via the axios interceptor when called
|
||
* programmatically, or you can construct the URL for use in img tags when
|
||
* the auth token is set as a header.
|
||
*
|
||
* For use in <img> tags without auth headers, prefer fetching as a blob and
|
||
* creating an object URL (see fetchThumbnailBlob below).
|
||
*/
|
||
export function getCadThumbnailUrl(cadFileId: string): string {
|
||
return `/api/cad/${cadFileId}/thumbnail`
|
||
}
|
||
|
||
/**
|
||
* Fetch the thumbnail PNG as a Blob and return an object URL suitable for
|
||
* use in <img src=...> without needing explicit auth headers.
|
||
* Remember to call URL.revokeObjectURL() when the component unmounts.
|
||
*/
|
||
export async function fetchThumbnailBlob(cadFileId: string): Promise<string> {
|
||
const res = await api.get<Blob>(`/cad/${cadFileId}/thumbnail`, {
|
||
responseType: 'blob',
|
||
})
|
||
return URL.createObjectURL(res.data)
|
||
}
|
||
|
||
/**
|
||
* Fetch the glTF model file as a Blob and return an object URL.
|
||
* Remember to call URL.revokeObjectURL() when the consumer is done.
|
||
*/
|
||
export async function fetchModelBlob(cadFileId: string): Promise<string> {
|
||
const res = await api.get<Blob>(`/cad/${cadFileId}/model`, {
|
||
responseType: 'blob',
|
||
})
|
||
return URL.createObjectURL(res.data)
|
||
}
|
||
|
||
/**
|
||
* Return the parsed_objects JSON for a CAD file.
|
||
*/
|
||
export async function getCadObjects(cadFileId: string): Promise<CadObjects> {
|
||
const res = await api.get<CadObjects>(`/cad/${cadFileId}/objects`)
|
||
return res.data
|
||
}
|
||
|
||
/**
|
||
* Ask the backend to re-queue STEP processing for a CAD file (admin only).
|
||
* Returns the Celery task_id (or null if the worker is not available).
|
||
*/
|
||
export async function regenerateThumbnail(
|
||
cadFileId: string,
|
||
): Promise<RegenerateThumbnailResponse> {
|
||
const res = await api.post<RegenerateThumbnailResponse>(
|
||
`/cad/${cadFileId}/regenerate-thumbnail`,
|
||
)
|
||
return res.data
|
||
}
|
||
|
||
export interface GenerateGltfResponse {
|
||
status: 'queued'
|
||
task_id: string
|
||
cad_file_id: string
|
||
}
|
||
|
||
/** Queue geometry GLB export directly from STEP via OCC (no Blender, no STL). */
|
||
export async function generateGltfGeometry(cadFileId: string): Promise<GenerateGltfResponse> {
|
||
const res = await api.post<GenerateGltfResponse>(`/cad/${cadFileId}/generate-gltf-geometry`)
|
||
return res.data
|
||
}
|
||
|
||
export interface ParsedObjectsResponse {
|
||
cad_file_id: string
|
||
original_name: string
|
||
processing_status: string
|
||
parsed_objects: {
|
||
dimensions_mm?: { x: number; y: number; z: number }
|
||
bbox_center_mm?: { x: number; y: number; z: number }
|
||
[key: string]: unknown
|
||
} | null
|
||
}
|
||
|
||
/** Return the parsed_objects metadata (dimensions, bbox) for a CAD file. */
|
||
export async function getParsedObjects(cadFileId: string): Promise<ParsedObjectsResponse> {
|
||
const res = await api.get<ParsedObjectsResponse>(`/cad/${cadFileId}/parsed-objects`)
|
||
return res.data
|
||
}
|
||
|
||
/** Force-reset a CAD file stuck in 'processing' to 'failed'. */
|
||
export async function resetStuckProcessing(cadFileId: string): Promise<{ status: string; message: string }> {
|
||
const res = await api.post<{ status: string; message: string }>(`/cad/${cadFileId}/reset-stuck`)
|
||
return res.data
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Part-material assignment
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export interface PartMaterialEntry {
|
||
type: 'library' | 'hex'
|
||
value: string
|
||
}
|
||
|
||
export type PartMaterialMap = Record<string, PartMaterialEntry>
|
||
|
||
interface PartMaterialsResponse {
|
||
cad_file_id: string
|
||
part_materials: PartMaterialMap | null
|
||
}
|
||
|
||
/** Return the saved part-material assignments for a CAD file (empty object if none). */
|
||
export async function getPartMaterials(cadFileId: string): Promise<PartMaterialMap> {
|
||
const res = await api.get<PartMaterialsResponse>(`/cad/${cadFileId}/part-materials`)
|
||
return res.data.part_materials ?? {}
|
||
}
|
||
|
||
/** Replace the part-material assignment map for a CAD file. Returns the updated map. */
|
||
export async function savePartMaterials(
|
||
cadFileId: string,
|
||
map: PartMaterialMap,
|
||
): Promise<PartMaterialMap> {
|
||
const res = await api.put<PartMaterialsResponse>(`/cad/${cadFileId}/part-materials`, map)
|
||
return res.data.part_materials ?? {}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Manual material overrides (partKey-keyed, Priority 4)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export interface ManualMaterialOverridesResponse {
|
||
cad_file_id: string
|
||
manual_material_overrides: Record<string, string> | null
|
||
}
|
||
|
||
/** Return the manual material overrides for a CAD file ({partKey: materialName}, empty if none). */
|
||
export async function getManualOverrides(cadFileId: string): Promise<Record<string, string>> {
|
||
const res = await api.get<ManualMaterialOverridesResponse>(
|
||
`/cad/${cadFileId}/manual-material-overrides`,
|
||
)
|
||
return res.data.manual_material_overrides ?? {}
|
||
}
|
||
|
||
/** Save manual material overrides keyed by partKey. Returns the saved map. */
|
||
export async function saveManualOverrides(
|
||
cadFileId: string,
|
||
overrides: Record<string, string>,
|
||
): Promise<Record<string, string>> {
|
||
const res = await api.put<ManualMaterialOverridesResponse>(
|
||
`/cad/${cadFileId}/manual-material-overrides`,
|
||
{ overrides },
|
||
)
|
||
return res.data.manual_material_overrides ?? {}
|
||
}
|