From a7862cf4032cc9b52aa2ff35fb1979c53331fba2 Mon Sep 17 00:00:00 2001 From: Ella-AWS <111664173+EllaCoat@users.noreply.github.com> Date: Sun, 23 Aug 2026 04:50:12 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Blueprint=20=E3=81=AE=E5=AD=A4?= =?UTF-8?q?=E7=AB=8B=20Bone=20=E3=81=AB=E3=82=88=E3=82=8B=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E8=A1=9D=E7=AA=81=E3=82=92=E9=98=B2=E3=81=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/formats/blueprint/codec.ts | 12 ++++++-- src/formats/blueprint/outlinerGroups.ts | 31 +++++++++++++++++++ src/tests/blueprintOutlinerGroups.test.ts | 36 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/formats/blueprint/outlinerGroups.ts create mode 100644 src/tests/blueprintOutlinerGroups.test.ts diff --git a/src/formats/blueprint/codec.ts b/src/formats/blueprint/codec.ts index fc07e69e..ad6c9145 100644 --- a/src/formats/blueprint/codec.ts +++ b/src/formats/blueprint/codec.ts @@ -11,6 +11,7 @@ import { localize as translate } from '../../util/lang' import { sanitizeStorageKey } from '../../util/minecraftUtil' import { Variant } from '../../variants' import { upgradeAnimatedJavaBlueprint } from './dfu' +import { groupsReferencedByOutliner } from './outlinerGroups' import * as blueprintSettings from './settings' // region Codec @@ -150,7 +151,10 @@ export const BLUEPRINT_CODEC = registerDeletableHandlerPatch({ } if (model.groups) { - for (const template of model.groups) { + for (const template of groupsReferencedByOutliner( + model.groups, + model.outliner + )) { // @ts-expect-error - missing UUID arg new Group(template, template.uuid).init() } @@ -291,13 +295,15 @@ export const BLUEPRINT_CODEC = registerDeletableHandlerPatch({ model.elements.push(element.getSaveCopy?.(!!model.meta)) } + const outliner = Outliner.toJSON() + model.groups = [] - for (const group of Group.all) { + for (const group of groupsReferencedByOutliner(Group.all, outliner)) { // @ts-expect-error - missing arg model.groups.push(group.getSaveCopy(false)) } - model.outliner = Outliner.toJSON() + model.outliner = outliner model.textures = [] for (const texture of Texture.all) { diff --git a/src/formats/blueprint/outlinerGroups.ts b/src/formats/blueprint/outlinerGroups.ts new file mode 100644 index 00000000..685ae381 --- /dev/null +++ b/src/formats/blueprint/outlinerGroups.ts @@ -0,0 +1,31 @@ +interface GroupWithUUID { + uuid?: unknown +} + +interface OutlinerGroupEntry { + uuid?: unknown + children?: unknown + content?: unknown +} + +export function groupsReferencedByOutliner( + groups: readonly T[], + outliner: readonly unknown[] | undefined +): T[] { + if (outliner === undefined) return [...groups] + + const referencedUUIDs = new Set() + function visit(entries: readonly unknown[]) { + for (const entry of entries) { + if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) continue + + const group = entry as OutlinerGroupEntry + if (typeof group.uuid === 'string') referencedUUIDs.add(group.uuid) + if (Array.isArray(group.children)) visit(group.children) + if (Array.isArray(group.content)) visit(group.content) + } + } + + visit(outliner) + return groups.filter(group => typeof group.uuid === 'string' && referencedUUIDs.has(group.uuid)) +} diff --git a/src/tests/blueprintOutlinerGroups.test.ts b/src/tests/blueprintOutlinerGroups.test.ts new file mode 100644 index 00000000..0d830eca --- /dev/null +++ b/src/tests/blueprintOutlinerGroups.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import { groupsReferencedByOutliner } from '../formats/blueprint/outlinerGroups' + +describe('groupsReferencedByOutliner', () => { + it('collects nested children and legacy content entries while ignoring UUID strings', () => { + const groups = [ + { uuid: 'root' }, + { uuid: 'child' }, + { uuid: 'legacy' }, + { uuid: 'string-entry' }, + { uuid: 'orphan' }, + ] + + const referenced = groupsReferencedByOutliner(groups, [ + { + uuid: 'root', + children: [{ uuid: 'child', content: [{ uuid: 'legacy' }, 'string-entry'] }], + }, + 'string-entry', + ]) + + expect(referenced.map(group => group.uuid)).toEqual(['root', 'child', 'legacy']) + }) + + it('keeps all groups when outliner is undefined', () => { + const groups = [{ uuid: 'first' }, { uuid: 'second' }] + + expect(groupsReferencedByOutliner(groups, undefined)).toEqual(groups) + }) + + it('excludes all groups for a defined empty outliner', () => { + const groups = [{ uuid: 'orphan' }] + + expect(groupsReferencedByOutliner(groups, [])).toEqual([]) + }) +})