mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Add plate object count metadata and viewer display
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user