Skip to content

Commit 5d5c49b

Browse files
feat(ui): merge down deletes merged entities
1 parent b0f75c5 commit 5d5c49b

File tree

3 files changed

+76
-17
lines changed

3 files changed

+76
-17
lines changed

invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsMergeDown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const CanvasEntityMenuItemsMergeDown = memo(() => {
1818
if (entityIdentifierBelowThisOne === null) {
1919
return;
2020
}
21-
canvasManager.compositor.mergeByEntityIdentifiers([entityIdentifierBelowThisOne, entityIdentifier]);
21+
canvasManager.compositor.mergeByEntityIdentifiers([entityIdentifierBelowThisOne, entityIdentifier], true);
2222
}, [canvasManager.compositor, entityIdentifier, entityIdentifierBelowThisOne]);
2323

2424
return (

invokeai/frontend/web/src/features/controlLayers/konva/CanvasCompositorModule.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getImageDataTransparency,
1010
getPrefixedId,
1111
getRectUnion,
12+
mapId,
1213
previewBlob,
1314
} from 'features/controlLayers/konva/util';
1415
import {
@@ -317,18 +318,23 @@ export class CanvasCompositorModule extends CanvasModuleBase {
317318
* All entities must have the same type.
318319
*
319320
* @param entityIdentifiers The entity identifiers to merge
321+
* @param deleteMergedEntities Whether to delete the merged entities after creating the new merged entity
320322
* @returns A promise that resolves to the image DTO, or null if the merge failed
321323
*/
322324
mergeByEntityIdentifiers = async <T extends CanvasRenderableEntityIdentifier>(
323-
entityIdentifiers: T[]
325+
entityIdentifiers: T[],
326+
deleteMergedEntities: boolean
324327
): Promise<ImageDTO | null> => {
325328
if (entityIdentifiers.length <= 1) {
326329
this.log.warn({ entityIdentifiers }, 'Cannot merge less than 2 entities');
327330
return null;
328331
}
329332
const type = entityIdentifiers[0]?.type;
330333
assert(type, 'Cannot merge entities with no type (this should never happen)');
334+
331335
const adapters = this.manager.getAdapters(entityIdentifiers);
336+
assert(adapters.length === entityIdentifiers.length, 'Failed to get all adapters for entity identifiers');
337+
332338
const rect = this.getRectOfAdapters(adapters);
333339

334340
const compositingOptions: CompositingOptions = {
@@ -353,6 +359,7 @@ export class CanvasCompositorModule extends CanvasModuleBase {
353359
objects: [imageDTOToImageObject(result.value)],
354360
position: { x: Math.floor(rect.x), y: Math.floor(rect.y) },
355361
},
362+
mergedEntitiesToDelete: deleteMergedEntities ? entityIdentifiers.map(mapId) : [],
356363
};
357364

358365
switch (type) {
@@ -405,7 +412,7 @@ export class CanvasCompositorModule extends CanvasModuleBase {
405412

406413
const entityIdentifiers = entities.map(getEntityIdentifier);
407414

408-
return this.mergeByEntityIdentifiers(entityIdentifiers);
415+
return this.mergeByEntityIdentifiers(entityIdentifiers, false);
409416
};
410417

411418
/**

invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,29 @@ export const canvasSlice = createSlice({
123123
id: string;
124124
overrides?: Partial<CanvasRasterLayerState>;
125125
isSelected?: boolean;
126+
mergedEntitiesToDelete?: string[];
126127
}>
127128
) => {
128-
const { id, overrides, isSelected } = action.payload;
129+
const { id, overrides, isSelected, mergedEntitiesToDelete = [] } = action.payload;
129130
const entityState = getRasterLayerState(id, overrides);
130131

131132
state.rasterLayers.entities.push(entityState);
132133

133-
if (isSelected) {
134+
if (mergedEntitiesToDelete.length > 0) {
135+
state.rasterLayers.entities = state.rasterLayers.entities.filter(
136+
(entity) => !mergedEntitiesToDelete.includes(entity.id)
137+
);
138+
}
139+
140+
if (isSelected || mergedEntitiesToDelete.length > 0) {
134141
state.selectedEntityIdentifier = getEntityIdentifier(entityState);
135142
}
136143
},
137-
prepare: (payload: { overrides?: Partial<CanvasRasterLayerState>; isSelected?: boolean }) => ({
144+
prepare: (payload: {
145+
overrides?: Partial<CanvasRasterLayerState>;
146+
isSelected?: boolean;
147+
mergedEntitiesToDelete?: string[];
148+
}) => ({
138149
payload: { ...payload, id: getPrefixedId('raster_layer') },
139150
}),
140151
},
@@ -261,19 +272,34 @@ export const canvasSlice = createSlice({
261272
controlLayerAdded: {
262273
reducer: (
263274
state,
264-
action: PayloadAction<{ id: string; overrides?: Partial<CanvasControlLayerState>; isSelected?: boolean }>
275+
action: PayloadAction<{
276+
id: string;
277+
overrides?: Partial<CanvasControlLayerState>;
278+
isSelected?: boolean;
279+
mergedEntitiesToDelete?: string[];
280+
}>
265281
) => {
266-
const { id, overrides, isSelected } = action.payload;
282+
const { id, overrides, isSelected, mergedEntitiesToDelete = [] } = action.payload;
267283

268284
const entityState = getControlLayerState(id, overrides);
269285

270286
state.controlLayers.entities.push(entityState);
271287

272-
if (isSelected) {
288+
if (mergedEntitiesToDelete.length > 0) {
289+
state.controlLayers.entities = state.controlLayers.entities.filter(
290+
(entity) => !mergedEntitiesToDelete.includes(entity.id)
291+
);
292+
}
293+
294+
if (isSelected || mergedEntitiesToDelete.length > 0) {
273295
state.selectedEntityIdentifier = getEntityIdentifier(entityState);
274296
}
275297
},
276-
prepare: (payload: { overrides?: Partial<CanvasControlLayerState>; isSelected?: boolean }) => ({
298+
prepare: (payload: {
299+
overrides?: Partial<CanvasControlLayerState>;
300+
isSelected?: boolean;
301+
mergedEntitiesToDelete?: string[];
302+
}) => ({
277303
payload: { ...payload, id: getPrefixedId('control_layer') },
278304
}),
279305
},
@@ -585,19 +611,34 @@ export const canvasSlice = createSlice({
585611
rgAdded: {
586612
reducer: (
587613
state,
588-
action: PayloadAction<{ id: string; overrides?: Partial<CanvasRegionalGuidanceState>; isSelected?: boolean }>
614+
action: PayloadAction<{
615+
id: string;
616+
overrides?: Partial<CanvasRegionalGuidanceState>;
617+
isSelected?: boolean;
618+
mergedEntitiesToDelete?: string[];
619+
}>
589620
) => {
590-
const { id, overrides, isSelected } = action.payload;
621+
const { id, overrides, isSelected, mergedEntitiesToDelete = [] } = action.payload;
591622

592623
const entityState = getRegionalGuidanceState(id, overrides);
593624

594625
state.regionalGuidance.entities.push(entityState);
595626

596-
if (isSelected) {
627+
if (mergedEntitiesToDelete.length > 0) {
628+
state.regionalGuidance.entities = state.regionalGuidance.entities.filter(
629+
(entity) => !mergedEntitiesToDelete.includes(entity.id)
630+
);
631+
}
632+
633+
if (isSelected || mergedEntitiesToDelete.length > 0) {
597634
state.selectedEntityIdentifier = getEntityIdentifier(entityState);
598635
}
599636
},
600-
prepare: (payload?: { overrides?: Partial<CanvasRegionalGuidanceState>; isSelected?: boolean }) => ({
637+
prepare: (payload?: {
638+
overrides?: Partial<CanvasRegionalGuidanceState>;
639+
isSelected?: boolean;
640+
mergedEntitiesToDelete?: string[];
641+
}) => ({
601642
payload: { ...payload, id: getPrefixedId('regional_guidance') },
602643
}),
603644
},
@@ -812,19 +853,30 @@ export const canvasSlice = createSlice({
812853
id: string;
813854
overrides?: Partial<CanvasInpaintMaskState>;
814855
isSelected?: boolean;
856+
mergedEntitiesToDelete?: string[];
815857
}>
816858
) => {
817-
const { id, overrides, isSelected } = action.payload;
859+
const { id, overrides, isSelected, mergedEntitiesToDelete = [] } = action.payload;
818860

819861
const entityState = getInpaintMaskState(id, overrides);
820862

821863
state.inpaintMasks.entities.push(entityState);
822864

823-
if (isSelected) {
865+
if (mergedEntitiesToDelete.length > 0) {
866+
state.inpaintMasks.entities = state.inpaintMasks.entities.filter(
867+
(entity) => !mergedEntitiesToDelete.includes(entity.id)
868+
);
869+
}
870+
871+
if (isSelected || mergedEntitiesToDelete.length > 0) {
824872
state.selectedEntityIdentifier = getEntityIdentifier(entityState);
825873
}
826874
},
827-
prepare: (payload?: { overrides?: Partial<CanvasInpaintMaskState>; isSelected?: boolean }) => ({
875+
prepare: (payload?: {
876+
overrides?: Partial<CanvasInpaintMaskState>;
877+
isSelected?: boolean;
878+
mergedEntitiesToDelete?: string[];
879+
}) => ({
828880
payload: { ...payload, id: getPrefixedId('inpaint_mask') },
829881
}),
830882
},

0 commit comments

Comments
 (0)