Skip to content

Commit a6ffd63

Browse files
committed
✨ feat: the rotate command is adapted to the dynamic plane.
1 parent 624c21c commit a6ffd63

File tree

3 files changed

+67
-55
lines changed

3 files changed

+67
-55
lines changed

packages/chili/src/commands/createCommand.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.
22

3-
import { Config, GeometryNode, IView, Property, Transaction, XYZ } from "chili-core";
4-
import { ViewUtils } from "chili-vis";
3+
import { GeometryNode, Property, Transaction } from "chili-core";
54
import { MultistepCommand } from "./multistepCommand";
65

76
let count = 1;
@@ -15,14 +14,6 @@ export abstract class CreateCommand extends MultistepCommand {
1514
});
1615
}
1716

18-
protected readonly findPlane = (view: IView, origin: XYZ, point: XYZ | undefined) => {
19-
if (point === undefined || !Config.instance.dynamicWorkplane) {
20-
return view.workplane.translateTo(origin);
21-
} else {
22-
return ViewUtils.raycastClosestPlane(view, origin, point);
23-
}
24-
};
25-
2617
protected abstract geometryNode(): GeometryNode;
2718
}
2819

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license.
22

3-
import { Matrix4, ShapeMeshData, XYZ, command } from "chili-core";
4-
import { Dimension, PointSnapData } from "../../snap";
5-
import { AngleStep, IStep, PointStep } from "../../step";
3+
import { Matrix4, Plane, PlaneAngle, Precision, ShapeMeshData, XYZ, command } from "chili-core";
4+
import { Dimension, SnapLengthAtPlaneData } from "../../snap";
5+
import { AngleStep, IStep, LengthAtPlaneStep, PointStep } from "../../step";
66
import { TransformedCommand } from "./transformedCommand";
77

88
@command({
@@ -11,75 +11,85 @@ import { TransformedCommand } from "./transformedCommand";
1111
icon: "icon-rotate",
1212
})
1313
export class Rotate extends TransformedCommand {
14+
private _planeAngle: PlaneAngle | undefined;
15+
1416
protected override transfrom(point: XYZ): Matrix4 {
15-
const normal = this.stepDatas[0].view.workplane.normal;
17+
const normal = this.stepDatas[1].plane!.normal;
1618
const center = this.stepDatas[0].point!;
17-
const p1 = this.stepDatas[1].point!;
18-
const v1 = p1.sub(center);
19-
const v2 = point.sub(center);
20-
const angle = v1.angleOnPlaneTo(v2, normal)!;
19+
const angle = (this._planeAngle?.angle! * Math.PI) / 180;
2120
return Matrix4.createRotationAt(center, normal, angle);
2221
}
2322

2423
getSteps(): IStep[] {
2524
let firstStep = new PointStep("operate.pickFistPoint");
26-
let secondStep = new PointStep("operate.pickNextPoint", this.getSecondPointData);
25+
let secondStep = new LengthAtPlaneStep("operate.pickNextPoint", this.getSecondPointData);
2726
let thirdStep = new AngleStep(
2827
"operate.pickNextPoint",
2928
() => this.stepDatas[0].point!,
3029
() => this.stepDatas[1].point!,
31-
this.getThirdPointData,
30+
this.getAngleData,
3231
);
3332
return [firstStep, secondStep, thirdStep];
3433
}
3534

36-
private readonly getSecondPointData = (): PointSnapData => {
35+
private readonly getSecondPointData = (): SnapLengthAtPlaneData => {
36+
const { point, view } = this.stepDatas[0];
3737
return {
38-
refPoint: () => this.stepDatas[0].point!,
39-
dimension: Dimension.D1D2D3,
40-
plane: () => this.stepDatas[0].view.workplane.translateTo(this.stepDatas[0].point!),
41-
preview: this.linePreview,
42-
validator: (p) => p.distanceTo(this.stepDatas[0].point!) > 1e-6,
38+
point: () => point!,
39+
preview: this.circlePreview,
40+
plane: (p: XYZ | undefined) => this.findPlane(view, point!, p),
41+
validator: (p: XYZ) => {
42+
if (p.distanceTo(point!) < Precision.Distance) return false;
43+
return p.sub(point!).isParallelTo(this.stepDatas[0].view.workplane.normal) === false;
44+
},
4345
};
4446
};
4547

46-
private readonly getThirdPointData = (): PointSnapData => {
48+
private readonly getAngleData = () => {
49+
const [center, p1] = [this.stepDatas[0].point!, this.stepDatas[1].point!];
50+
const plane = this.stepDatas[1].plane ?? this.findPlane(this.stepDatas[1].view, center, p1);
51+
const points: ShapeMeshData[] = [this.meshPoint(center), this.meshPoint(p1)];
52+
this._planeAngle = new PlaneAngle(new Plane(center, plane.normal, p1.sub(center)));
4753
return {
4854
dimension: Dimension.D1D2,
49-
preview: this.rotatePreview,
50-
plane: () => this.stepDatas[0].view.workplane.translateTo(this.stepDatas[0].point!),
51-
validator: (p) => {
52-
return (
53-
p.distanceTo(this.stepDatas[0].point!) > 1e-3 &&
54-
p.distanceTo(this.stepDatas[1].point!) > 1e-3
55-
);
56-
},
55+
preview: (point: XYZ | undefined) => this.anglePreview(point, center, p1, points),
56+
plane: () => plane,
5757
};
5858
};
5959

60-
private readonly rotatePreview = (point: XYZ | undefined): ShapeMeshData[] => {
61-
let p1 = this.meshPoint(this.stepDatas[0].point!);
62-
let l1 = this.getRayData(this.stepDatas[1].point!);
63-
let result = [p1, l1, this.meshPoint(this.stepDatas[1].point!)];
64-
if (point) {
65-
let shape = this.transformPreview(point);
66-
let l2 = this.getRayData(point);
67-
result.push(l2, shape);
60+
private anglePreview(
61+
point: XYZ | undefined,
62+
center: XYZ,
63+
p1: XYZ,
64+
points: ShapeMeshData[],
65+
): ShapeMeshData[] {
66+
point = point ?? p1;
67+
this._planeAngle!.movePoint(point);
68+
const result = [...points];
69+
if (Math.abs(this._planeAngle!.angle) > Precision.Angle) {
70+
result.push(
71+
this.meshCreatedShape(
72+
"arc",
73+
this._planeAngle!.plane.normal,
74+
center,
75+
p1,
76+
this._planeAngle!.angle,
77+
),
78+
this.transformPreview(point),
79+
);
6880
}
6981
return result;
70-
};
71-
72-
private getRayData(end: XYZ) {
73-
let start = this.stepDatas[0].point!;
74-
let e = start.add(end.sub(start).normalize()!.multiply(1e6));
75-
return this.getTempLineData(start, e);
7682
}
7783

78-
private readonly linePreview = (point: XYZ | undefined): ShapeMeshData[] => {
79-
let p1 = this.meshPoint(this.stepDatas[0].point!);
80-
if (!point) {
81-
return [p1];
82-
}
83-
return [p1, this.getTempLineData(this.stepDatas[0].point!, point)];
84+
private readonly circlePreview = (end: XYZ | undefined) => {
85+
const visualCenter = this.meshPoint(this.stepDatas[0].point!);
86+
if (!end) return [visualCenter];
87+
const { point, view } = this.stepDatas[0];
88+
const plane = this.findPlane(view, point!, end);
89+
return [
90+
visualCenter,
91+
this.meshLine(this.stepDatas[0].point!, end),
92+
this.meshCreatedShape("circle", plane.normal, point!, plane.projectDistance(point!, end)),
93+
];
8494
};
8595
}

packages/chili/src/commands/multistepCommand.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import {
44
AsyncController,
55
CancelableCommand,
6+
Config,
67
EdgeMeshData,
78
IShape,
89
IShapeFactory,
10+
IView,
911
LineType,
1012
Property,
1113
Result,
@@ -15,6 +17,7 @@ import {
1517
} from "chili-core";
1618
import { SnapResult } from "../snap";
1719
import { IStep } from "../step";
20+
import { ViewUtils } from "chili-vis";
1821

1922
export abstract class MultistepCommand extends CancelableCommand {
2023
protected stepDatas: SnapResult[] = [];
@@ -98,6 +101,14 @@ export abstract class MultistepCommand extends CancelableCommand {
98101
return edgeMesh;
99102
}
100103

104+
protected readonly findPlane = (view: IView, origin: XYZ, point: XYZ | undefined) => {
105+
if (point === undefined || !Config.instance.dynamicWorkplane) {
106+
return view.workplane.translateTo(origin);
107+
} else {
108+
return ViewUtils.raycastClosestPlane(view, origin, point);
109+
}
110+
};
111+
101112
protected abstract getSteps(): IStep[];
102113

103114
protected abstract executeMainTask(): void;

0 commit comments

Comments
 (0)