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 circle and cylinder measurement #4996

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export class Cylinder extends Primitive {
private readonly _axis = new Vector3();
private readonly _size = new Vector3();

public get endCapArea(): number {
return Math.PI * this.radius * this.radius;
}

// ==================================================
// OVERRIDES of Primitive
// ==================================================
Expand All @@ -35,7 +39,7 @@ export class Cylinder extends Primitive {
}

public override get area(): number {
return 2 * Math.PI * this.radius * this.height;
return 2 * Math.PI * this.radius * this.height + 2 * this.endCapArea;
}

public override get volume(): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum PrimitiveType {
VerticalArea,
Box,
Cylinder,
HorizontalCircle,
HorizontalCylinder,
VerticalCylinder,
PlaneX,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function getIconByPrimitiveType(primitiveType: PrimitiveType): IconName {
return 'Cube';
case PrimitiveType.Cylinder:
return 'CylinderArbitrary';
case PrimitiveType.HorizontalCircle:
return 'Circle';
case PrimitiveType.VerticalCylinder:
return 'CylinderVertical';
case PrimitiveType.HorizontalCylinder:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { CopyToClipboardCommand } from '../../base/concreteCommands/CopyToClipbo
import { ToggleMetricUnitsCommand } from '../../base/concreteCommands/ToggleMetricUnitsCommand';
import { Cylinder } from '../../base/utilities/primitives/Cylinder';
import { Annotation } from './helpers/Annotation';
import { PrimitiveType } from '../../base/utilities/primitives/PrimitiveType';

export class CylinderGizmoDomainObject extends CylinderDomainObject {
// ==================================================
// CONSTRUCTOR
// ==================================================

public constructor() {
super();
public constructor(primitiveType: PrimitiveType = PrimitiveType.Cylinder) {
super(primitiveType);
this.color = new Color(Color.NAMES.white);
}
// ==================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export class AnnotationsCreateTool extends NavigationTool {
}

// ==================================================
// OVERRIDES of BaseEditTool
// INSTANCE METHODS
// ==================================================

private createCreator(): BaseCreator | undefined {
Expand All @@ -200,18 +200,13 @@ export class AnnotationsCreateTool extends NavigationTool {
if (!(gizmo instanceof CylinderGizmoDomainObject)) {
return undefined;
}
const isHorizontal = this.primitiveType === PrimitiveType.HorizontalCylinder;
return new CylinderCreator(this, gizmo, isHorizontal);
return new CylinderCreator(this, gizmo, this.primitiveType, true);
}
default:
return undefined;
}
}

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

public escape(): void {
if (this._creator !== undefined && this._creator.escape()) {
this.endCreatorIfFinished(this._creator, true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*!
* Copyright 2024 Cognite AS
*/

import { type DomainObject } from '../../base/domainObjects/DomainObject';
import { type PrimitiveType } from '../../base/utilities/primitives/PrimitiveType';
import { CylinderDomainObject } from '../primitives/cylinder/CylinderDomainObject';
import { Color } from 'three';

export class MeasureCylinderDomainObject extends CylinderDomainObject {
// ==================================================
// CONSTRUCTOR
// ==================================================

public constructor(primitiveType: PrimitiveType) {
super(primitiveType);
this.color = new Color(Color.NAMES.magenta);
}

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

public override clone(what?: symbol): DomainObject {
const clone = new MeasureCylinderDomainObject(this.primitiveType);
clone.copyFrom(this, what);
return clone;
}

public override get canRotate(): boolean {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { type IconName } from '../../base/utilities/IconName';
import { Box3 } from 'three';
import { type DomainObject } from '../../base/domainObjects/DomainObject';
import { MeasurementFolder } from './MeasurementFolder';
import { MeasureCylinderDomainObject } from './MeasureCylinderDomainObject';
import { CylinderCreator } from '../primitives/cylinder/CylinderCreator';

export class MeasurementTool extends PrimitiveEditTool {
// ==================================================
Expand All @@ -42,6 +44,8 @@ export class MeasurementTool extends PrimitiveEditTool {
new SetMeasurementTypeCommand(PrimitiveType.HorizontalArea),
new SetMeasurementTypeCommand(PrimitiveType.VerticalArea),
new SetMeasurementTypeCommand(PrimitiveType.Box),
new SetMeasurementTypeCommand(PrimitiveType.VerticalCylinder),
new SetMeasurementTypeCommand(PrimitiveType.HorizontalCircle),
undefined, // Separator
new UndoCommand(),
new ShowMeasurementsOnTopCommand()
Expand All @@ -62,14 +66,11 @@ export class MeasurementTool extends PrimitiveEditTool {
const sceneBoundingBox = this.renderTarget.clippedVisualSceneBoundingBox;
const boundingBox = new Box3();
for (const domainObject of this.getSelectable()) {
if (domainObject instanceof MeasureBoxDomainObject) {
boundingBox.makeEmpty();
domainObject.box.expandBoundingBox(boundingBox);
boundingBox.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION);
if (!sceneBoundingBox.intersectsBox(boundingBox)) {
continue;
}
} else if (domainObject instanceof MeasureLineDomainObject) {
if (
domainObject instanceof MeasureBoxDomainObject ||
domainObject instanceof MeasureLineDomainObject ||
domainObject instanceof MeasureCylinderDomainObject
) {
boundingBox.makeEmpty();
domainObject.expandBoundingBox(boundingBox);
boundingBox.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION);
Expand All @@ -93,7 +94,8 @@ export class MeasurementTool extends PrimitiveEditTool {
protected override canBeSelected(domainObject: VisualDomainObject): boolean {
return (
domainObject instanceof MeasureBoxDomainObject ||
domainObject instanceof MeasureLineDomainObject
domainObject instanceof MeasureLineDomainObject ||
domainObject instanceof MeasureCylinderDomainObject
);
}

Expand Down Expand Up @@ -122,6 +124,11 @@ export class MeasurementTool extends PrimitiveEditTool {
case PrimitiveType.VerticalArea:
case PrimitiveType.Box:
return new BoxCreator(this, new MeasureBoxDomainObject(this.primitiveType));

case PrimitiveType.VerticalCylinder:
case PrimitiveType.HorizontalCircle:
return new CylinderCreator(this, new MeasureCylinderDomainObject(this.primitiveType));

default:
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,29 +84,21 @@ export class SetMeasurementTypeCommand extends RenderTargetCommand {
function getTooltipByPrimitiveType(primitiveType: PrimitiveType): TranslationInput {
switch (primitiveType) {
case PrimitiveType.Line:
return {
key: 'MEASUREMENTS_ADD_LINE'
};
return { key: 'MEASUREMENTS_ADD_LINE' };
case PrimitiveType.Polyline:
return {
key: 'MEASUREMENTS_ADD_POLYLINE'
};
return { key: 'MEASUREMENTS_ADD_POLYLINE' };
case PrimitiveType.Polygon:
return {
key: 'MEASUREMENTS_ADD_POLYGON'
};
return { key: 'MEASUREMENTS_ADD_POLYGON' };
case PrimitiveType.VerticalArea:
return {
key: 'MEASUREMENTS_ADD_VERTICAL_AREA'
};
return { key: 'MEASUREMENTS_ADD_VERTICAL_AREA' };
case PrimitiveType.HorizontalArea:
return {
key: 'MEASUREMENTS_ADD_HORIZONTAL_AREA'
};
return { key: 'MEASUREMENTS_ADD_HORIZONTAL_AREA' };
case PrimitiveType.Box:
return {
key: 'MEASUREMENTS_ADD_VOLUME'
};
return { key: 'MEASUREMENTS_ADD_VOLUME' };
case PrimitiveType.HorizontalCircle:
return { key: 'MEASUREMENTS_ADD_CIRCLE' };
case PrimitiveType.VerticalCylinder:
return { key: 'MEASUREMENTS_ADD_CYLINDER' };
default:
throw new Error('Unknown PrimitiveType');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { type IconName } from '../../../base/utilities/IconName';
import { SolidDomainObject } from '../common/SolidDomainObject';
import { SolidPrimitiveRenderStyle } from '../common/SolidPrimitiveRenderStyle';
import { Box } from '../../../base/utilities/primitives/Box';
import { type Vector3 } from 'three';
import { type Box3, type Vector3 } from 'three';
import { type RevealRenderTarget } from '../../../base/renderTarget/RevealRenderTarget';

export abstract class BoxDomainObject extends SolidDomainObject {
Expand Down Expand Up @@ -191,4 +191,8 @@ export abstract class BoxDomainObject extends SolidDomainObject {
throw new Error('Unknown MeasureType type');
}
}

public expandBoundingBox(boundingBox: Box3): void {
this.box.expandBoundingBox(boundingBox);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ export class BoxView extends GroupThreeView<BoxDomainObject> {
return FocusType.Rotation;
}
}
// }
return FocusType.Body;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { Plane, type Ray, Vector3 } from 'three';
import { type PrimitiveType } from '../../../base/utilities/primitives/PrimitiveType';
import { PrimitiveType } from '../../../base/utilities/primitives/PrimitiveType';
import { BaseCreator } from '../../../base/domainObjectsHelpers/BaseCreator';
import { FocusType } from '../../../base/domainObjectsHelpers/FocusType';
import { Changes } from '../../../base/domainObjectsHelpers/Changes';
Expand All @@ -22,18 +22,33 @@ export class CylinderCreator extends BaseCreator {
// ==================================================

private readonly _domainObject: CylinderDomainObject;
private readonly _isHorizontal;
private _radius = Cylinder.MinSize;
private readonly _primitiveType: PrimitiveType;

// Click order for the points (point 0 is always center)
private readonly _radiusOrder: number = 1;
private readonly _otherCenterOrder: number = 2;

// ==================================================
// CONSTRUCTOR
// ==================================================

public constructor(tool: BaseTool, domainObject: CylinderDomainObject, isHorizontal: boolean) {
public constructor(
tool: BaseTool,
domainObject: CylinderDomainObject,
primitiveType?: PrimitiveType,
isReversedClickOrder = false
) {
super(tool);
this._primitiveType = primitiveType ?? domainObject.primitiveType;
this._domainObject = domainObject;
this._domainObject.focusType = FocusType.Pending;
this._isHorizontal = isHorizontal;

// Click order for the points (Used for 3D annotations)
if (isReversedClickOrder) {
this._radiusOrder = 2;
this._otherCenterOrder = 1;
}
}

// ==================================================
Expand All @@ -49,6 +64,9 @@ export class CylinderCreator extends BaseCreator {
}

public override get maximumPointCount(): number {
if (this._domainObject.primitiveType === PrimitiveType.HorizontalCircle) {
return 2;
}
return 3;
}

Expand All @@ -58,13 +76,13 @@ export class CylinderCreator extends BaseCreator {
isPending: boolean
): boolean {
const domainObject = this._domainObject;
point = this.recalculatePoint(point, ray, domainObject.primitiveType);
point = this.recalculatePoint(point, ray);
if (point === undefined) {
return false;
}
this.addRawPoint(point, isPending);

this.rebuild();

domainObject.notify(Changes.geometry);
if (this.isFinished) {
domainObject.setFocusInteractive(FocusType.Focus);
Expand All @@ -76,15 +94,12 @@ export class CylinderCreator extends BaseCreator {
// INSTANCE METHODS
// ==================================================

private recalculatePoint(
point: Vector3 | undefined,
ray: Ray,
_primitiveType: PrimitiveType
): Vector3 | undefined {
private recalculatePoint(point: Vector3 | undefined, ray: Ray): Vector3 | undefined {
// Recalculate the point anyway for >= 1 points
// This makes it more natural and you can pick in empty space
if (this.notPendingPointCount === 1) {
if (this._isHorizontal) {
if (this.notPendingPointCount === this._otherCenterOrder) {
// Calculate the other center point
if (this._primitiveType === PrimitiveType.HorizontalCylinder) {
if (point === undefined) {
const plane = new Plane().setFromNormalAndCoplanarPoint(UP_VECTOR, this.firstPoint);
const newPoint = ray.intersectPlane(plane, new Vector3());
Expand All @@ -108,9 +123,20 @@ export class CylinderCreator extends BaseCreator {
return point;
}
}
} else if (this.notPendingPointCount === 2) {
} else if (this.notPendingPointCount === this._radiusOrder) {
// Calculate the radius
const { center, axis } = this._domainObject.cylinder;

if (this._radiusOrder === 1) {
// When radius is the second point, we can calculate it from the intersection with the plane
const plane = new Plane().setFromNormalAndCoplanarPoint(axis, center);
const pointOnRay = ray.intersectPlane(plane, new Vector3());
if (pointOnRay !== null) {
this._radius = pointOnRay?.distanceTo(center);
this._radius = Math.max(this._radius, Cylinder.MinSize);
return pointOnRay;
}
}
const lineLength = ray.origin.distanceTo(center) * 100;
const v0 = center.clone().addScaledVector(axis, -lineLength);
const v1 = center.clone().addScaledVector(axis, +lineLength);
Expand All @@ -125,8 +151,8 @@ export class CylinderCreator extends BaseCreator {

/**
* Create the cylinder by adding points. The first point will make a centerA.
* The second point will give the centerB.
* The third will give radius. The radius is already calculated in the recalculatePoint
* The second/third point will give the centerB.
* The third/second will give radius. The radius is already calculated in the recalculatePoint
*/

private rebuild(): void {
Expand All @@ -138,18 +164,22 @@ export class CylinderCreator extends BaseCreator {
const { centerA, centerB } = cylinder;
centerA.copy(this.firstPoint);
centerB.copy(this.firstPoint);
const smallVector = new Vector3(Cylinder.MinSize, 0, 0);
centerA.sub(smallVector);
centerB.add(smallVector);
cylinder.forceMinSize();
}
if (this.pointCount === 2) {
if (this._primitiveType === PrimitiveType.HorizontalCylinder) {
centerA.x -= Cylinder.MinSize;
centerA.y -= Cylinder.MinSize;
centerB.x += Cylinder.MinSize;
centerB.y += Cylinder.MinSize;
} else {
centerA.z -= Cylinder.MinSize;
centerB.z += Cylinder.MinSize;
}
} else if (this.pointCount - 1 === this._otherCenterOrder) {
const { centerA, centerB } = cylinder;
centerA.copy(this.firstPoint);
centerB.copy(this.lastPoint);
cylinder.forceMinSize();
} else {
} else if (this.pointCount - 1 === this._radiusOrder) {
cylinder.radius = this._radius;
}
cylinder.forceMinSize();
}
}
Loading
Loading