diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index ad245191b44..74b36c913d7 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -2180,7 +2180,8 @@
"rgReferenceImagesNotSupported": "regional Reference Images not supported for selected base model",
"rgAutoNegativeNotSupported": "Auto-Negative not supported for selected base model",
"rgNoRegion": "no region drawn",
- "fluxFillIncompatibleWithControlLoRA": "Control LoRA is not compatible with FLUX Fill"
+ "fluxFillIncompatibleWithControlLoRA": "Control LoRA is not compatible with FLUX Fill",
+ "bboxHidden": "Bounding box is hidden (shift+o to toggle)"
},
"errors": {
"unableToFindImage": "Unable to find image",
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx
new file mode 100644
index 00000000000..32b51e888e2
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx
@@ -0,0 +1,24 @@
+import { Alert, AlertIcon, AlertTitle } from '@invoke-ai/ui-library';
+import { useStore } from '@nanostores/react';
+import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export const CanvasAlertsBboxVisibility = memo(() => {
+ const { t } = useTranslation();
+ const canvasManager = useCanvasManager();
+ const isBboxHidden = useStore(canvasManager.tool.tools.bbox.$isBboxHidden);
+
+ if (!isBboxHidden) {
+ return null;
+ }
+
+ return (
+
+
+ {t('controlLayers.warnings.bboxHidden')}
+
+ );
+});
+
+CanvasAlertsBboxVisibility.displayName = 'CanvasAlertsBboxVisibility';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx
index 1e7affd2bd1..2781f6e58f3 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx
@@ -15,6 +15,7 @@ import { useCanvasEntityQuickSwitchHotkey } from 'features/controlLayers/hooks/u
import { useCanvasFilterHotkey } from 'features/controlLayers/hooks/useCanvasFilterHotkey';
import { useCanvasInvertMaskHotkey } from 'features/controlLayers/hooks/useCanvasInvertMaskHotkey';
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
+import { useCanvasToggleBboxHotkey } from 'features/controlLayers/hooks/useCanvasToggleBboxHotkey';
import { useCanvasToggleNonRasterLayersHotkey } from 'features/controlLayers/hooks/useCanvasToggleNonRasterLayersHotkey';
import { useCanvasTransformHotkey } from 'features/controlLayers/hooks/useCanvasTransformHotkey';
import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys';
@@ -31,6 +32,7 @@ export const CanvasToolbar = memo(() => {
useCanvasFilterHotkey();
useCanvasInvertMaskHotkey();
useCanvasToggleNonRasterLayersHotkey();
+ useCanvasToggleBboxHotkey();
return (
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts
new file mode 100644
index 00000000000..6dda338af52
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts
@@ -0,0 +1,18 @@
+import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
+import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
+import { useCallback } from 'react';
+
+export const useCanvasToggleBboxHotkey = () => {
+ const canvasManager = useCanvasManager();
+
+ const handleToggleBboxVisibility = useCallback(() => {
+ canvasManager.tool.tools.bbox.toggleBboxVisibility();
+ }, [canvasManager]);
+
+ useRegisteredHotkeys({
+ id: 'toggleBbox',
+ category: 'canvas',
+ callback: handleToggleBboxVisibility,
+ dependencies: [handleToggleBboxVisibility],
+ });
+};
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
index 0ed1c20eb4b..10f12f0c183 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
@@ -66,6 +66,11 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
*/
$aspectRatioBuffer = atom(1);
+ /**
+ * Buffer to store the visibility of the bbox.
+ */
+ $isBboxHidden = atom(false);
+
constructor(parent: CanvasToolModule) {
super();
this.id = getPrefixedId(this.type);
@@ -191,6 +196,9 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
// Update on busy state changes
this.subscriptions.add(this.manager.$isBusy.listen(this.render));
+
+ // Listen for stage changes to update the bbox's visibility
+ this.subscriptions.add(this.$isBboxHidden.listen(this.render));
}
// This is a noop. The cursor is changed when the cursor enters or leaves the bbox.
@@ -206,13 +214,15 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
};
/**
- * Renders the bbox. The bbox is only visible when the tool is set to 'bbox'.
+ * Renders the bbox.
*/
render = () => {
const tool = this.manager.tool.$tool.get();
const { x, y, width, height } = this.manager.stateApi.runSelector(selectBbox).rect;
+ this.konva.group.visible(!this.$isBboxHidden.get());
+
// We need to reach up to the preview layer to enable/disable listening so that the bbox can be interacted with.
// If the mangaer is busy, we disable listening so the bbox cannot be interacted with.
this.konva.group.listening(tool === 'bbox' && !this.manager.$isBusy.get());
@@ -478,4 +488,8 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
this.subscriptions.clear();
this.konva.group.destroy();
};
+
+ toggleBboxVisibility = () => {
+ this.$isBboxHidden.set(!this.$isBboxHidden.get());
+ };
}
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
index 954c95447dd..99ee36fde39 100644
--- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
+++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
@@ -126,6 +126,7 @@ export const useHotkeyData = (): HotkeysData => {
addHotkey('canvas', 'cancelSegmentAnything', ['esc']);
addHotkey('canvas', 'toggleNonRasterLayers', ['shift+h']);
addHotkey('canvas', 'fitBboxToMasks', ['shift+b']);
+ addHotkey('canvas', 'toggleBbox', ['shift+o']);
// Workflows
addHotkey('workflows', 'addNode', ['shift+a', 'space']);
diff --git a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx
index dc6672437f4..2c8a516f982 100644
--- a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx
@@ -1,5 +1,6 @@
import { ContextMenu, Divider, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
+import { CanvasAlertsBboxVisibility } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility';
import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress';
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
import { CanvasAlertsSaveAllImagesToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllImagesToGallery';
@@ -92,6 +93,7 @@ export const CanvasWorkspacePanel = memo(() => {
+