diff --git a/packages/chili-core/src/command/commandKeys.ts b/packages/chili-core/src/command/commandKeys.ts index 62d0e11e..b0f64004 100644 --- a/packages/chili-core/src/command/commandKeys.ts +++ b/packages/chili-core/src/command/commandKeys.ts @@ -15,6 +15,7 @@ export type CommandKeys = | "file.export.iges" | "file.export.stp" | "create.arc" + | "create.bezier" | "create.box" | "create.line" | "create.circle" diff --git a/packages/chili-core/src/i18n/en.ts b/packages/chili-core/src/i18n/en.ts index 652d056f..bbd02da1 100644 --- a/packages/chili-core/src/i18n/en.ts +++ b/packages/chili-core/src/i18n/en.ts @@ -54,6 +54,7 @@ export default { "line.end": "End", "polygon.points": "Points", "command.arc": "Arc", + "command.bezier": "Bezier", "command.boolean.common": "Common", "command.boolean.cut": "Cut", "command.boolean.fuse": "Fuse", diff --git a/packages/chili-core/src/i18n/keys.ts b/packages/chili-core/src/i18n/keys.ts index 471f53d7..d8a75135 100644 --- a/packages/chili-core/src/i18n/keys.ts +++ b/packages/chili-core/src/i18n/keys.ts @@ -86,6 +86,7 @@ export type I18nKeys = | "command.line" | "command.line.isConnected" | "command.box" + | "command.bezier" | "command.circle" | "command.rect" | "command.move" diff --git a/packages/chili-core/src/i18n/zh-cn.ts b/packages/chili-core/src/i18n/zh-cn.ts index 814c8697..4bc656b2 100644 --- a/packages/chili-core/src/i18n/zh-cn.ts +++ b/packages/chili-core/src/i18n/zh-cn.ts @@ -54,6 +54,7 @@ export default { "line.end": "终点", "polygon.points": "点", "command.arc": "圆弧", + "command.bezier": "贝塞尔", "command.boolean.common": "相交", "command.boolean.cut": "剪切", "command.boolean.fuse": "融合", diff --git a/packages/chili-core/src/shape/curve.ts b/packages/chili-core/src/shape/curve.ts index 474a3fae..04f02fc3 100644 --- a/packages/chili-core/src/shape/curve.ts +++ b/packages/chili-core/src/shape/curve.ts @@ -9,7 +9,6 @@ export interface ICurve { firstParameter(): number; lastParameter(): number; point(parameter: number): XYZ; - trim(start: number, end: number): void; project(point: XYZ): XYZ[]; isCN(n: number): boolean; d0(u: number): XYZ; @@ -33,6 +32,34 @@ export interface ICircle extends IConic { radius: number; } +export interface IBoundedCurve extends ICurve { + startPoint(): XYZ; + endPoint(): XYZ; +} + +export interface IBezierCurve extends IBoundedCurve { + degree(): number; + weight(index: number): number; + insertPoleAfter(index: number, point: XYZ, weight: number | undefined): void; + insertPoleBefore(index: number, point: XYZ, weight: number | undefined): void; + removePole(index: number): void; + setPole(index: number, point: XYZ, weight: number | undefined): void; + setWeight(index: number, weight: number): void; + nbPoles(): number; + pole(index: number): XYZ; + poles(): XYZ[]; +} + +export interface ITrimmedCurve extends IBoundedCurve { + basisCurve(): ICurve; +} + +export interface IOffsetCurve extends ICurve { + basisCurve(): ICurve; + offset(): number; + direction(): XYZ; +} + export namespace ICurve { export function isConic(curve: ICurve): curve is IConic { let conic = curve as IConic; diff --git a/packages/chili-core/src/shape/shape.ts b/packages/chili-core/src/shape/shape.ts index 3ca81980..3e3e4168 100644 --- a/packages/chili-core/src/shape/shape.ts +++ b/packages/chili-core/src/shape/shape.ts @@ -2,7 +2,7 @@ import { Result } from "../foundation"; import { Matrix4, Ray, XYZ } from "../math"; -import { ICurve } from "./curve"; +import { ICurve, ITrimmedCurve } from "./curve"; import { IShapeMeshData } from "./meshData"; import { ShapeType } from "./shapeType"; @@ -16,6 +16,7 @@ export enum CurveType { BSplineCurve, OffsetCurve, OtherCurve, + TrimmedCurve, } export enum SurfaceType { @@ -67,7 +68,7 @@ export interface IVertex extends IShape {} export interface IEdge extends IShape { intersect(other: IEdge | Ray): XYZ[]; length(): number; - asCurve(): Result; + asCurve(): ITrimmedCurve; offset(distance: number, dir: XYZ): Result; } diff --git a/packages/chili-core/src/shape/shapeFactory.ts b/packages/chili-core/src/shape/shapeFactory.ts index 14d72c4a..af649aae 100644 --- a/packages/chili-core/src/shape/shapeFactory.ts +++ b/packages/chili-core/src/shape/shapeFactory.ts @@ -2,12 +2,14 @@ import { Result } from "../foundation"; import { Plane, Ray, XYZ } from "../math"; +import { IBezierCurve } from "./curve"; import { ICompound, IEdge, IFace, IShape, ISolid, IVertex, IWire } from "./shape"; import { IShapeConverter } from "./shapeConverter"; export interface IShapeFactory { readonly converter: IShapeConverter; face(...wire: IWire[]): Result; + bezier(points: XYZ[], weights?: number[]): Result; point(point: XYZ): Result; line(start: XYZ, end: XYZ): Result; arc(normal: XYZ, center: XYZ, start: XYZ, angle: number): Result; diff --git a/packages/chili-geo/src/utils.ts b/packages/chili-geo/src/utils.ts index 707c112e..9f0de3d0 100644 --- a/packages/chili-geo/src/utils.ts +++ b/packages/chili-geo/src/utils.ts @@ -7,7 +7,7 @@ export class GeoUtils { let res: { edge: IEdge; point: XYZ } | undefined = undefined; let minDistance = Number.MAX_VALUE; for (const edge of wire.findSubShapes(ShapeType.Edge) as IEdge[]) { - let tempPoint = edge.asCurve().unwrap().nearestPoint(point); + let tempPoint = edge.asCurve().nearestPoint(point); let tempDistance = tempPoint.distanceTo(point); if (tempDistance < minDistance) { res = { edge, point: tempPoint }; @@ -37,15 +37,15 @@ export class GeoUtils { firstEdge = edge as IEdge; break; } - return this.curveNormal(firstEdge!.asCurve().unwrap()); + return this.curveNormal(firstEdge!.asCurve()); }; static findNextEdge(wire: IWire, edge: IEdge): Result { - let curve = edge.asCurve().unwrap(); + let curve = edge.asCurve(); let point = curve.point(curve.lastParameter()); for (const e of wire.iterSubShapes(ShapeType.Edge, true)) { if (e.isEqual(edge)) continue; - let testCurve = (e as IEdge).asCurve().unwrap(); + let testCurve = (e as IEdge).asCurve(); if ( point.distanceTo(testCurve.point(testCurve.firstParameter())) < Precision.Distance || point.distanceTo(testCurve.point(testCurve.lastParameter())) < Precision.Distance @@ -62,7 +62,7 @@ export class GeoUtils { } if (shape.shapeType === ShapeType.Edge) { - let curve = (shape as IEdge).asCurve().unwrap(); + let curve = (shape as IEdge).asCurve(); return this.curveNormal(curve); } diff --git a/packages/chili-occ/src/occGeometry.ts b/packages/chili-occ/src/occGeometry.ts index ba406dba..d996754c 100644 --- a/packages/chili-occ/src/occGeometry.ts +++ b/packages/chili-occ/src/occGeometry.ts @@ -1,18 +1,35 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { CurveType, ICircle, ICurve, IDisposable, ILine, Plane, XYZ } from "chili-core"; -import { Geom_Circle, Geom_Curve, Geom_Line, Geom_TrimmedCurve } from "../occ-wasm/chili_occ"; +import { + CurveType, + IBezierCurve, + IBoundedCurve, + ICircle, + ICurve, + IDisposable, + ILine, + IOffsetCurve, + ITrimmedCurve, + Plane, + XYZ, +} from "chili-core"; +import { + Geom_BezierCurve, + Geom_BoundedCurve, + Geom_Circle, + Geom_Curve, + Geom_Line, + Geom_OffsetCurve, + Geom_TrimmedCurve, +} from "../occ-wasm/chili_occ"; import { OccHelps } from "./occHelps"; export class OccCurve implements ICurve, IDisposable { - readonly curve: Geom_TrimmedCurve; readonly curveType: CurveType; - constructor(curve: Geom_Curve, start: number, end: number) { - let curveHandle = new occ.Handle_Geom_Curve_2(curve); + constructor(readonly curve: Geom_Curve) { this.curveType = OccHelps.getCurveType(curve); - this.curve = new occ.Geom_TrimmedCurve(curveHandle, start, end, true, true); } nearestPoint(point: XYZ): XYZ { @@ -46,14 +63,13 @@ export class OccCurve implements ICurve, IDisposable { } parameter(point: XYZ): number { - let api = new occ.GeomAPI_ProjectPointOnCurve_2(OccHelps.toPnt(point), this.curve.BasisCurve()); + let api = new occ.GeomAPI_ProjectPointOnCurve_2( + OccHelps.toPnt(point), + new occ.Handle_Geom_Curve_2(this.curve), + ); return api.LowerDistanceParameter(); } - trim(start: number, end: number) { - this.curve.SetTrim(start, end, true, true); - } - project(point: XYZ): XYZ[] { let result = new Array(); let api = new occ.GeomAPI_ProjectPointOnCurve_2( @@ -125,20 +141,8 @@ export class OccCurve implements ICurve, IDisposable { } export class OccLine extends OccCurve implements ILine { - constructor( - private line: Geom_Line, - start: number, - end: number, - ) { - super(line, start, end); - } - - get start(): XYZ { - return OccHelps.toXYZ(this.curve.StartPoint()); - } - - get endPoint(): XYZ { - return OccHelps.toXYZ(this.curve.EndPoint()); + constructor(private line: Geom_Line) { + super(line); } get direction(): XYZ { @@ -170,12 +174,8 @@ export class OccCircle extends OccCurve implements ICircle { this.circle.SetPosition(OccHelps.toAx2(value)); } - constructor( - private circle: Geom_Circle, - start: number, - end: number, - ) { - super(circle, start, end); + constructor(private circle: Geom_Circle) { + super(circle); } get center(): XYZ { @@ -194,3 +194,108 @@ export class OccCircle extends OccCurve implements ICircle { this.circle.SetRadius(value); } } + +export class OccBoundedCurve extends OccCurve implements IBoundedCurve { + constructor(private boundedCurve: Geom_BoundedCurve) { + super(boundedCurve); + } + + startPoint(): XYZ { + return OccHelps.toXYZ(this.boundedCurve.StartPoint()); + } + + endPoint(): XYZ { + return OccHelps.toXYZ(this.boundedCurve.EndPoint()); + } +} + +export class OccTrimmedCurve extends OccBoundedCurve implements ITrimmedCurve { + constructor(private trimmedCurve: Geom_TrimmedCurve) { + super(trimmedCurve); + } + + basisCurve(): ICurve { + return OccHelps.wrapCurve(this.trimmedCurve.BasisCurve().get()); + } +} + +export class OccOffsetCurve extends OccCurve implements IOffsetCurve { + constructor(private offsetCurve: Geom_OffsetCurve) { + super(offsetCurve); + } + + basisCurve(): ICurve { + return OccHelps.wrapCurve(this.offsetCurve.BasisCurve().get()); + } + + offset(): number { + return this.offsetCurve.Offset(); + } + + direction(): XYZ { + return OccHelps.toXYZ(this.offsetCurve.Direction()); + } +} + +export class OccBezierCurve extends OccBoundedCurve implements IBezierCurve { + constructor(private bezier: Geom_BezierCurve) { + super(bezier); + } + + weight(index: number): number { + return this.bezier.Weight(index); + } + + insertPoleAfter(index: number, point: XYZ, weight: number | undefined): void { + if (weight === undefined) { + this.bezier.InsertPoleAfter_1(index, OccHelps.toPnt(point)); + } else { + this.bezier.InsertPoleAfter_2(index, OccHelps.toPnt(point), weight); + } + } + + insertPoleBefore(index: number, point: XYZ, weight: number | undefined): void { + if (weight === undefined) { + this.bezier.InsertPoleBefore_1(index, OccHelps.toPnt(point)); + } else { + this.bezier.InsertPoleBefore_2(index, OccHelps.toPnt(point), weight); + } + } + + removePole(index: number): void { + this.bezier.RemovePole(index); + } + + setPole(index: number, point: XYZ, weight: number | undefined): void { + if (weight === undefined) { + this.bezier.SetPole_1(index, OccHelps.toPnt(point)); + } else { + this.bezier.SetPole_2(index, OccHelps.toPnt(point), weight); + } + } + + setWeight(index: number, weight: number): void { + this.setWeight(index, weight); + } + + nbPoles(): number { + return this.bezier.NbPoles(); + } + + pole(index: number): XYZ { + return OccHelps.toXYZ(this.bezier.Pole(index)); + } + + degree(): number { + return this.bezier.Degree(); + } + + poles(): XYZ[] { + let result: XYZ[] = []; + let pls = this.bezier.Poles_2(); + for (let i = 1; i <= pls.Length(); i++) { + result.push(OccHelps.toXYZ(pls.Value(i))); + } + return result; + } +} diff --git a/packages/chili-occ/src/occHelps.ts b/packages/chili-occ/src/occHelps.ts index c72a89fa..12320fdf 100644 --- a/packages/chili-occ/src/occHelps.ts +++ b/packages/chili-occ/src/occHelps.ts @@ -1,9 +1,25 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { CurveType, IShape, Id, JoinType, Matrix4, Orientation, Plane, ShapeType, XYZ } from "chili-core"; +import { + CurveType, + ICurve, + IShape, + Id, + JoinType, + Matrix4, + Orientation, + Plane, + ShapeType, + XYZ, +} from "chili-core"; import { GeomAbs_JoinType, + Geom_BezierCurve, + Geom_Circle, Geom_Curve, + Geom_Line, + Geom_OffsetCurve, + Geom_TrimmedCurve, TopAbs_ShapeEnum, TopoDS_Shape, gp_Ax2, @@ -26,6 +42,7 @@ import { OccVertex, OccWire, } from "./occShape"; +import { OccBezierCurve, OccCircle, OccLine, OccOffsetCurve, OccTrimmedCurve } from "./occGeometry"; export class OccHelps { static toXYZ(p: gp_Pnt | gp_Dir | gp_Vec): XYZ { @@ -128,7 +145,9 @@ export class OccHelps { else if (isType("Geom_BezierCurve")) return CurveType.BezierCurve; else if (isType("Geom_BSplineCurve")) return CurveType.BSplineCurve; else if (isType("Geom_OffsetCurve")) return CurveType.OffsetCurve; - else return CurveType.OtherCurve; + else if (isType("Geom_TrimmedCurve")) return CurveType.TrimmedCurve; + + throw new Error("Unknown curve type"); } static getShapeType(shape: TopoDS_Shape): ShapeType { @@ -230,6 +249,21 @@ export class OccHelps { } } + static wrapCurve(curve: Geom_Curve): ICurve { + let isType = (type: string) => curve.IsInstance_2(type); + if (isType("Geom_Line")) return new OccLine(curve as Geom_Line); + else if (isType("Geom_Circle")) return new OccCircle(curve as Geom_Circle); + // else if (isType("Geom_Ellipse")) return new OccLine(curve as Geom_Line); + // else if (isType("Geom_Hyperbola")) return new OccLine(curve as Geom_Line); + // else if (isType("Geom_Parabola")) return new OccLine(curve as Geom_Line); + else if (isType("Geom_BezierCurve")) return new OccBezierCurve(curve as Geom_BezierCurve); + // else if (isType("Geom_BSplineCurve")) return new OccLine(curve as Geom_Line); + else if (isType("Geom_OffsetCurve")) return new OccOffsetCurve(curve as Geom_OffsetCurve); + else if (isType("Geom_TrimmedCurve")) return new OccTrimmedCurve(curve as Geom_TrimmedCurve); + + throw new Error("Unknown curve type"); + } + static getActualShape(shape: TopoDS_Shape): TopoDS_Shape { switch (shape.ShapeType()) { case occ.TopAbs_ShapeEnum.TopAbs_COMPOUND: diff --git a/packages/chili-occ/src/occShape.ts b/packages/chili-occ/src/occShape.ts index 12a2730d..53934d96 100644 --- a/packages/chili-occ/src/occShape.ts +++ b/packages/chili-occ/src/occShape.ts @@ -11,6 +11,7 @@ import { IShapeMeshData, IShell, ISolid, + ITrimmedCurve, IVertex, IWire, Id, @@ -37,7 +38,7 @@ import { } from "../occ-wasm/chili_occ"; import { OccShapeConverter } from "./occConverter"; -import { OccCircle, OccCurve, OccLine } from "./occGeometry"; +import { OccCircle, OccCurve, OccLine, OccTrimmedCurve } from "./occGeometry"; import { OccHelps } from "./occHelps"; import { OccMesh } from "./occMesh"; @@ -157,12 +158,6 @@ export class OccEdge extends OccShape implements IEdge { super(shape, id); } - static fromCurve(curve: OccCurve): OccEdge { - let trimmed = new occ.Handle_Geom_TrimmedCurve_2(curve.curve); - let edge = new occ.BRepBuilderAPI_MakeEdge_24(trimmed); - return new OccEdge(edge.Edge()); - } - intersect(other: IEdge | Ray): XYZ[] { if (other instanceof Ray) { let start = OccHelps.toPnt(other.location); @@ -211,22 +206,11 @@ export class OccEdge extends OccShape implements IEdge { return Result.ok(new OccEdge(edge.Edge())); } - asCurve(): Result { + asCurve(): ITrimmedCurve { let s: any = { current: 0 }; let e: any = { current: 0 }; - let curve = occ.BRep_Tool.Curve_2(this.shape, s, e).get(); - let curveType = OccHelps.getCurveType(curve); - if (curveType === CurveType.Line) { - return Result.ok(new OccLine(curve as Geom_Line, s.current, e.current)); - } - if (curveType === CurveType.Circle) { - return Result.ok(new OccCircle(curve as Geom_Circle, s.current, e.current)); - } - if (curveType === CurveType.OffsetCurve) { - return Result.ok(new OccCurve(curve, s.current, e.current)); - } - Logger.warn(`Unsupported curve type: ${curveType}`); - return Result.ok(new OccCurve(curve, s.current, e.current)); + let curve = occ.BRep_Tool.Curve_2(this.shape, s, e); + return new OccTrimmedCurve(new occ.Geom_TrimmedCurve(curve, s.current, e.current, true, true)); } } diff --git a/packages/chili-occ/src/shapeFactory.ts b/packages/chili-occ/src/shapeFactory.ts index 87f4d13d..7f7391de 100644 --- a/packages/chili-occ/src/shapeFactory.ts +++ b/packages/chili-occ/src/shapeFactory.ts @@ -1,6 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { + IBezierCurve, ICompound, IEdge, IFace, @@ -20,12 +21,14 @@ import { import { BRepAlgoAPI_BooleanOperation, BRepBuilderAPI_MakeWire, + Geom_BezierCurve, Message_ProgressRange, TopoDS_Shape, } from "../occ-wasm/chili_occ"; import { OccShapeConverter } from "./occConverter"; import { OccHelps } from "./occHelps"; import { OccCompound, OccEdge, OccFace, OccShape, OccSolid, OccVertex, OccWire } from "./occShape"; +import { OccBezierCurve } from "./occGeometry"; export class ShapeFactory implements IShapeFactory { readonly converter: IShapeConverter = new OccShapeConverter(); @@ -91,6 +94,25 @@ export class ShapeFactory implements IShapeFactory { return Result.err("Create arc error"); } + bezier(points: XYZ[], weights?: number[]): Result { + let tolPoints = new occ.TColgp_Array1OfPnt_2(1, points.length); + points.forEach((x, i) => { + tolPoints.SetValue(i + 1, OccHelps.toPnt(x)); + }); + let bezier: Geom_BezierCurve; + if (weights) { + let tolWeights = new occ.TColStd_Array1OfReal_2(1, weights.length); + weights.forEach((x, i) => { + tolWeights.SetValue(i + 1, x); + }); + bezier = new occ.Geom_BezierCurve_2(tolPoints, tolPoints); + } else { + bezier = new occ.Geom_BezierCurve_1(tolPoints); + } + let edge = new occ.BRepBuilderAPI_MakeEdge_24(new occ.Handle_Geom_Curve_2(bezier)); + return Result.ok(new OccEdge(edge.Edge())); + } + circle(normal: XYZ, center: XYZ, radius: number): Result { if (MathUtils.almostEqual(radius, 0)) { return Result.err("Radius cannot be 0"); diff --git a/packages/chili-occ/test/occ.test.ts b/packages/chili-occ/test/occ.test.ts index 4266b1e3..c4443710 100644 --- a/packages/chili-occ/test/occ.test.ts +++ b/packages/chili-occ/test/occ.test.ts @@ -23,10 +23,7 @@ describe("shape test", () => { expect(make1.IsDone()).toBeTruthy(); let edge1 = new OccEdge(make1.Edge()); expect(edge1.shapeType).toBe(ShapeType.Edge); - let ps = edge1 - .asCurve() - .unwrap()! - .project(new XYZ(5, 0, 0)); + let ps = edge1.asCurve().project(new XYZ(5, 0, 0)); expect(ps.length).toBe(1); expect(ps[0].x).toBe(5); @@ -93,12 +90,12 @@ describe("geometry test", () => { let make = new occ.BRepBuilderAPI_MakeEdge_3(start, end); expect(make.IsDone()).toBeTruthy(); let edge = new OccEdge(make.Edge()); - let curve = edge.asCurve().unwrap()!; + let curve = edge.asCurve(); expect(curve instanceof OccCurve).toBe(true); expect(edge.length()).toBe(20); expect(curve.point(curve.firstParameter()).x).toBe(-10); expect(curve.point(curve.lastParameter()).x).toBe(10); - expect(curve.curveType).toBe(CurveType.Line); + expect(curve.curveType).toBe(CurveType.TrimmedCurve); expect(curve.point(0).x).toBe(-10); expect(curve.firstParameter()).toBe(0); expect(curve.lastParameter()).toBe(20); @@ -208,9 +205,9 @@ describe("curve test", () => { let shape = new OccEdge(e1); shape.matrix = Matrix4.createTranslation(10, 20, 30); let edge = shape.mesh.edges?.groups.at(0)?.shape as OccEdge; - let p2 = shape.asCurve().unwrap().point(0); + let p2 = shape.asCurve().point(0); expect(p2?.x).toBeCloseTo(20); - expect(edge.asCurve().unwrap().point(0).x).toBeCloseTo(20); + expect(edge.asCurve().point(0).x).toBeCloseTo(20); }); }); }); diff --git a/packages/chili-three/src/threeView.ts b/packages/chili-three/src/threeView.ts index 8cf19327..3dc22987 100644 --- a/packages/chili-three/src/threeView.ts +++ b/packages/chili-three/src/threeView.ts @@ -121,8 +121,9 @@ export class ThreeView extends Observable implements IView { protected initRender(): WebGLRenderer { let renderer = new WebGLRenderer({ - antialias: false, + antialias: true, alpha: true, + logarithmicDepthBuffer: true, }); renderer.setPixelRatio(window.devicePixelRatio); return renderer; diff --git a/packages/chili-three/test/testEdge.ts b/packages/chili-three/test/testEdge.ts index 3980cc40..cfa450ac 100644 --- a/packages/chili-three/test/testEdge.ts +++ b/packages/chili-three/test/testEdge.ts @@ -5,6 +5,7 @@ import { IEdge, IShape, IShapeMeshData, + ITrimmedCurve, LineType, Matrix4, Orientation, @@ -43,8 +44,8 @@ export class TestEdge implements IEdge { length(): number { return this.start.distanceTo(this.end); } - asCurve(): Result { - return Result.err("this"); + asCurve(): ITrimmedCurve { + throw new Error("Method not implemented."); } get id(): string { return "testEdge"; diff --git a/packages/chili-ui/src/profile/ribbon.ts b/packages/chili-ui/src/profile/ribbon.ts index a5a4b8b7..be689ec8 100644 --- a/packages/chili-ui/src/profile/ribbon.ts +++ b/packages/chili-ui/src/profile/ribbon.ts @@ -23,6 +23,7 @@ export const DefaultRibbon: RibbonTabProfile[] = [ "create.circle", "create.polygon", "create.box", + "create.bezier", ], }, { diff --git a/packages/chili/src/commands/create/bezier.ts b/packages/chili/src/commands/create/bezier.ts new file mode 100644 index 00000000..b6cc737e --- /dev/null +++ b/packages/chili/src/commands/create/bezier.ts @@ -0,0 +1,93 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { + AsyncController, + EditableGeometryEntity, + GeometryModel, + ShapeMeshData, + XYZ, + command, +} from "chili-core"; +import { Dimension, SnapPointData } from "../../snap"; +import { IStep, PointStep } from "../../step"; +import { CreateCommand } from "../createCommand"; + +@command({ + name: "create.bezier", + display: "command.bezier", + icon: "icon-bezier", +}) +export class BezierCommand extends CreateCommand { + static count = 0; + + protected override create(): GeometryModel { + let bezier = this.application.shapeFactory.bezier(this.stepDatas.map((x) => x.point!)); + let body = new EditableGeometryEntity(this.document, bezier.unwrap()); + return new GeometryModel(this.document, `Bezier ${BezierCommand.count++}`, body); + } + + protected override async executeSteps(): Promise { + let steps = this.getSteps(); + let firstStep = true; + while (true) { + let step = firstStep ? steps[0] : steps[1]; + if (firstStep) firstStep = false; + this.controller = new AsyncController(); + let data = await step.execute(this.document, this.controller); + if (data === undefined) { + return this.controller.result?.status === "success"; + } + this.stepDatas.push(data); + } + } + + protected override getSteps(): IStep[] { + let firstStep = new PointStep("operate.pickFistPoint"); + let secondStep = new PointStep("operate.pickNextPoint", this.getNextData); + return [firstStep, secondStep]; + } + + private getNextData = (): SnapPointData => { + return { + refPoint: () => this.stepDatas.at(-1)!.point!, + dimension: Dimension.D1D2D3, + validators: [this.validator], + preview: this.preview, + }; + }; + + private preview = (point: XYZ | undefined): ShapeMeshData[] => { + let ps: ShapeMeshData[] = this.stepDatas.map((data) => this.previewPoint(data.point!)); + let points = this.stepDatas.map((data) => data.point) as XYZ[]; + if (point) { + points.push(point); + } + if (points.length > 1) { + ps.push(...this.previewLines(points)); + let bezier = this.application.shapeFactory.bezier(points); + ps.push(bezier.unwrap().mesh.edges!); + } + + return ps; + }; + + private previewLines = (points: XYZ[]): ShapeMeshData[] => { + if (points.length < 2) { + return []; + } + let res: ShapeMeshData[] = []; + for (let i = 1; i < points.length; i++) { + res.push(this.previewLine(points[i - 1], points[i])); + } + return res; + }; + + private validator = (point: XYZ): boolean => { + for (const data of this.stepDatas) { + if (point.distanceTo(data.point!) < 0.001) { + return false; + } + } + return true; + }; +} diff --git a/packages/chili/src/commands/create/index.ts b/packages/chili/src/commands/create/index.ts index b80db133..51e6f332 100644 --- a/packages/chili/src/commands/create/index.ts +++ b/packages/chili/src/commands/create/index.ts @@ -1,6 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. export * from "./arc"; +export * from "./bezier"; export * from "./box"; export * from "./circle"; export * from "./converter"; diff --git a/packages/chili/src/commands/create/offset.ts b/packages/chili/src/commands/create/offset.ts index 884ea6db..0900b55c 100644 --- a/packages/chili/src/commands/create/offset.ts +++ b/packages/chili/src/commands/create/offset.ts @@ -84,7 +84,7 @@ export class OffsetCommand extends CreateCommand { } private getEdgeAxis(edge: IEdge, start: XYZ) { - let curve = edge.asCurve().unwrap(); + let curve = edge.asCurve(); let direction = curve.dn(curve.parameter(start), 1); let normal = GeoUtils.normal(edge); return { @@ -101,9 +101,9 @@ export class OffsetCommand extends CreateCommand { } let nearest = GeoUtils.nearestPoint(wire, start); let nextEdge = GeoUtils.findNextEdge(wire, nearest.edge).unwrap(); - let direction = nearest.edge.asCurve().unwrap().dn(0, 1); + let direction = nearest.edge.asCurve().dn(0, 1); let scale = nearest.edge.orientation() === nextEdge.orientation() ? 1 : -1; - let nextDirection = nextEdge.asCurve().unwrap().dn(0, 1).multiply(scale); + let nextDirection = nextEdge.asCurve().dn(0, 1).multiply(scale); if (direction.cross(nextDirection).normalize()?.isOppositeTo(normal)) { direction = direction.multiply(-1); } diff --git a/packages/chili/src/commands/create/polygon.ts b/packages/chili/src/commands/create/polygon.ts index fc6fec26..648aa75f 100644 --- a/packages/chili/src/commands/create/polygon.ts +++ b/packages/chili/src/commands/create/polygon.ts @@ -40,7 +40,9 @@ export class Polygon extends CreateFaceableCommand { if (firstStep) firstStep = false; this.controller = new AsyncController(); let data = await step.execute(this.document, this.controller); - if (data === undefined) return false; + if (data === undefined) { + return this.controller.result?.status === "success"; + } this.stepDatas.push(data); if (this.isClose(data)) { return true; diff --git a/packages/chili/src/commands/create/revolve.ts b/packages/chili/src/commands/create/revolve.ts index 165a5f4d..f893d8a0 100644 --- a/packages/chili/src/commands/create/revolve.ts +++ b/packages/chili/src/commands/create/revolve.ts @@ -36,7 +36,7 @@ export class Revolve extends CreateCommand { protected override create(): GeometryModel { let shape = this.stepDatas[0].shapes[0].shape; // todo assert - let edge = (this.stepDatas[1].shapes[0].shape as IEdge).asCurve().value as ILine; + let edge = (this.stepDatas[1].shapes[0].shape as IEdge).asCurve().basisCurve() as ILine; let axis = new Ray(edge.point(0), edge.direction); let body = new RevolveBody(this.document, shape, axis, this._angle); return new GeometryModel(this.document, `Revolve ${count++}`, body); @@ -58,8 +58,7 @@ class LineFilter implements IShapeFilter { allow(shape: IShape): boolean { if (shape.shapeType === ShapeType.Edge) { let edge = shape as IEdge; - let curve = edge.asCurve().value; - if (curve === undefined) return false; + let curve = edge.asCurve(); return ICurve.isLine(curve); } return false; diff --git a/packages/chili/src/snap/objectSnap.ts b/packages/chili/src/snap/objectSnap.ts index 3647a559..0b26b328 100644 --- a/packages/chili/src/snap/objectSnap.ts +++ b/packages/chili/src/snap/objectSnap.ts @@ -119,10 +119,7 @@ export class ObjectSnap implements ISnapper { VisualConfig.hintVertexSize, VisualConfig.hintVertexColor, ); - this._hintVertex = [ - view.document.visual.context, - view.document.visual.context.displayMesh(data), - ]; + this._hintVertex = [view.document.visual.context, view.document.visual.context.displayMesh(data)]; } private snapeInvisible(view: IView, x: number, y: number): SnapedData | undefined { @@ -158,9 +155,9 @@ export class ObjectSnap implements ISnapper { if (shape.shape.shapeType === ShapeType.Edge) { if (this._invisibleInfos.has(shape)) return; let curve = (shape.shape as IEdge).asCurve(); - if (!curve.isOk) return; - if (ICurve.isCircle(curve.value)) { - this.showCircleCenter(curve.value, view, shape); + let basisCurve = curve.basisCurve(); + if (ICurve.isCircle(basisCurve)) { + this.showCircleCenter(basisCurve, view, shape); } } } @@ -210,8 +207,7 @@ export class ObjectSnap implements ISnapper { ) return result; let curve = (shape.shape as IEdge).asCurve(); - if (!curve.isOk) return result; - let point = curve.value.project(this.referencePoint()).at(0); + let point = curve.project(this.referencePoint()).at(0); if (point === undefined) return result; result.push({ view, @@ -274,20 +270,17 @@ export class ObjectSnap implements ISnapper { private getEdgeFeaturePoints(view: IView, shape: VisualShapeData, infos: SnapedData[]) { let curve = (shape.shape as IEdge).asCurve(); - if (!curve.isOk) return; - let start = curve.value.point(curve.value.firstParameter()); - let end = curve.value.point(curve.value.lastParameter()); + let start = curve.point(curve.firstParameter()); + let end = curve.point(curve.lastParameter()); let addPoint = (point: XYZ, info: string) => infos.push({ view, point: point, info, shapes: [shape] }); if (ObjectSnapType.has(this._snapType, ObjectSnapType.endPoint)) { addPoint(start, I18n.translate("snap.end")); addPoint(end, I18n.translate("snap.end")); } - if ( - ObjectSnapType.has(this._snapType, ObjectSnapType.midPoint) && - curve.value.curveType === CurveType.Line - ) { - addPoint(XYZ.center(start, end), I18n.translate("snap.mid")); + if (ObjectSnapType.has(this._snapType, ObjectSnapType.midPoint)) { + let mid = curve.point((curve.firstParameter() + curve.lastParameter()) * 0.5); + addPoint(mid, I18n.translate("snap.mid")); } } } diff --git a/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts b/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts index b303f9cb..2f388466 100644 --- a/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts +++ b/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts @@ -217,6 +217,9 @@ export abstract class SnapEventHandler implements IEventHandler { if (event.key === "Escape") { this._snaped = undefined; this.cancel(); + } else if (event.key === "Enter") { + this._snaped = undefined; + this.finish(); } else if (["-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(event.key)) { PubSub.default.pub("showInput", event.key, (text: string) => { let error = this.inputError(text); diff --git a/public/iconfont.js b/public/iconfont.js index 7144d667..23eb0304 100644 --- a/public/iconfont.js +++ b/public/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_3585225='',function(v){var h=(h=document.getElementsByTagName("script"))[h.length-1],l=h.getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var a,z,t,m,i,c=function(h,l){l.parentNode.insertBefore(h,l)};if(l&&!v.__iconfont__svg__cssinject__){v.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(h){console&&console.log(h)}}a=function(){var h,l=document.createElement("div");l.innerHTML=v._iconfont_svg_string_3585225,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(h=document.body).firstChild?c(l,h.firstChild):h.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(z=function(){document.removeEventListener("DOMContentLoaded",z,!1),a()},document.addEventListener("DOMContentLoaded",z,!1)):document.attachEvent&&(t=a,m=v.document,i=!1,p(),m.onreadystatechange=function(){"complete"==m.readyState&&(m.onreadystatechange=null,o())})}function o(){i||(i=!0,t())}function p(){try{m.documentElement.doScroll("left")}catch(h){return void setTimeout(p,50)}o()}}(window); \ No newline at end of file +window._iconfont_svg_string_3585225='',function(v){var h=(h=document.getElementsByTagName("script"))[h.length-1],l=h.getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var z,a,m,t,i,c=function(h,l){l.parentNode.insertBefore(h,l)};if(l&&!v.__iconfont__svg__cssinject__){v.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(h){console&&console.log(h)}}z=function(){var h,l=document.createElement("div");l.innerHTML=v._iconfont_svg_string_3585225,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(h=document.body).firstChild?c(l,h.firstChild):h.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(z,0):(a=function(){document.removeEventListener("DOMContentLoaded",a,!1),z()},document.addEventListener("DOMContentLoaded",a,!1)):document.attachEvent&&(m=z,t=v.document,i=!1,p(),t.onreadystatechange=function(){"complete"==t.readyState&&(t.onreadystatechange=null,o())})}function o(){i||(i=!0,m())}function p(){try{t.documentElement.doScroll("left")}catch(h){return void setTimeout(p,50)}o()}}(window); \ No newline at end of file