Add plate object count metadata and viewer display

This commit is contained in:
MisterBeardy
2026-02-03 14:14:00 -05:00
parent 1120debf75
commit 87c18e5fcf
6 changed files with 470 additions and 100 deletions
+48 -20
View File
@@ -46,6 +46,7 @@ interface Parsed3MFData {
objects: Map<string, ObjectData>;
buildItems: BuildItem[];
plateBounds: Map<number, { minX: number; minY: number; maxX: number; maxY: number }>;
plateOffsets: Map<number, { offsetX: number; offsetY: number }>;
}
// Parse 3MF transform - keep in 3MF coordinate space (Z-up)
@@ -140,6 +141,7 @@ async function parse3MF(arrayBuffer: ArrayBuffer): Promise<Parsed3MFData> {
const objects = new Map<string, ObjectData>();
const buildItems: BuildItem[] = [];
const plateBounds = new Map<number, { minX: number; minY: number; maxX: number; maxY: number }>();
const plateOffsets = new Map<number, { offsetX: number; offsetY: number }>();
const parser = new DOMParser();
// Helper to load and parse a model file from the zip
@@ -214,6 +216,8 @@ async function parse3MF(arrayBuffer: ArrayBuffer): Promise<Parsed3MFData> {
const plateEl = plateElements[i];
let plateId: number | null = null;
const metadataElements = plateEl.getElementsByTagName('metadata');
let plateOffsetX = 0;
let plateOffsetY = 0;
for (let j = 0; j < metadataElements.length; j++) {
const metaEl = metadataElements[j];
const key = metaEl.getAttribute('key');
@@ -225,9 +229,24 @@ async function parse3MF(arrayBuffer: ArrayBuffer): Promise<Parsed3MFData> {
plateId = parsed;
}
}
} else if (key === 'pos_x') {
const value = metaEl.getAttribute('value');
const parsed = value ? Number.parseFloat(value) : Number.NaN;
if (Number.isFinite(parsed)) {
plateOffsetX = parsed;
}
} else if (key === 'pos_y') {
const value = metaEl.getAttribute('value');
const parsed = value ? Number.parseFloat(value) : Number.NaN;
if (Number.isFinite(parsed)) {
plateOffsetY = parsed;
}
}
}
if (plateId == null) continue;
if (plateOffsetX !== 0 || plateOffsetY !== 0) {
plateOffsets.set(plateId, { offsetX: plateOffsetX, offsetY: plateOffsetY });
}
const modelInstances = plateEl.getElementsByTagName('model_instance');
for (let j = 0; j < modelInstances.length; j++) {
@@ -296,11 +315,11 @@ async function parse3MF(arrayBuffer: ArrayBuffer): Promise<Parsed3MFData> {
}
}
}
return { objects, buildItems, plateBounds };
return { objects, buildItems, plateBounds, plateOffsets };
}
const mainDoc = await loadModelFile(mainModelPath);
if (!mainDoc) return { objects, buildItems, plateBounds };
if (!mainDoc) return { objects, buildItems, plateBounds, plateOffsets };
// Parse objects - Bambu Studio uses components to reference external files
const objectElements = mainDoc.getElementsByTagName('object');
@@ -415,7 +434,7 @@ async function parse3MF(arrayBuffer: ArrayBuffer): Promise<Parsed3MFData> {
}
}
return { objects, buildItems, plateBounds };
return { objects, buildItems, plateBounds, plateOffsets };
}
function createGeometryFromMesh(mesh: MeshData): THREE.BufferGeometry {
@@ -697,19 +716,25 @@ export function ModelViewer({
setLoading(false);
}
// Handle resize
// Handle resize (window + container)
const handleResize = () => {
if (!container) return;
const w = container.clientWidth;
const h = container.clientHeight;
if (w === 0 || h === 0) return;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
};
window.addEventListener('resize', handleResize);
const resizeObserver = new ResizeObserver(() => {
handleResize();
});
resizeObserver.observe(container);
return () => {
window.removeEventListener('resize', handleResize);
resizeObserver.disconnect();
cancelAnimationFrame(animationId);
controls.dispose();
renderer.dispose();
@@ -750,33 +775,36 @@ export function ModelViewer({
// Always place models on the build plate (Y=0)
group.position.y = -box.min.y;
// For a selected plate, center the plate contents on the build plate
const shouldRecenter = isStlModel || parsedData!.buildItems.length === 0;
const centerOffsetX = shouldRecenter ? -center.x : 0;
const centerOffsetZ = shouldRecenter ? -center.z : 0;
const selectedPlateBounds = (!isStlModel && selectedPlateId != null && parsedData!.buildItems.length > 0)
? parsedData!.plateBounds.get(selectedPlateId)
: undefined;
const selectedPlateOffset = (!isStlModel && selectedPlateId != null)
? parsedData!.plateOffsets.get(selectedPlateId)
: undefined;
const shouldCenterOnPlate = isStlModel
|| parsedData!.buildItems.length === 0
|| (selectedPlateId != null && !selectedPlateBounds && !selectedPlateOffset);
const centerOffsetX = shouldCenterOnPlate ? -center.x : 0;
const centerOffsetZ = shouldCenterOnPlate ? -center.z : 0;
let plateOffsetX = 0;
let plateOffsetZ = 0;
if (!isStlModel && selectedPlateId != null && parsedData!.buildItems.length > 0) {
if (!isStlModel && selectedPlateId != null && parsedData!.buildItems.length > 0 && selectedPlateBounds) {
const plateBox = new THREE.Box3().setFromObject(group);
const bounds = parsedData!.plateBounds.get(selectedPlateId);
if (bounds) {
plateOffsetX = plateBox.min.x - bounds.minX;
plateOffsetZ = plateBox.min.z - bounds.minY;
} else {
const epsilon = 1e-6;
plateOffsetX = Math.floor((plateBox.min.x + epsilon) / buildVolume.x) * buildVolume.x;
plateOffsetZ = Math.floor((plateBox.min.z + epsilon) / buildVolume.y) * buildVolume.y;
}
plateOffsetX = plateBox.min.x - selectedPlateBounds.minX;
plateOffsetZ = plateBox.min.z - selectedPlateBounds.minY;
}
const plateCenterX = buildVolume.x / 2;
const plateCenterZ = buildVolume.y / 2;
if (!isStlModel && selectedPlateId != null && parsedData!.buildItems.length > 0) {
if (!isStlModel && selectedPlateId != null && parsedData!.buildItems.length > 0 && selectedPlateBounds) {
group.position.x = centerOffsetX - plateOffsetX;
group.position.z = centerOffsetZ - plateOffsetZ;
} else if (isStlModel) {
} else if (!isStlModel && selectedPlateId != null && selectedPlateOffset) {
group.position.x = centerOffsetX + (plateCenterX - selectedPlateOffset.offsetX);
group.position.z = centerOffsetZ + (plateCenterZ - selectedPlateOffset.offsetY);
} else if (shouldCenterOnPlate) {
group.position.x = centerOffsetX + plateCenterX;
group.position.z = centerOffsetZ + plateCenterZ;
} else {