Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-components): Add NextClippingCommand to the clip tool #4641

Merged
merged 5 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { type VisualDomainObject } from '../../base/domainObjects/VisualDomainOb
import { SetClipTypeCommand } from './commands/SetClipTypeCommand';
import { PlaneCreator } from '../primitives/plane/PlaneCreator';
import { SliceDomainObject } from './SliceDomainObject';
import { NextOrPrevClippingCommand } from './commands/NextClippingCommand';

export class ClipTool extends PrimitiveEditTool {
// ==================================================
Expand Down Expand Up @@ -47,8 +48,10 @@ export class ClipTool extends PrimitiveEditTool {
new SetClipTypeCommand(PrimitiveType.Box),
undefined, // Separator
new ApplyClipCommand(),
new ShowClippingOnTopCommand(),
new ShowAllClippingCommand()
new NextOrPrevClippingCommand(false),
new NextOrPrevClippingCommand(true),
new ShowAllClippingCommand(),
new ShowClippingOnTopCommand()
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*!
* Copyright 2024 Cognite AS
*/

import { type BaseCommand } from '../../../base/commands/BaseCommand';
import { RenderTargetCommand } from '../../../base/commands/RenderTargetCommand';
import { type TranslateKey } from '../../../base/utilities/TranslateKey';
import { CropBoxDomainObject } from '../CropBoxDomainObject';
import { SliceDomainObject } from '../SliceDomainObject';
import { ApplyClipCommand } from './ApplyClipCommand';

export class NextOrPrevClippingCommand extends RenderTargetCommand {
private readonly _next: boolean;

// ==================================================
// CONSTRUCTORS
// ==================================================

public constructor(next: boolean) {
super();
this._next = next;
}

// ==================================================
// OVERRIDES
// ==================================================

public override get tooltip(): TranslateKey {
if (this._next) {
return {
key: 'CLIP_NEXT',
fallback: 'Set the next crop box or slicing plane as global clipping'
};
} else {
return {
key: 'CLIP_PREV',
fallback: 'Set the previous crop box or slicing plane as global clipping'
};
}
}

public override get icon(): string {
return this._next ? 'ArrowRight' : 'ArrowLeft';
}

public override get isEnabled(): boolean {
if (!this.renderTarget.isGlobalClippingActive) {
return false;
}
const minimumCount = this._next ? 2 : 3; // Don't need both buttons if it is less than 3
const { rootDomainObject } = this;
// Require at least two crop boxes or one crop box and one slice
let count = 0;
for (const domainObject of rootDomainObject.getDescendants()) {
if (domainObject instanceof CropBoxDomainObject) {
count++;
if (count >= minimumCount) {
return true; // Optimization
}
}
}
if (rootDomainObject.getDescendantByType(SliceDomainObject) !== undefined) {
count++;
}
return count >= minimumCount;
}

public override equals(other: BaseCommand): boolean {
if (!(other instanceof NextOrPrevClippingCommand)) {
return false;
}
return this._next === other._next;
}

protected override invokeCore(): boolean {
nilscognite marked this conversation as resolved.
Show resolved Hide resolved
// This code treat the slicing planes as one single group, along with all the crop boxes.
// The next selected crop box or slicing planes will be used as clipping.
const array = this.createCropBoxesAndSliceArray();
if (array.length <= 1) {
return false;
}
const selectedIndex = array.findIndex((domainObject) => domainObject.isSelected);
if (selectedIndex === undefined) {
return false;
}
const nextIndex = this.getNextIndex(selectedIndex, array.length);
this.setAllInvisibleAndDeselected(array, nextIndex);

// Take the next crop box or slicing planes and use it as clipping
const nextCropBoxOrSlice = array[nextIndex];
this.setVisibleAndSelected(nextCropBoxOrSlice, true);
if (nextCropBoxOrSlice instanceof CropBoxDomainObject) {
nextCropBoxOrSlice.setThisAsGlobalCropBox();
} else {
ApplyClipCommand.setClippingPlanes(this.rootDomainObject);
}
this.renderTarget.fitView();
return true;
}

// ==================================================
// INSTANCE METHODS
// ==================================================

private createCropBoxesAndSliceArray(): Array<CropBoxDomainObject | SliceDomainObject> {
const { rootDomainObject } = this;
// Build the array of crop boxes and at least one slice
const array = new Array<CropBoxDomainObject | SliceDomainObject>();
for (const cropBox of rootDomainObject.getDescendantsByType(CropBoxDomainObject)) {
array.push(cropBox);
}
// Take the selected slice, otherwise take the first one
const selectedSlice = rootDomainObject.getSelectedDescendantByType(SliceDomainObject);
if (selectedSlice !== undefined) {
array.push(selectedSlice);
} else {
const sliceDomainObject = rootDomainObject.getDescendantByType(SliceDomainObject);
if (sliceDomainObject !== undefined) {
array.push(sliceDomainObject);
}
}
return array;
}

private getNextIndex(selectedIndex: number, arrayLength: number): number {
const increment = this._next ? 1 : -1;
const nextIndex = selectedIndex + increment;
if (nextIndex < 0) {
return arrayLength - 1;
} else if (nextIndex >= arrayLength) {
return 0;
}
return nextIndex;
}

private setAllInvisibleAndDeselected(
array: Array<CropBoxDomainObject | SliceDomainObject>,
exceptIndex: number
): void {
for (let i = 0; i < array.length; i++) {
if (i !== exceptIndex) {
this.setVisibleAndSelected(array[i], false);
}
}
}

private setVisibleAndSelected(
domainObject: CropBoxDomainObject | SliceDomainObject,
value: boolean
): void {
domainObject.setSelectedInteractive(value);
if (domainObject instanceof SliceDomainObject) {
this.setAllSliceDomainObjectsVisible(value);
} else {
domainObject.setVisibleInteractive(value, this.renderTarget);
}
}

private setAllSliceDomainObjectsVisible(visible: boolean): void {
const { rootDomainObject } = this;
for (const sliceDomainObject of rootDomainObject.getDescendantsByType(SliceDomainObject)) {
sliceDomainObject.setVisibleInteractive(visible, this.renderTarget);
}
}

public getCropBoxesAndSliceCount(): number {
const { rootDomainObject } = this;
// Require at least two crop boxes or one crop box and one slice
let count = 0;
for (const domainObject of rootDomainObject.getDescendants()) {
if (domainObject instanceof CropBoxDomainObject) {
count++;
}
}
if (rootDomainObject.getDescendantByType(SliceDomainObject) !== undefined) {
count++;
}
return count;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class ExampleTool extends BaseEditTool {
// ==================================================

public override onKey(event: KeyboardEvent, down: boolean): void {
if (down && event.key === 'Delete') {
if (down && (event.key === 'Delete' || event.key === 'Backspace')) {
const domainObject = this.getSelected();
if (domainObject !== undefined) {
domainObject.removeInteractive();
Expand All @@ -61,7 +61,7 @@ export class ExampleTool extends BaseEditTool {
hsl.h = (hsl.h + Math.sign(delta) * 0.02) % 1;
domainObject.color.setHSL(hsl.h, hsl.s, hsl.l);
domainObject.notify(Changes.color);
} else if (event.ctrlKey) {
} else if (event.ctrlKey || event.metaKey) {
// Change opacity
const opacity = domainObject.renderStyle.opacity + Math.sign(delta) * 0.05;
domainObject.renderStyle.opacity = clamp(opacity, 0.2, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export abstract class PrimitiveEditTool extends BaseEditTool {
}

public override onKey(event: KeyboardEvent, down: boolean): void {
if (down && event.key === 'Delete') {
if (down && (event.key === 'Delete' || event.key === 'Backspace')) {
const domainObject = this.getSelected();
if (domainObject !== undefined) {
domainObject.removeInteractive();
Expand Down
Loading