From 3505efed8ce32aaab8eac19fab2d85a969357f26 Mon Sep 17 00:00:00 2001 From: L1Z0 <2grif484d@mozmail.com> Date: Mon, 28 Sep 2026 19:58:29 +0200 Subject: [PATCH] block requirements --- AnimationDirector.js | 15 ++++++++---- test/block-requirements.test.js | 42 +++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/AnimationDirector.js b/AnimationDirector.js index 3b64c64..638da22 100644 --- a/AnimationDirector.js +++ b/AnimationDirector.js @@ -785,10 +785,13 @@ function refreshBlockGuideFaces() { const roots = Group.all.filter((group) => group.name.startsWith(BLOCK_GUIDE_PREFIX) && !isBlockRequirementGuide(group.parent)); + const elements = []; for (const root of roots) { - hideInternalBlockGuideFaces(Cube.all.filter((cube) => cube.getAllAncestors().includes(root))); + const cubes = Cube.all.filter((cube) => cube.getAllAncestors().includes(root)); + hideInternalBlockGuideFaces(cubes); + elements.push(...cubes); } - if (roots.length) Canvas.updateAllFaces(); + if (elements.length) Canvas.updateView({ elements, element_aspects: { faces: true, uv: true } }); } function rebuildBlockRequirementGuides(animationId, entry) { @@ -826,7 +829,9 @@ } Project[BLOCK_REQUIREMENTS_PROPERTY] = JSON.stringify(updated); hideInternalBlockGuideFaces(createdElements); - Canvas.updateAll(); + // Rebuilding every cube would reset forced actor materials in single-texture formats. + Canvas.updateView({ elements: createdElements, groups: createdGroups, + element_aspects: { transform: true, geometry: true, faces: true, uv: true, visibility: true } }); for (const cube of createdElements) applyBlockRequirementGuideMaterial(cube); Undo.finishEdit(entry ? 'Set block requirement' : 'Remove block requirement', { outliner: true, elements: createdElements, groups: createdGroups, block_requirement_edit: true }); @@ -914,7 +919,7 @@ blockRequirementPanel = new Panel('animationdirector_block_requirements', { name: 'Block Requirements', icon: 'view_in_ar', condition: () => !!Project, growable: true, resizable: true, min_height: 240, expand_button: true, - default_position: { slot: 'right_bar', height: 520, float_size: [360, 620] }, + default_position: { slot: 'left_bar', height: 520, float_size: [360, 620] }, component: { data() { return { projectId: null, animationId: '', draftId: '', ids: [], form: {}, values: {}, error: '', status: '', dirty: false }; }, computed: { @@ -1020,7 +1025,7 @@ const selected = parseAfwStageAnimationName(getSelectedAnimation()); const ids = blockRequirementAnimationIds(); view.load(selected?.id || (view.projectId === Project.uuid && view.animationId) || ids[0] || ''); - if (blockRequirementPanel.slot === 'hidden') blockRequirementPanel.moveTo('right_bar'); + if (blockRequirementPanel.slot === 'hidden') blockRequirementPanel.moveTo('left_bar'); blockRequirementPanel.folded = false; blockRequirementPanel.getContainerPanel().selectTab(blockRequirementPanel); blockRequirementPanel.update(); diff --git a/test/block-requirements.test.js b/test/block-requirements.test.js index 52addd9..3960bf2 100644 --- a/test/block-requirements.test.js +++ b/test/block-requirements.test.js @@ -17,6 +17,7 @@ function harness() { const events = new Map(); const previewEvents = new Map(); const timers = new Map(); + const previewUpdates = []; let sequence = 0; class Group { static all = []; @@ -57,8 +58,20 @@ function harness() { MenuBar: { menus: { tools: { addAction() {} } }, update() {} }, THREE: { MeshBasicMaterial: class { constructor(options) { Object.assign(this, options); } }, DoubleSide: 2 }, Canvas: { - updateAll() { for (const cube of Cube.all) previewEvents.get('update_faces')?.({ element: cube }); }, + updateAll() { + for (const cube of Cube.all) { + cube.mesh.material = 'selected-shared-texture'; + previewEvents.get('update_faces')?.({ element: cube }); + } + }, updateAllFaces() { this.updateAll(); }, + updateView(options) { + previewUpdates.push(options); + for (const cube of options.elements || []) { + cube.mesh.material = 'selected-shared-texture'; + if (options.element_aspects?.faces) previewEvents.get('update_faces')?.({ element: cube }); + } + }, }, Undo: { initEdit(aspects) { edits.push({ before: project.animationdirector_block_requirements, aspects }); }, @@ -72,7 +85,7 @@ function harness() { console, }; vm.runInNewContext(source, context); - return { ...context.blockTest, context, project, dialogs, exports, errors, edits, events, previewEvents, timers, Group, Cube }; + return { ...context.blockTest, context, project, dialogs, exports, errors, edits, events, previewEvents, timers, previewUpdates, Group, Cube }; } function wallForm(overrides = {}) { @@ -80,6 +93,31 @@ function wallForm(overrides = {}) { facing: '', clearance_width: 1, clearance_height: 2, clearance_depth: 1, wall_thickness: 1, ...overrides }; } +test('building, editing, refreshing and removing guides never resets actor or folder texture overrides', () => { + const h = harness(); + const actor = new h.Group({ name: 'actor1_body' }); + actor.init(); actor.addTo('root'); + const cubes = ['actor-texture', 'folder-override-texture'].map((material, index) => { + const cube = new h.Cube({ name: `actor1_cube${index}`, from: [0, 0, 0], to: [16, 16, 16] }); + cube.init(); cube.addTo(actor); + cube.mesh.material = material; + for (const face of Object.values(cube.faces)) face.texture = material; + return cube; + }); + const before = cubes.map((cube) => ({ material: cube.mesh.material, faces: JSON.stringify(cube.faces) })); + h.context.registration.onload(); + h.rebuildBlockRequirementGuides('test', h.readBlockRequirementForm(wallForm())); + h.rebuildBlockRequirementGuides('test', h.readBlockRequirementForm(wallForm({ width_min: 3, clearance_depth: 2 }))); + h.refreshBlockGuideFaces(); + h.rebuildBlockRequirementGuides('test', null); + assert.deepEqual(cubes.map((cube) => ({ material: cube.mesh.material, faces: JSON.stringify(cube.faces) })), before); + for (const update of h.previewUpdates) { + assert.ok(update.elements.every((cube) => !cubes.includes(cube))); + assert.ok((update.groups || []).every((group) => group !== actor)); + } + assert.equal(h.previewUpdates.length, 4); +}); + test('guide cubes omit internal faces, preserve colored boundaries and restore newly exposed faces', () => { const h = harness(); const make = (kind, from, to) => new h.Cube({ name: `__ad_block_requirement_${kind}_test`, from, to });