Implement translate duplication

This commit is contained in:
Nettika 2026-01-30 00:03:57 -08:00
parent af4e1aedff
commit 93c55899ec
No known key found for this signature in database
3 changed files with 53 additions and 2 deletions

View file

@ -145,8 +145,8 @@ A step-by-step checklist for porting MatterControl's design features to a Vue +
- [x] Left-click object: center/zoom camera on object - [x] Left-click object: center/zoom camera on object
### Shift Modifier Behaviors ### Shift Modifier Behaviors
- [ ] Click with Shift: add to selection (already in Phase 4) - [x] Click with Shift: add to selection (already in Phase 4)
- [ ] Drag translate handle with Shift: duplicate selection along axis - [x] Drag translate handle with Shift: duplicate selection along axis
--- ---

View file

@ -951,6 +951,27 @@ function onCanvasMouseDown(event: MouseEvent) {
dragState.isRightDrag = event.button === 2 || event.button === 1 dragState.isRightDrag = event.button === 2 || event.button === 1
dragState.startMousePosition.copy(mouse) dragState.startMousePosition.copy(mouse)
// Shift+drag: duplicate selection first, then move duplicates
if (isShiftPressed) {
const currentSelection = [...sceneStore.selectedObjects]
const duplicatedIds: string[] = []
for (const obj of currentSelection) {
const duplicate = sceneStore.duplicateObject(obj.id)
if (duplicate) {
duplicatedIds.push(duplicate.id)
}
}
// Select the duplicates (originals stay in place)
if (duplicatedIds.length > 0) {
sceneStore.clearSelection()
for (const id of duplicatedIds) {
sceneStore.addToSelection(id)
}
}
}
// Store original positions and calculate center // Store original positions and calculate center
const selectedMeshes = sceneStore.selectedObjects.map((o) => o.mesh) const selectedMeshes = sceneStore.selectedObjects.map((o) => o.mesh)
if (selectedMeshes.length > 0) { if (selectedMeshes.length > 0) {

View file

@ -138,6 +138,35 @@ export const useSceneStore = defineStore('scene', () => {
selectedIds.value = new Set() selectedIds.value = new Set()
} }
function duplicateObject(id: string): SceneObject | null {
const original = objects.value.find((o) => o.id === id)
if (!original || !threeScene) return null
// Clone the mesh with geometry and material
const clonedMesh = original.mesh.clone()
clonedMesh.geometry = original.mesh.geometry.clone()
if (original.mesh.material instanceof THREE.Material) {
clonedMesh.material = original.mesh.material.clone()
}
// Generate a new name based on the original
const baseName = original.name.replace(/ \d+$/, '') // Remove trailing number if present
const objectName = generateName(baseName)
const sceneObject: SceneObject = {
id: generateId(),
name: objectName,
mesh: clonedMesh,
visible: original.visible,
locked: false,
}
threeScene.add(clonedMesh)
objects.value = [...objects.value, sceneObject]
return sceneObject
}
const objectList = computed(() => objects.value) const objectList = computed(() => objects.value)
const objectCount = computed(() => objects.value.length) const objectCount = computed(() => objects.value.length)
const selectedObjects = computed(() => objects.value.filter((o) => selectedIds.value.has(o.id))) const selectedObjects = computed(() => objects.value.filter((o) => selectedIds.value.has(o.id)))
@ -164,5 +193,6 @@ export const useSceneStore = defineStore('scene', () => {
toggleSelection, toggleSelection,
selectAll, selectAll,
clearSelection, clearSelection,
duplicateObject,
} }
}) })