tmp
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 3D Area -->
|
||||
<div class="col-md-9 h-100 position-relative">
|
||||
<div id="plater-container" class="w-100 h-100 rounded shadow-sm border border-secondary" style="overflow: hidden; background: #f8f9fa;"></div>
|
||||
<div id="plater-container" class="w-100 h-100 rounded shadow-sm border border-secondary" style="overflow: hidden; background: #f8f9fa; position: relative;"></div>
|
||||
|
||||
<!-- Parameterized Scale Input Box -->
|
||||
<div id="scale-panel" class="position-absolute top-50 start-0 translate-middle-y ms-5 ps-4 d-none" style="z-index: 10; pointer-events: none;">
|
||||
@@ -174,6 +174,13 @@ let offsetX = {{ offset_x|default(0) }};
|
||||
let offsetY = {{ offset_y|default(0) }};
|
||||
let loadedModels = [];
|
||||
let activeModel = null;
|
||||
let selectedModels = [];
|
||||
let selectionBoxDiv = document.createElement('div');
|
||||
selectionBoxDiv.id = 'selection-box';
|
||||
selectionBoxDiv.style.cssText = 'position: absolute; border: 1px dashed #007bff; background: rgba(0, 123, 255, 0.1); pointer-events: none; display: none; z-index: 100;';
|
||||
document.getElementById('plater-container').appendChild(selectionBoxDiv);
|
||||
let dragStartPoint = null;
|
||||
let isDraggingBox = false;
|
||||
const initialAddId = new URLSearchParams(window.location.search).get('add');
|
||||
|
||||
initPlater();
|
||||
@@ -271,12 +278,24 @@ function initPlater() {
|
||||
});
|
||||
transformControl.addEventListener('dragging-changed', function (event) {
|
||||
orbit.enabled = !event.value;
|
||||
if (!event.value && activeModel) {
|
||||
scene.attach(activeModel);
|
||||
transformProxy.position.copy(activeModel.getWorldPosition(new THREE.Vector3()));
|
||||
if (!event.value && selectedModels.length > 0) {
|
||||
let center = new THREE.Vector3();
|
||||
selectedModels.forEach(m => {
|
||||
scene.attach(m);
|
||||
m.geometry.computeBoundingBox();
|
||||
let box = m.geometry.boundingBox.clone();
|
||||
box.applyMatrix4(m.matrixWorld);
|
||||
center.add(box.getCenter(new THREE.Vector3()));
|
||||
});
|
||||
center.divideScalar(selectedModels.length);
|
||||
|
||||
transformProxy.position.copy(center);
|
||||
transformProxy.rotation.set(0, 0, 0);
|
||||
transformProxy.scale.set(1, 1, 1);
|
||||
transformProxy.attach(activeModel);
|
||||
|
||||
selectedModels.forEach(m => {
|
||||
transformProxy.attach(m);
|
||||
});
|
||||
}
|
||||
});
|
||||
scene.add(transformControl);
|
||||
@@ -306,7 +325,7 @@ function setTransformMode(mode) {
|
||||
document.getElementById('btn-scale').className = mode === 'scale' ? 'btn btn-primary btn-sm rounded' : 'btn btn-outline-secondary btn-sm rounded';
|
||||
document.getElementById('btn-layflat').className = 'btn btn-outline-info btn-sm rounded';
|
||||
|
||||
if (mode === 'scale' && activeModel) {
|
||||
if (mode === 'scale' && selectedModels && selectedModels.length > 0) {
|
||||
document.getElementById('scale-panel').classList.remove('d-none');
|
||||
updateScalePanel();
|
||||
} else {
|
||||
@@ -324,47 +343,56 @@ function setTransformMode(mode) {
|
||||
}
|
||||
|
||||
function updateScalePanel() {
|
||||
if (!activeModel) return;
|
||||
const v = activeModel.getWorldScale(new THREE.Vector3());
|
||||
document.getElementById('scale-x').value = v.x.toFixed(3);
|
||||
document.getElementById('scale-y').value = v.y.toFixed(3);
|
||||
document.getElementById('scale-z').value = v.z.toFixed(3);
|
||||
if (selectedModels.length === 0) return;
|
||||
|
||||
// Check if scales match
|
||||
let firstScale = selectedModels[0].getWorldScale(new THREE.Vector3());
|
||||
let allXMatch = true, allYMatch = true, allZMatch = true;
|
||||
|
||||
for (let i = 1; i < selectedModels.length; i++) {
|
||||
let v = selectedModels[i].getWorldScale(new THREE.Vector3());
|
||||
if (Math.abs(v.x - firstScale.x) > 0.001) allXMatch = false;
|
||||
if (Math.abs(v.y - firstScale.y) > 0.001) allYMatch = false;
|
||||
if (Math.abs(v.z - firstScale.z) > 0.001) allZMatch = false;
|
||||
}
|
||||
|
||||
document.getElementById('scale-x').value = allXMatch ? firstScale.x.toFixed(3) : '';
|
||||
document.getElementById('scale-y').value = allYMatch ? firstScale.y.toFixed(3) : '';
|
||||
document.getElementById('scale-z').value = allZMatch ? firstScale.z.toFixed(3) : '';
|
||||
}
|
||||
|
||||
function applyScaleInput(axis) {
|
||||
if (!activeModel) return;
|
||||
let val = parseFloat(document.getElementById('scale-' + axis).value);
|
||||
if (selectedModels.length === 0) return;
|
||||
let valStr = document.getElementById('scale-' + axis).value;
|
||||
if (valStr === '') return;
|
||||
let val = parseFloat(valStr);
|
||||
if (isNaN(val) || val <= 0.001) val = 1.0;
|
||||
|
||||
scene.attach(activeModel); // temporarily detach to operate purely on local=world scale
|
||||
|
||||
const isUniform = document.getElementById('scale-uniform').checked;
|
||||
|
||||
if (isUniform) {
|
||||
// Find previous scale
|
||||
const prev = activeModel.scale[axis];
|
||||
const ratio = val / prev;
|
||||
activeModel.scale.x *= ratio;
|
||||
activeModel.scale.y *= ratio;
|
||||
activeModel.scale.z *= ratio;
|
||||
} else {
|
||||
activeModel.scale[axis] = val;
|
||||
}
|
||||
selectedModels.forEach(m => {
|
||||
scene.attach(m);
|
||||
if (isUniform) {
|
||||
const prev = m.scale[axis];
|
||||
const ratio = val / prev;
|
||||
m.scale.x *= ratio;
|
||||
m.scale.y *= ratio;
|
||||
m.scale.z *= ratio;
|
||||
} else {
|
||||
m.scale[axis] = val;
|
||||
}
|
||||
m.updateMatrixWorld(true);
|
||||
});
|
||||
|
||||
activeModel.updateMatrixWorld(true);
|
||||
|
||||
// re-attach proxy pivot logic without modifying the actual spatial scale
|
||||
transformProxy.scale.set(1, 1, 1);
|
||||
transformProxy.attach(activeModel);
|
||||
selectedModels.forEach(m => {
|
||||
transformProxy.attach(m);
|
||||
});
|
||||
|
||||
updateScalePanel();
|
||||
}
|
||||
|
||||
function removeActiveModel() {
|
||||
if (activeModel) {
|
||||
removeModel(activeModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onKeyDown(event) {
|
||||
switch (event.key.toLowerCase()) {
|
||||
@@ -386,6 +414,15 @@ function onPointerDown(event) {
|
||||
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
dragStartPoint = { x: event.clientX, y: event.clientY };
|
||||
isDraggingBox = false;
|
||||
orbit.enabled = false;
|
||||
document.addEventListener('pointermove', onPointerMoveBox);
|
||||
document.addEventListener('pointerup', onPointerUpBox);
|
||||
}
|
||||
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(loadedModels, true);
|
||||
@@ -424,30 +461,146 @@ function onPointerDown(event) {
|
||||
|
||||
// Exit lay flat mode and reset to translate
|
||||
setTransformMode('translate');
|
||||
selectModel(obj);
|
||||
selectModels([obj]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (intersects.length > 0) {
|
||||
selectModel(intersects[0].object);
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
toggleModelSelection(intersects[0].object);
|
||||
} else {
|
||||
selectModels([intersects[0].object]);
|
||||
}
|
||||
} else {
|
||||
selectModel(null);
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
selectModels([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectModel(model) {
|
||||
if (activeModel && activeModel !== model) {
|
||||
scene.attach(activeModel);
|
||||
function onPointerMoveBox(event) {
|
||||
if (!dragStartPoint) return;
|
||||
const dx = Math.abs(event.clientX - dragStartPoint.x);
|
||||
const dy = Math.abs(event.clientY - dragStartPoint.y);
|
||||
if (dx > 5 || dy > 5) {
|
||||
isDraggingBox = true;
|
||||
|
||||
const container = document.getElementById('plater-container');
|
||||
const rect = container.getBoundingClientRect();
|
||||
|
||||
const startX = dragStartPoint.x - rect.left;
|
||||
const startY = dragStartPoint.y - rect.top;
|
||||
const currentX = event.clientX - rect.left;
|
||||
const currentY = event.clientY - rect.top;
|
||||
|
||||
selectionBoxDiv.style.display = 'block';
|
||||
selectionBoxDiv.style.left = Math.min(currentX, startX) + 'px';
|
||||
selectionBoxDiv.style.top = Math.min(currentY, startY) + 'px';
|
||||
selectionBoxDiv.style.width = Math.abs(currentX - startX) + 'px';
|
||||
selectionBoxDiv.style.height = Math.abs(currentY - startY) + 'px';
|
||||
}
|
||||
activeModel = model;
|
||||
if (model) {
|
||||
scene.attach(model);
|
||||
transformProxy.position.copy(model.getWorldPosition(new THREE.Vector3()));
|
||||
}
|
||||
|
||||
function onPointerUpBox(event) {
|
||||
document.removeEventListener('pointermove', onPointerMoveBox);
|
||||
document.removeEventListener('pointerup', onPointerUpBox);
|
||||
orbit.enabled = true;
|
||||
|
||||
if (isDraggingBox) {
|
||||
selectionBoxDiv.style.display = 'none';
|
||||
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
const minX = Math.min(dragStartPoint.x, event.clientX) - rect.left;
|
||||
const maxX = Math.max(dragStartPoint.x, event.clientX) - rect.left;
|
||||
const minY = Math.min(dragStartPoint.y, event.clientY) - rect.top;
|
||||
const maxY = Math.max(dragStartPoint.y, event.clientY) - rect.top;
|
||||
|
||||
let newSelection = [...selectedModels];
|
||||
|
||||
loadedModels.forEach(m => {
|
||||
m.geometry.computeBoundingBox();
|
||||
let box = m.geometry.boundingBox.clone();
|
||||
box.applyMatrix4(m.matrixWorld);
|
||||
|
||||
// Project 8 corners
|
||||
const corners = [
|
||||
new THREE.Vector3(box.min.x, box.min.y, box.min.z),
|
||||
new THREE.Vector3(box.max.x, box.min.y, box.min.z),
|
||||
new THREE.Vector3(box.min.x, box.max.y, box.min.z),
|
||||
new THREE.Vector3(box.max.x, box.max.y, box.min.z),
|
||||
new THREE.Vector3(box.min.x, box.min.y, box.max.z),
|
||||
new THREE.Vector3(box.max.x, box.min.y, box.max.z),
|
||||
new THREE.Vector3(box.min.x, box.max.y, box.max.z),
|
||||
new THREE.Vector3(box.max.x, box.max.y, box.max.z)
|
||||
];
|
||||
|
||||
let inside = false;
|
||||
corners.forEach(v => {
|
||||
v.project(camera);
|
||||
let sx = (v.x * .5 + .5) * rect.width;
|
||||
let sy = (v.y * -.5 + .5) * rect.height;
|
||||
if (sx >= minX && sx <= maxX && sy >= minY && sy <= maxY) {
|
||||
inside = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (inside && !newSelection.includes(m)) {
|
||||
newSelection.push(m);
|
||||
}
|
||||
});
|
||||
selectModels(newSelection);
|
||||
} else if (dragStartPoint && !isDraggingBox) {
|
||||
// Just a ctrl+click that missed logic is handled by raycaster above, but we have to ensure no double toggle
|
||||
}
|
||||
dragStartPoint = null;
|
||||
isDraggingBox = false;
|
||||
}
|
||||
|
||||
|
||||
function toggleModelSelection(model) {
|
||||
let newSel = [...selectedModels];
|
||||
if (newSel.includes(model)) {
|
||||
newSel = newSel.filter(m => m !== model);
|
||||
} else {
|
||||
newSel.push(model);
|
||||
}
|
||||
selectModels(newSel);
|
||||
}
|
||||
|
||||
function selectModels(models) {
|
||||
selectedModels.forEach(m => {
|
||||
scene.attach(m);
|
||||
m.material.color.setHex(0xcccccc);
|
||||
});
|
||||
|
||||
selectedModels = models;
|
||||
activeModel = selectedModels.length > 0 ? selectedModels[selectedModels.length - 1] : null;
|
||||
|
||||
if (selectedModels.length > 0) {
|
||||
// compute joint center
|
||||
let center = new THREE.Vector3();
|
||||
let count = 0;
|
||||
selectedModels.forEach(m => {
|
||||
m.material.color.setHex(0x0d6efd);
|
||||
scene.attach(m); // ensure in world
|
||||
m.geometry.computeBoundingBox();
|
||||
let box = m.geometry.boundingBox.clone();
|
||||
box.applyMatrix4(m.matrixWorld);
|
||||
center.add(box.getCenter(new THREE.Vector3()));
|
||||
count++;
|
||||
});
|
||||
center.divideScalar(count);
|
||||
|
||||
transformProxy.position.copy(center);
|
||||
transformProxy.rotation.set(0, 0, 0);
|
||||
transformProxy.scale.set(1, 1, 1);
|
||||
transformProxy.attach(model);
|
||||
|
||||
selectedModels.forEach(m => {
|
||||
transformProxy.attach(m);
|
||||
});
|
||||
|
||||
transformControl.attach(transformProxy);
|
||||
if(transformControl.getMode() === 'scale') {
|
||||
document.getElementById('scale-panel').classList.remove('d-none');
|
||||
@@ -459,14 +612,32 @@ function selectModel(model) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeModel(model) {
|
||||
if (activeModel === model) {
|
||||
transformControl.detach();
|
||||
scene.attach(model);
|
||||
activeModel = null;
|
||||
// Keep a backward compatible selectModel definition for single cases
|
||||
function selectModel(model) {
|
||||
if (model) {
|
||||
selectModels([model]);
|
||||
} else {
|
||||
selectModels([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeModel(model) {
|
||||
if (selectedModels.includes(model)) {
|
||||
selectedModels = selectedModels.filter(m => m !== model);
|
||||
if (selectedModels.length === 0) transformControl.detach();
|
||||
}
|
||||
scene.attach(model);
|
||||
scene.remove(model);
|
||||
loadedModels = loadedModels.filter(m => m !== model);
|
||||
activeModel = selectedModels.length > 0 ? selectedModels[0] : null;
|
||||
}
|
||||
|
||||
function removeActiveModel() {
|
||||
if (selectedModels.length > 0) {
|
||||
[...selectedModels].forEach(m => removeModel(m));
|
||||
selectModels([]);
|
||||
}
|
||||
}
|
||||
|
||||
function clearPlate() {
|
||||
@@ -476,12 +647,29 @@ function clearPlate() {
|
||||
scene.remove(m);
|
||||
});
|
||||
loadedModels = [];
|
||||
selectedModels = [];
|
||||
activeModel = null;
|
||||
}
|
||||
|
||||
function addModelToPlate(btnElement, fileId, url, name, status) {
|
||||
let matrixData = btnElement ? btnElement.getAttribute('data-matrix') : null;
|
||||
|
||||
if (matrixData) {
|
||||
try {
|
||||
let data = JSON.parse(matrixData);
|
||||
if (data.settings) {
|
||||
if (data.settings.infill) document.getElementById('infill-density').value = data.settings.infill;
|
||||
if (data.settings.support) {
|
||||
let supportSelect = document.getElementById('support-type');
|
||||
supportSelect.value = data.settings.support;
|
||||
supportSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
if (data.settings.support_pattern) document.getElementById('support-pattern').value = data.settings.support_pattern;
|
||||
if (data.settings.quality) document.getElementById('quality').value = data.settings.quality;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (matrixData && matrixData.includes('"is_composite"')) {
|
||||
try {
|
||||
let comp = JSON.parse(matrixData);
|
||||
@@ -538,7 +726,7 @@ function loadSTL(fileId, url, name, status, matrixData, callback) {
|
||||
const minZ = geometry.boundingBox.min.z;
|
||||
geometry.translate(-center.x, -center.y, -minZ);
|
||||
|
||||
const material = new THREE.MeshPhongMaterial({ color: 0x0d6efd, specular: 0x111111, shininess: 200 });
|
||||
const material = new THREE.MeshPhongMaterial({ color: 0xcccccc, specular: 0x111111, shininess: 200 });
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.userData = {
|
||||
fileId: fileId,
|
||||
@@ -547,9 +735,15 @@ function loadSTL(fileId, url, name, status, matrixData, callback) {
|
||||
geomTrans: new THREE.Matrix4().makeTranslation(-center.x, -center.y, -minZ)
|
||||
};
|
||||
|
||||
if (matrixData && matrixData.trim() !== '' && matrixData !== 'None' && !matrixData.includes('"is_composite"')) {
|
||||
if (matrixData && matrixData.trim() !== '' && matrixData !== 'None') {
|
||||
try {
|
||||
let mArray = JSON.parse(matrixData);
|
||||
// Skip if it actually is a composite (handled by addModelToPlate)
|
||||
if (mArray && mArray.is_composite === true) return;
|
||||
|
||||
if (mArray && !Array.isArray(mArray) && mArray.matrix) {
|
||||
mArray = mArray.matrix;
|
||||
}
|
||||
let savedMatrix = new THREE.Matrix4().fromArray(mArray);
|
||||
savedMatrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
|
||||
} catch (e) {
|
||||
@@ -622,7 +816,7 @@ function animate() {
|
||||
}
|
||||
|
||||
function mergeAndSlice() {
|
||||
selectModel(null); // Detach any active model to bake transformProxy world coordinates into its local matrix properties
|
||||
selectModels([]); // Detach any active model to bake transformProxy world coordinates into its local matrix properties
|
||||
|
||||
if (loadedModels.length === 0) {
|
||||
alert("{{ _('Please add at least one model to the build plate.') }}");
|
||||
@@ -728,8 +922,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('add-model-btn-' + addId);
|
||||
if (btn) {
|
||||
let matrixData = btn.getAttribute('data-matrix');
|
||||
if (matrixData && matrixData.includes('"is_composite"')) {
|
||||
window.isCompositeEdit = true;
|
||||
if (matrixData) {
|
||||
try {
|
||||
let d = JSON.parse(matrixData);
|
||||
if (d && d.is_composite === true) {
|
||||
window.isCompositeEdit = true;
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
btn.click();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user