UNPKG

replicad

Version:

The library to build browser based 3D models with code

1 lines 1.25 MB
{"version":3,"file":"replicad.umd.cjs","sources":["../src/oclib.ts","../src/register.ts","../src/constants.ts","../src/geom.ts","../src/geomHelpers.ts","../src/lib2d/definitions.ts","../src/definitionMaps.ts","../src/utils/precisionRound.ts","../src/utils/range.ts","../src/utils/zip.ts","../src/utils/round2.ts","../src/lib2d/utils.ts","../src/lib2d/ocWrapper.ts","../src/lib2d/BoundingBox2d.ts","../src/lib2d/vectorOperations.ts","../src/lib2d/Curve2D.ts","../src/lib2d/approximations.ts","../src/lib2d/intersections.ts","../src/lib2d/makeCurves.ts","../src/lib2d/offset.ts","../src/lib2d/customCorners.ts","../../../node_modules/.pnpm/flatqueue@2.0.3/node_modules/flatqueue/index.js","../../../node_modules/.pnpm/flatbush@4.0.0/node_modules/flatbush/index.js","../src/lib2d/stitching.ts","../src/utils/round5.ts","../src/lib2d/svgPath.ts","../src/finders/definitions.ts","../src/utils/ProgressRange.ts","../src/measureShape.ts","../src/finders/generic3dfinder.ts","../src/finders/edgeFinder.ts","../src/finders/faceFinder.ts","../src/finders/cornerFinder.ts","../src/finders/index.ts","../src/shapes.ts","../src/shapeHelpers.ts","../src/sketcherlib.ts","../src/addThickness.ts","../src/sketches/Sketch.ts","../src/Sketcher.ts","../src/curves.ts","../src/blueprints/svg.ts","../src/blueprints/Blueprint.ts","../src/Sketcher2d.ts","../src/shortcuts.ts","../src/sketches/CompoundSketch.ts","../src/blueprints/CompoundBlueprint.ts","../src/sketches/Sketches.ts","../src/blueprints/Blueprints.ts","../src/blueprints/lib.ts","../src/blueprints/cannedBlueprints.ts","../src/blueprints/booleanOperations.ts","../src/blueprints/boolean2D.ts","../src/sketches/cannedSketches.ts","../../../node_modules/.pnpm/opentype.js@1.3.4/node_modules/opentype.js/dist/opentype.module.js","../src/text.ts","../src/importers.ts","../src/projection/ProjectionCamera.ts","../src/projection/makeProjectedEdges.ts","../src/blueprints/offset.ts","../src/blueprints/customCorners.ts","../src/blueprints/approximations.ts","../src/draw.ts","../src/utils/uuid.ts","../src/export/assemblyExporter.ts"],"sourcesContent":["import { OpenCascadeInstance } from \"replicad-opencascadejs\";\n\nconst OC: { library: OpenCascadeInstance | null } = {\n library: null,\n};\n\nexport const setOC = (oc: OpenCascadeInstance): void => {\n OC.library = oc;\n};\n\nexport const getOC = (): OpenCascadeInstance => {\n if (!OC.library) throw new Error(\"oppencascade has not been loaded\");\n return OC.library;\n};\n","import { getOC } from \"./oclib\";\nimport { OpenCascadeInstance } from \"replicad-opencascadejs\";\n\ninterface Deletable {\n delete: () => void;\n}\n\nif (!(globalThis as any).FinalizationRegistry) {\n console.log(\"Garbage collection will not work\");\n\n (globalThis as any).FinalizationRegistry = (() => ({\n register: () => null,\n unregister: () => null,\n })) as any;\n}\n\nconst deletetableRegistry = new (globalThis as any).FinalizationRegistry(\n (heldValue: Deletable) => {\n try {\n heldValue.delete();\n } catch (e) {\n console.error(e);\n }\n }\n);\n\nexport class WrappingObj<Type extends Deletable> {\n protected oc: OpenCascadeInstance;\n private _wrapped: Type | null;\n\n constructor(wrapped: Type) {\n this.oc = getOC();\n if (wrapped) {\n deletetableRegistry.register(this, wrapped, wrapped);\n }\n this._wrapped = wrapped;\n }\n\n get wrapped(): Type {\n if (this._wrapped === null) throw new Error(\"This object has been deleted\");\n return this._wrapped;\n }\n\n set wrapped(newWrapped: Type) {\n if (this._wrapped) {\n deletetableRegistry.unregister(this._wrapped);\n this._wrapped.delete();\n }\n\n deletetableRegistry.register(this, newWrapped, newWrapped);\n this._wrapped = newWrapped;\n }\n\n delete() {\n deletetableRegistry.unregister(this.wrapped);\n this.wrapped?.delete();\n this._wrapped = null;\n }\n}\n\nexport const GCWithScope = () => {\n function gcWithScope<Type extends Deletable>(value: Type): Type {\n deletetableRegistry.register(gcWithScope, value);\n return value;\n }\n\n return gcWithScope;\n};\n\nexport const GCWithObject = (obj: any) => {\n function registerForGC<Type extends Deletable>(value: Type): Type {\n deletetableRegistry.register(obj, value);\n return value;\n }\n\n return registerForGC;\n};\n\nexport const localGC = (\n debug?: boolean\n): [\n <T extends Deletable>(v: T) => T,\n () => void,\n Set<Deletable> | undefined\n] => {\n const cleaner = new Set<Deletable>();\n\n return [\n <T extends Deletable>(v: T): T => {\n cleaner.add(v);\n return v;\n },\n\n () => {\n [...cleaner.values()].forEach((d) => d.delete());\n cleaner.clear();\n },\n debug ? cleaner : undefined,\n ];\n};\n","export const HASH_CODE_MAX = 2147483647;\nexport const DEG2RAD = Math.PI / 180;\nexport const RAD2DEG = 180 / Math.PI;\n","import { WrappingObj, GCWithScope } from \"./register.js\";\nimport { DEG2RAD, RAD2DEG } from \"./constants.js\";\nimport { getOC } from \"./oclib.js\";\n\nimport {\n gp_Ax1,\n gp_Ax2,\n gp_Ax3,\n gp_Vec,\n gp_XYZ,\n gp_Dir,\n gp_Pnt,\n OpenCascadeInstance,\n gp_Trsf,\n TopoDS_Shape,\n Bnd_Box,\n} from \"replicad-opencascadejs\";\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000;\n\nexport type SimplePoint = [number, number, number];\nexport type Point =\n | SimplePoint\n | Vector\n | [number, number]\n | { XYZ: () => gp_XYZ; delete: () => void };\n\nexport function isPoint(p: unknown): p is Point {\n if (Array.isArray(p)) return p.length === 3 || p.length === 2;\n else if (p instanceof Vector) return true;\n else if (p && typeof (p as any)?.XYZ === \"function\") return true;\n return false;\n}\n\nexport const makeAx3 = (center: Point, dir: Point, xDir?: Point): gp_Ax3 => {\n const oc = getOC();\n const origin = asPnt(center);\n const direction = asDir(dir);\n\n let axis: gp_Ax3;\n if (xDir) {\n const xDirection = asDir(xDir);\n axis = new oc.gp_Ax3_3(origin, direction, xDirection);\n xDirection.delete();\n } else {\n axis = new oc.gp_Ax3_4(origin, direction);\n }\n origin.delete();\n direction.delete();\n return axis;\n};\n\nexport const makeAx2 = (center: Point, dir: Point, xDir?: Point): gp_Ax2 => {\n const oc = getOC();\n const origin = asPnt(center);\n const direction = asDir(dir);\n\n let axis: gp_Ax2;\n if (xDir) {\n const xDirection = asDir(xDir);\n axis = new oc.gp_Ax2_2(origin, direction, xDirection);\n xDirection.delete();\n } else {\n axis = new oc.gp_Ax2_3(origin, direction);\n }\n origin.delete();\n direction.delete();\n return axis;\n};\n\nexport const makeAx1 = (center: Point, dir: Point): gp_Ax1 => {\n const oc = getOC();\n const origin = asPnt(center);\n const direction = asDir(dir);\n const axis = new oc.gp_Ax1_2(origin, direction);\n origin.delete();\n direction.delete();\n return axis;\n};\n\nconst makeVec = (vector: Point = [0, 0, 0]): gp_Vec => {\n const oc = getOC();\n\n if (Array.isArray(vector)) {\n if (vector.length === 3) return new oc.gp_Vec_4(...vector);\n else if (vector.length === 2) return new oc.gp_Vec_4(...vector, 0);\n } else if (vector instanceof Vector) {\n return new oc.gp_Vec_3(vector.wrapped.XYZ());\n } else if (vector.XYZ) return new oc.gp_Vec_3(vector.XYZ());\n return new oc.gp_Vec_4(0, 0, 0);\n};\n\nexport class Vector extends WrappingObj<gp_Vec> {\n constructor(vector: Point = [0, 0, 0]) {\n super(makeVec(vector));\n }\n\n get repr(): string {\n return `x: ${round3(this.x)}, y: ${round3(this.y)}, z: ${round3(this.z)}`;\n }\n\n get x(): number {\n return this.wrapped.X();\n }\n\n get y(): number {\n return this.wrapped.Y();\n }\n\n get z(): number {\n return this.wrapped.Z();\n }\n\n get Length(): number {\n return this.wrapped.Magnitude();\n }\n\n toTuple(): [number, number, number] {\n return [this.x, this.y, this.z];\n }\n\n cross(v: Vector): Vector {\n return new Vector(this.wrapped.Crossed(v.wrapped));\n }\n\n dot(v: Vector): number {\n return this.wrapped.Dot(v.wrapped);\n }\n\n sub(v: Vector): Vector {\n return new Vector(this.wrapped.Subtracted(v.wrapped));\n }\n\n add(v: Vector): Vector {\n return new Vector(this.wrapped.Added(v.wrapped));\n }\n\n multiply(scale: number): Vector {\n return new Vector(this.wrapped.Multiplied(scale));\n }\n\n normalized(): Vector {\n return new Vector(this.wrapped.Normalized());\n }\n\n normalize(): Vector {\n this.wrapped.Normalize();\n return this;\n }\n\n getCenter(): Vector {\n return this;\n }\n\n getAngle(v: Vector): number {\n return this.wrapped.Angle(v.wrapped) * RAD2DEG;\n }\n\n projectToPlane(plane: Plane): Vector {\n const base = plane.origin;\n const normal = plane.zDir;\n\n const v1 = this.sub(base);\n\n const v2 = normal.multiply(v1.dot(normal) / normal.Length ** 2);\n const projection = this.sub(v2);\n\n v1.delete();\n v2.delete();\n\n return projection;\n }\n equals(other: Vector): boolean {\n return this.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001);\n }\n\n toPnt(): gp_Pnt {\n return new this.oc.gp_Pnt_2(this.wrapped.XYZ());\n }\n\n toDir(): gp_Dir {\n return new this.oc.gp_Dir_3(this.wrapped.XYZ());\n }\n\n rotate(\n angle: number,\n center: Point = [0, 0, 0],\n direction: Point = [0, 0, 1]\n ): Vector {\n const ax = makeAx1(center, direction);\n this.wrapped.Rotate(ax, angle * DEG2RAD);\n ax.delete();\n return this;\n }\n}\n\ntype Direction = Point | \"X\" | \"Y\" | \"Z\";\n\nconst DIRECTIONS: Record<string, Point> = {\n X: [1, 0, 0],\n Y: [0, 1, 0],\n Z: [0, 0, 1],\n};\n\nexport function makeDirection(p: Direction): Point {\n if (p === \"X\" || p === \"Y\" || p === \"Z\") {\n return DIRECTIONS[p];\n }\n return p;\n}\n\nexport function asPnt(coords: Point): gp_Pnt {\n const v = new Vector(coords);\n const pnt = v.toPnt();\n v.delete();\n return pnt;\n}\n\nexport function asDir(coords: Point): gp_Dir {\n const v = new Vector(coords);\n const dir = v.toDir();\n v.delete();\n return dir;\n}\n\ntype CoordSystem = \"reference\" | { origin: Point; zDir: Point; xDir: Point };\n\nexport class Transformation extends WrappingObj<gp_Trsf> {\n constructor(transform?: gp_Trsf) {\n const oc = getOC();\n super(transform || new oc.gp_Trsf_1());\n }\n\n translate(xDist: number, yDist: number, zDist: number): Transformation;\n translate(vector: Point): Transformation;\n translate(\n xDistOrVector: number | Point,\n yDist = 0,\n zDist = 0\n ): Transformation {\n const translation = new Vector(\n typeof xDistOrVector === \"number\"\n ? [xDistOrVector, yDist, zDist]\n : xDistOrVector\n );\n\n this.wrapped.SetTranslation_1(translation.wrapped);\n\n return this;\n }\n\n rotate(\n angle: number,\n position: Point = [0, 0, 0],\n direction: Point = [0, 0, 1]\n ): Transformation {\n const dir = asDir(direction);\n const origin = asPnt(position);\n const axis = new this.oc.gp_Ax1_2(origin, dir);\n\n this.wrapped.SetRotation_1(axis, angle * DEG2RAD);\n axis.delete();\n dir.delete();\n origin.delete();\n\n return this;\n }\n\n mirror(\n inputPlane: Plane | PlaneName | Point = \"YZ\",\n inputOrigin?: Point\n ): this {\n const r = GCWithScope();\n\n let origin: Point;\n let direction: Point;\n\n if (typeof inputPlane === \"string\") {\n const plane = r(createNamedPlane(inputPlane, inputOrigin));\n origin = plane.origin;\n direction = plane.zDir;\n } else if (inputPlane instanceof Plane) {\n origin = inputOrigin || inputPlane.origin;\n direction = inputPlane.zDir;\n } else {\n origin = inputOrigin || [0, 0, 0];\n direction = inputPlane;\n }\n\n const mirrorAxis = r(makeAx2(origin, direction));\n this.wrapped.SetMirror_3(mirrorAxis);\n\n return this;\n }\n\n scale(center: Point, scale: number): this {\n const pnt = asPnt(center);\n this.wrapped.SetScale(pnt, scale);\n pnt.delete();\n return this;\n }\n\n coordSystemChange(fromSystem: CoordSystem, toSystem: CoordSystem): this {\n const r = GCWithScope();\n const fromAx = r(\n fromSystem === \"reference\"\n ? new this.oc.gp_Ax3_1()\n : makeAx3(fromSystem.origin, fromSystem.zDir, fromSystem.xDir)\n );\n\n const toAx = r(\n toSystem === \"reference\"\n ? new this.oc.gp_Ax3_1()\n : makeAx3(toSystem.origin, toSystem.zDir, toSystem.xDir)\n );\n this.wrapped.SetTransformation_1(fromAx, toAx);\n return this;\n }\n\n transformPoint(point: Point): gp_Pnt {\n const pnt = asPnt(point);\n const newPoint = pnt.Transformed(this.wrapped);\n pnt.delete();\n return newPoint;\n }\n\n transform(shape: TopoDS_Shape): TopoDS_Shape {\n const transformer = new this.oc.BRepBuilderAPI_Transform_2(\n shape,\n this.wrapped,\n true\n );\n return transformer.ModifiedShape(shape);\n }\n}\n\nexport class Plane {\n oc: OpenCascadeInstance;\n\n xDir: Vector;\n yDir: Vector;\n zDir: Vector;\n\n // @ts-expect-error initialised indirectly\n private _origin: Vector;\n // @ts-expect-error initialised indirectly\n private localToGlobal: Transformation;\n // @ts-expect-error initialised indirectly\n private globalToLocal: Transformation;\n\n constructor(\n origin: Point,\n xDirection: Point | null = null,\n normal: Point = [0, 0, 1]\n ) {\n this.oc = getOC();\n\n const zDir = new Vector(normal);\n if (zDir.Length === 0) {\n throw new Error(\"normal should be non null\");\n }\n this.zDir = zDir.normalize();\n\n let xDir: Vector;\n if (!xDirection) {\n const ax3 = makeAx3(origin, zDir);\n xDir = new Vector(ax3.XDirection());\n ax3.delete();\n } else {\n xDir = new Vector(xDirection);\n }\n\n if (xDir.Length === 0) {\n throw new Error(\"xDir should be non null\");\n }\n\n this.xDir = xDir.normalize();\n this.yDir = this.zDir.cross(this.xDir).normalize();\n\n this.origin = new Vector(origin);\n }\n\n delete(): void {\n this.localToGlobal.delete();\n this.xDir.delete();\n this.yDir.delete();\n this.zDir.delete();\n this._origin.delete();\n }\n\n clone(): Plane {\n return new Plane(this.origin, this.xDir, this.zDir);\n }\n\n get origin(): Vector {\n return this._origin;\n }\n\n set origin(newOrigin: Vector) {\n this._origin = newOrigin;\n this._calcTransforms();\n }\n\n translateTo(point: Point): Plane {\n const newPlane = this.clone();\n newPlane.origin = new Vector(point);\n return newPlane;\n }\n\n translate(xDist: number, yDist: number, zDist: number): Plane;\n translate(vector: Point): Plane;\n translate(xDistOrVector: number | Point, yDist = 0, zDist = 0): Plane {\n const translation = new Vector(\n typeof xDistOrVector === \"number\"\n ? [xDistOrVector, yDist, zDist]\n : xDistOrVector\n );\n\n return this.translateTo(this.origin.add(translation));\n }\n\n translateX(xDist: number): Plane {\n return this.translate(xDist, 0, 0);\n }\n\n translateY(yDist: number): Plane {\n return this.translate(0, yDist, 0);\n }\n\n translateZ(zDist: number): Plane {\n return this.translate(0, 0, zDist);\n }\n\n pivot(angle: number, direction: Direction = [1, 0, 0]): Plane {\n const dir = makeDirection(direction);\n const zDir = new Vector(this.zDir).rotate(angle, [0, 0, 0], dir);\n const xDir = new Vector(this.xDir).rotate(angle, [0, 0, 0], dir);\n\n return new Plane(this.origin, xDir, zDir);\n }\n\n rotate2DAxes(angle: number): Plane {\n const xDir = new Vector(this.xDir).rotate(angle, [0, 0, 0], this.zDir);\n\n return new Plane(this.origin, xDir, this.zDir);\n }\n\n _calcTransforms(): void {\n const globalCoordSystem = new this.oc.gp_Ax3_1();\n const localCoordSystem = makeAx3(this.origin, this.zDir, this.xDir);\n\n const forwardT = new this.oc.gp_Trsf_1();\n forwardT.SetTransformation_1(globalCoordSystem, localCoordSystem);\n this.globalToLocal = new Transformation();\n this.globalToLocal.coordSystemChange(\"reference\", {\n origin: this.origin,\n zDir: this.zDir,\n xDir: this.xDir,\n });\n\n this.localToGlobal = new Transformation();\n this.localToGlobal.coordSystemChange(\n {\n origin: this.origin,\n zDir: this.zDir,\n xDir: this.xDir,\n },\n \"reference\"\n );\n }\n\n setOrigin2d(x: number, y: number): void {\n this.origin = this.toWorldCoords([x, y]);\n }\n\n toLocalCoords(vec: Vector): Vector {\n const pnt = this.globalToLocal.transformPoint(vec);\n const newVec = new Vector(pnt);\n pnt.delete();\n return newVec;\n }\n\n toWorldCoords(v: Point): Vector {\n const pnt = this.localToGlobal.transformPoint(v);\n const newVec = new Vector(pnt);\n pnt.delete();\n return newVec;\n }\n}\n\nexport type PlaneName =\n | \"XY\"\n | \"YZ\"\n | \"ZX\"\n | \"XZ\"\n | \"YX\"\n | \"ZY\"\n | \"front\"\n | \"back\"\n | \"left\"\n | \"right\"\n | \"top\"\n | \"bottom\";\n\nconst PLANES_CONFIG: Record<\n PlaneName,\n {\n xDir: [number, number, number];\n normal: [number, number, number];\n }\n> = {\n XY: {\n xDir: [1, 0, 0],\n normal: [0, 0, 1],\n },\n YZ: {\n xDir: [0, 1, 0],\n normal: [1, 0, 0],\n },\n ZX: {\n xDir: [0, 0, 1],\n normal: [0, 1, 0],\n },\n XZ: {\n xDir: [1, 0, 0],\n normal: [0, -1, 0],\n },\n YX: {\n xDir: [0, 1, 0],\n normal: [0, 0, -1],\n },\n ZY: {\n xDir: [0, 0, 1],\n normal: [-1, 0, 0],\n },\n front: {\n xDir: [1, 0, 0],\n normal: [0, 0, 1],\n },\n back: {\n xDir: [-1, 0, 0],\n normal: [0, 0, -1],\n },\n left: {\n xDir: [0, 0, 1],\n normal: [-1, 0, 0],\n },\n right: {\n xDir: [0, 0, -1],\n normal: [1, 0, 0],\n },\n top: {\n xDir: [1, 0, 0],\n normal: [0, 1, 0],\n },\n bottom: {\n xDir: [1, 0, 0],\n normal: [0, -1, 0],\n },\n};\n\nexport const createNamedPlane = (\n plane: PlaneName,\n sourceOrigin: Point | number = [0, 0, 0]\n): Plane => {\n const config = PLANES_CONFIG[plane];\n if (!config) throw new Error(`Could not find plane ${plane}`);\n\n let origin: Point;\n if (typeof sourceOrigin === \"number\") {\n origin = config.normal.map((v: number) => v * sourceOrigin) as Point;\n } else {\n origin = sourceOrigin;\n }\n return new Plane(origin, config.xDir, config.normal);\n};\n\nexport class BoundingBox extends WrappingObj<Bnd_Box> {\n constructor(wrapped?: Bnd_Box) {\n const oc = getOC();\n let boundBox = wrapped;\n if (!boundBox) {\n boundBox = new oc.Bnd_Box_1();\n }\n super(boundBox);\n }\n\n get repr(): string {\n const [min, max] = this.bounds;\n return `${new Vector(min).repr} - ${new Vector(max).repr}`;\n }\n\n get bounds(): [SimplePoint, SimplePoint] {\n const xMin = { current: 0 };\n const yMin = { current: 0 };\n const zMin = { current: 0 };\n const xMax = { current: 0 };\n const yMax = { current: 0 };\n const zMax = { current: 0 };\n\n // @ts-ignore missing type in oc\n this.wrapped.Get(xMin, yMin, zMin, xMax, yMax, zMax);\n return [\n [xMin.current, yMin.current, zMin.current],\n [xMax.current, yMax.current, zMax.current],\n ];\n }\n\n get center(): SimplePoint {\n const [[xmin, ymin, zmin], [xmax, ymax, zmax]] = this.bounds;\n return [\n xmin + (xmax - xmin) / 2,\n ymin + (ymax - ymin) / 2,\n zmin + (zmax - zmin) / 2,\n ];\n }\n\n get width(): number {\n const [[xmin], [xmax]] = this.bounds;\n return Math.abs(xmax - xmin);\n }\n\n get height(): number {\n const [[, ymin], [, ymax]] = this.bounds;\n return Math.abs(ymax - ymin);\n }\n\n get depth(): number {\n const [[, , zmin], [, , zmax]] = this.bounds;\n return Math.abs(zmax - zmin);\n }\n\n add(other: BoundingBox) {\n this.wrapped.Add_1(other.wrapped);\n }\n\n isOut(other: BoundingBox): boolean {\n return this.wrapped.IsOut_4(other.wrapped);\n }\n}\n","import {\n createNamedPlane,\n Plane,\n PlaneName,\n Point,\n Transformation,\n Vector,\n} from \"./geom\";\nimport { Face } from \"./shapes\";\nimport { Point2D } from \"./lib2d\";\nimport { TopoDS_Shape } from \"replicad-opencascadejs\";\n\nexport const makePlaneFromFace = (\n face: Face,\n originOnSurface: Point2D = [0, 0]\n): Plane => {\n const originPoint = face.pointOnSurface(...originOnSurface);\n const normal = face.normalAt(originPoint);\n const v = new Vector([0, 0, 1]);\n let xd = v.cross(normal);\n if (xd.Length < 1e-8) {\n xd.delete();\n xd = new Vector([1, 0, 0]);\n }\n\n v.delete();\n return new Plane(originPoint, xd, normal);\n};\n\nfunction makePlane(plane: Plane): Plane;\nfunction makePlane(plane?: PlaneName, origin?: Point | number): Plane;\nfunction makePlane(\n plane: Plane | PlaneName = \"XY\",\n origin: Point | number = [0, 0, 0]\n): Plane {\n if (plane instanceof Plane) {\n return plane.clone();\n } else {\n return createNamedPlane(plane, origin);\n }\n}\n\nexport { makePlane };\n\nexport function rotate(\n shape: TopoDS_Shape,\n angle: number,\n position: Point = [0, 0, 0],\n direction: Point = [0, 0, 1]\n): TopoDS_Shape {\n const transformation = new Transformation();\n transformation.rotate(angle, position, direction);\n const newShape = transformation.transform(shape);\n transformation.delete();\n return newShape;\n}\n\nexport function translate(shape: TopoDS_Shape, vector: Point): TopoDS_Shape {\n const transformation = new Transformation();\n transformation.translate(vector);\n const newShape = transformation.transform(shape);\n transformation.delete();\n return newShape;\n}\n\nexport function mirror(\n shape: TopoDS_Shape,\n inputPlane?: Plane | PlaneName | Point,\n origin?: Point\n): TopoDS_Shape {\n const transformation = new Transformation();\n transformation.mirror(inputPlane, origin);\n const newShape = transformation.transform(shape);\n transformation.delete();\n return newShape;\n}\n\nexport function scale(\n shape: TopoDS_Shape,\n center: Point,\n scale: number\n): TopoDS_Shape {\n const transformation = new Transformation();\n transformation.scale(center, scale);\n const newShape = transformation.transform(shape);\n transformation.delete();\n return newShape;\n}\n","export type Point2D = [number, number];\n\nexport function isPoint2D(point: unknown): point is Point2D {\n return Array.isArray(point) && point.length === 2;\n}\n\nexport type Matrix2X2 = [[number, number], [number, number]];\n\nexport function isMatrix2X2(matrix: unknown): matrix is Matrix2X2 {\n return (\n Array.isArray(matrix) &&\n matrix.length === 2 &&\n matrix.every((row) => Array.isArray(row) && row.length === 2)\n );\n}\n","import { getOC } from \"./oclib.js\";\n\nexport type CurveType =\n | \"LINE\"\n | \"CIRCLE\"\n | \"ELLIPSE\"\n | \"HYPERBOLA\"\n | \"PARABOLA\"\n | \"BEZIER_CURVE\"\n | \"BSPLINE_CURVE\"\n | \"OFFSET_CURVE\"\n | \"OTHER_CURVE\";\n\nlet CURVE_TYPES_MAP: Map<any, CurveType> | null = null;\n\nconst getCurveTypesMap = (refresh?: boolean): Map<any, CurveType> => {\n if (CURVE_TYPES_MAP && !refresh) return CURVE_TYPES_MAP;\n\n const oc = getOC();\n const ga = oc.GeomAbs_CurveType;\n\n CURVE_TYPES_MAP = new Map([\n [ga.GeomAbs_Line, \"LINE\"],\n [ga.GeomAbs_Circle, \"CIRCLE\"],\n [ga.GeomAbs_Ellipse, \"ELLIPSE\"],\n [ga.GeomAbs_Hyperbola, \"HYPERBOLA\"],\n [ga.GeomAbs_Parabola, \"PARABOLA\"],\n [ga.GeomAbs_BezierCurve, \"BEZIER_CURVE\"],\n [ga.GeomAbs_BSplineCurve, \"BSPLINE_CURVE\"],\n [ga.GeomAbs_OffsetCurve, \"OFFSET_CURVE\"],\n [ga.GeomAbs_OtherCurve, \"OTHER_CURVE\"],\n ]);\n return CURVE_TYPES_MAP;\n};\n\nexport const findCurveType = (type: any): CurveType => {\n let shapeType = getCurveTypesMap().get(type);\n if (!shapeType) shapeType = getCurveTypesMap(true).get(type);\n if (!shapeType) throw new Error(\"unknown type\");\n return shapeType;\n};\n","// from https://stackoverflow.com/a/49729715\nexport default function precisionRound(number: number, precision: number) {\n const factor = Math.pow(10, precision);\n const n = precision < 0 ? number : 0.01 / factor + number;\n return Math.round(n * factor) / factor;\n}\n","export default function range(len: number): number[] {\n return Array.from(Array(len).keys());\n}\n","import range from \"./range\";\n\nexport default function zip<T extends unknown[][]>(\n arrays: T\n): { [K in keyof T]: T[K] extends (infer V)[] ? V : never }[] {\n const minLength = Math.min(...arrays.map((arr) => arr.length));\n // @ts-expect-error This is too much for ts\n return range(minLength).map((i) => arrays.map((arr) => arr[i]));\n}\n","export default function round2(v: number): number {\n return Math.round(v * 100) / 100;\n}\n","import round2 from \"../utils/round2\";\nimport { Point2D } from \"./definitions\";\n\nexport const reprPnt = ([x, y]: Point2D): string => {\n return `(${round2(x)},${round2(y)})`;\n};\n\nconst asFixed = (p: number, precision = 1e-9): string => {\n let num = p;\n if (Math.abs(p) < precision) num = 0;\n return num.toFixed(-Math.log10(precision));\n};\nexport const removeDuplicatePoints = (\n points: Point2D[],\n precision = 1e-9\n): Point2D[] => {\n return Array.from(\n new Map(\n points.map(([p0, p1]) => [\n `[${asFixed(p0, precision)},${asFixed(p1, precision)}]`,\n [p0, p1] as Point2D,\n ])\n ).values()\n );\n};\n","import { gp_Ax2d, gp_Dir2d, gp_Pnt2d, gp_Vec2d } from \"replicad-opencascadejs\";\nimport { getOC } from \"../oclib\";\nimport { localGC } from \"../register\";\nimport { Point2D } from \"./definitions\";\n\nexport const pnt = ([x, y]: Point2D): gp_Pnt2d => {\n const oc = getOC();\n return new oc.gp_Pnt2d_3(x, y);\n};\n\nexport const direction2d = ([x, y]: Point2D): gp_Dir2d => {\n const oc = getOC();\n return new oc.gp_Dir2d_4(x, y);\n};\n\nexport const vec = ([x, y]: Point2D): gp_Vec2d => {\n const oc = getOC();\n return new oc.gp_Vec2d_4(x, y);\n};\n\nexport const axis2d = (point: Point2D, direction: Point2D): gp_Ax2d => {\n const oc = getOC();\n const [r, gc] = localGC();\n const axis = new oc.gp_Ax2d_2(r(pnt(point)), r(direction2d(direction)));\n gc();\n return axis;\n};\n","import { WrappingObj, GCWithScope } from \"../register.js\";\nimport { getOC } from \"../oclib.js\";\nimport { Bnd_Box2d } from \"replicad-opencascadejs\";\n\nimport { Point2D } from \"./definitions.js\";\nimport { reprPnt } from \"./utils.js\";\nimport { pnt } from \"./ocWrapper.js\";\n\nexport class BoundingBox2d extends WrappingObj<Bnd_Box2d> {\n constructor(wrapped?: Bnd_Box2d) {\n const oc = getOC();\n let boundBox = wrapped;\n if (!boundBox) {\n boundBox = new oc.Bnd_Box2d();\n }\n super(boundBox);\n }\n\n get repr(): string {\n const [min, max] = this.bounds;\n return `${reprPnt(min)} - ${reprPnt(max)}`;\n }\n\n get bounds(): [Point2D, Point2D] {\n const xMin = { current: 0 };\n const yMin = { current: 0 };\n const xMax = { current: 0 };\n const yMax = { current: 0 };\n\n // @ts-expect-error missing type in oc\n this.wrapped.Get(xMin, yMin, xMax, yMax);\n return [\n [xMin.current, yMin.current],\n [xMax.current, yMax.current],\n ];\n }\n\n get center(): Point2D {\n const [[xmin, ymin], [xmax, ymax]] = this.bounds;\n return [xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2];\n }\n\n get width(): number {\n const [[xmin], [xmax]] = this.bounds;\n return Math.abs(xmax - xmin);\n }\n\n get height(): number {\n const [[, ymin], [, ymax]] = this.bounds;\n return Math.abs(ymax - ymin);\n }\n\n outsidePoint(paddingPercent = 1): Point2D {\n const [min, max] = this.bounds;\n const width = max[0] - min[0];\n const height = max[1] - min[1];\n\n return [\n max[0] + (width / 100) * paddingPercent,\n max[1] + (height / 100) * paddingPercent * 0.9,\n ];\n }\n\n add(other: BoundingBox2d) {\n this.wrapped.Add_1(other.wrapped);\n }\n\n isOut(other: BoundingBox2d): boolean {\n return this.wrapped.IsOut_4(other.wrapped);\n }\n\n containsPoint(other: Point2D): boolean {\n const r = GCWithScope();\n const point = r(pnt(other));\n return !this.wrapped.IsOut_1(point);\n }\n}\n","import { Matrix2X2, Point2D } from \"./definitions\";\n\nexport const samePoint = (\n [x0, y0]: Point2D,\n [x1, y1]: Point2D,\n precision = 1e-6\n): boolean => {\n return Math.abs(x0 - x1) <= precision && Math.abs(y0 - y1) <= precision;\n};\n\nexport const add2d = ([x0, y0]: Point2D, [x1, y1]: Point2D): Point2D => {\n return [x0 + x1, y0 + y1];\n};\n\nexport const subtract2d = ([x0, y0]: Point2D, [x1, y1]: Point2D): Point2D => {\n return [x0 - x1, y0 - y1];\n};\n\nexport const scalarMultiply2d = (\n [x0, y0]: Point2D,\n scalar: number\n): Point2D => {\n return [x0 * scalar, y0 * scalar];\n};\n\nexport const distance2d = (\n [x0, y0]: Point2D,\n [x1, y1]: Point2D = [0, 0]\n): number => {\n return Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2);\n};\n\nexport const squareDistance2d = (\n [x0, y0]: Point2D,\n [x1, y1]: Point2D = [0, 0]\n): number => {\n return (x0 - x1) ** 2 + (y0 - y1) ** 2;\n};\n\nexport function crossProduct2d([x0, y0]: Point2D, [x1, y1]: Point2D): number {\n return x0 * y1 - y0 * x1;\n}\n\nexport const angle2d = (\n [x0, y0]: Point2D,\n [x1, y1]: Point2D = [0, 0]\n): number => {\n return Math.atan2(y1 * x0 - y0 * x1, x0 * x1 + y0 * y1);\n};\n\nexport const polarAngle2d = (\n [x0, y0]: Point2D,\n [x1, y1]: Point2D = [0, 0]\n): number => {\n return Math.atan2(y1 - y0, x1 - x0);\n};\n\nexport const normalize2d = ([x0, y0]: Point2D): Point2D => {\n const l = distance2d([x0, y0]);\n return [x0 / l, y0 / l];\n};\n\nexport const rotate2d = (\n point: Point2D,\n angle: number,\n center: Point2D = [0, 0]\n): Point2D => {\n const [px0, py0] = point;\n const [cx, cy] = center;\n\n const px = px0 - cx;\n const py = py0 - cy;\n\n const sinA = Math.sin(angle);\n const cosA = Math.cos(angle);\n\n const xnew = px * cosA - py * sinA;\n const ynew = px * sinA + py * cosA;\n\n return [xnew + cx, ynew + cy];\n};\n\nexport const polarToCartesian = (r: number, theta: number): Point2D => {\n const x = Math.cos(theta) * r;\n const y = Math.sin(theta) * r;\n return [x, y];\n};\n\nexport const cartesianToPolar = ([x, y]: Point2D): [number, number] => {\n const r = distance2d([x, y]);\n const theta = Math.atan2(y, x);\n\n return [r, theta];\n};\n\nexport const determinant2x2 = (matrix: Matrix2X2) => {\n return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];\n};\n","import {\n Geom2dAdaptor_Curve,\n Geom2d_Curve,\n Handle_Geom2d_Curve,\n} from \"replicad-opencascadejs\";\n\nimport { CurveType, findCurveType } from \"../definitionMaps\";\nimport precisionRound from \"../utils/precisionRound\";\nimport { getOC } from \"../oclib.js\";\nimport { GCWithScope, localGC, WrappingObj } from \"../register.js\";\nimport zip from \"../utils/zip.js\";\n\nimport { BoundingBox2d } from \"./BoundingBox2d.js\";\nimport { isPoint2D, Point2D } from \"./definitions.js\";\nimport { pnt } from \"./ocWrapper.js\";\nimport { reprPnt } from \"./utils.js\";\nimport { distance2d, samePoint } from \"./vectorOperations.js\";\n\nexport class Curve2D extends WrappingObj<Handle_Geom2d_Curve> {\n _boundingBox: null | BoundingBox2d;\n constructor(handle: Handle_Geom2d_Curve) {\n const oc = getOC();\n const inner = handle.get();\n\n super(new oc.Handle_Geom2d_Curve_2(inner));\n\n this._boundingBox = null;\n }\n\n get boundingBox() {\n if (this._boundingBox) return this._boundingBox;\n const oc = getOC();\n const boundBox = new oc.Bnd_Box2d();\n\n oc.BndLib_Add2dCurve.Add_3(this.wrapped, 1e-6, boundBox);\n\n this._boundingBox = new BoundingBox2d(boundBox);\n return this._boundingBox;\n }\n\n get repr() {\n return `${this.geomType} ${reprPnt(this.firstPoint)} - ${reprPnt(\n this.lastPoint\n )}`;\n }\n\n get innerCurve(): Geom2d_Curve {\n return this.wrapped.get();\n }\n\n value(parameter: number): Point2D {\n const pnt = this.innerCurve.Value(parameter);\n const vec: Point2D = [pnt.X(), pnt.Y()];\n pnt.delete();\n return vec;\n }\n\n get firstPoint(): Point2D {\n return this.value(this.firstParameter);\n }\n\n get lastPoint(): Point2D {\n return this.value(this.lastParameter);\n }\n\n get firstParameter(): number {\n return this.innerCurve.FirstParameter();\n }\n\n get lastParameter(): number {\n return this.innerCurve.LastParameter();\n }\n\n adaptor(): Geom2dAdaptor_Curve {\n const oc = getOC();\n return new oc.Geom2dAdaptor_Curve_2(this.wrapped);\n }\n\n get geomType(): CurveType {\n const adaptor = this.adaptor();\n const curveType = findCurveType(adaptor.GetType());\n adaptor.delete();\n return curveType;\n }\n\n clone(): Curve2D {\n return new Curve2D(this.innerCurve.Copy() as Handle_Geom2d_Curve);\n }\n\n reverse(): void {\n this.innerCurve.Reverse();\n }\n\n private distanceFromPoint(point: Point2D): number {\n const oc = getOC();\n const r = GCWithScope();\n\n const projector = r(\n new oc.Geom2dAPI_ProjectPointOnCurve_2(r(pnt(point)), this.wrapped)\n );\n\n let curveToPoint = Infinity;\n\n try {\n curveToPoint = projector.LowerDistance();\n } catch (e) {\n curveToPoint = Infinity;\n }\n\n return Math.min(\n curveToPoint,\n distance2d(point, this.firstPoint),\n distance2d(point, this.lastPoint)\n );\n }\n\n private distanceFromCurve(curve: Curve2D): number {\n const oc = getOC();\n const r = GCWithScope();\n\n let curveDistance = Infinity;\n const projector = r(\n new oc.Geom2dAPI_ExtremaCurveCurve(\n this.wrapped,\n curve.wrapped,\n this.firstParameter,\n this.lastParameter,\n curve.firstParameter,\n curve.lastParameter\n )\n );\n\n try {\n curveDistance = projector.LowerDistance();\n } catch (e) {\n curveDistance = Infinity;\n }\n\n // We need to take the shorter distance between the curves and the extremities\n return Math.min(\n curveDistance,\n this.distanceFromPoint(curve.firstPoint),\n this.distanceFromPoint(curve.lastPoint),\n curve.distanceFromPoint(this.firstPoint),\n curve.distanceFromPoint(this.lastPoint)\n );\n }\n\n distanceFrom(element: Curve2D | Point2D): number {\n if (isPoint2D(element)) {\n return this.distanceFromPoint(element);\n }\n\n return this.distanceFromCurve(element);\n }\n\n isOnCurve(point: Point2D): boolean {\n return this.distanceFromPoint(point) < 1e-9;\n }\n\n parameter(point: Point2D, precision = 1e-9): number {\n const oc = getOC();\n const r = GCWithScope();\n\n let lowerDistance;\n let lowerDistanceParameter;\n try {\n const projector = r(\n new oc.Geom2dAPI_ProjectPointOnCurve_2(r(pnt(point)), this.wrapped)\n );\n lowerDistance = projector.LowerDistance();\n lowerDistanceParameter = projector.LowerDistanceParameter();\n } catch (e) {\n // Perhaps it failed because it is on an extremity\n if (samePoint(point, this.firstPoint, precision))\n return this.firstParameter;\n if (samePoint(point, this.lastPoint, precision))\n return this.lastParameter;\n\n throw new Error(\"Failed to find parameter\");\n }\n\n if (lowerDistance > precision) {\n throw new Error(\n `Point ${reprPnt(point)} not on curve ${\n this.repr\n }, ${lowerDistance.toFixed(9)}`\n );\n }\n return lowerDistanceParameter;\n }\n\n tangentAt(index: number | Point2D): Point2D {\n const oc = getOC();\n const [r, gc] = localGC();\n\n let param;\n\n if (Array.isArray(index)) {\n param = this.parameter(index);\n } else {\n const paramLength =\n this.innerCurve.LastParameter() - this.innerCurve.FirstParameter();\n param = paramLength * index + this.innerCurve.FirstParameter();\n }\n\n const point = r(new oc.gp_Pnt2d_1());\n const dir = r(new oc.gp_Vec2d_1());\n\n this.innerCurve.D1(param, point, dir);\n\n const tgtVec = [dir.X(), dir.Y()] as Point2D;\n gc();\n\n return tgtVec;\n }\n\n splitAt(points: Point2D[] | number[], precision = 1e-9): Curve2D[] {\n const oc = getOC();\n const r = GCWithScope();\n\n let parameters = points.map((point: Point2D | number) => {\n if (isPoint2D(point)) return this.parameter(point, precision);\n return point;\n });\n\n // We only split on each point once\n parameters = Array.from(\n new Map(\n parameters.map((p) => [precisionRound(p, -Math.log10(precision)), p])\n ).values()\n ).sort((a, b) => a - b);\n const firstParam = this.firstParameter;\n const lastParam = this.lastParameter;\n\n if (firstParam > lastParam) {\n parameters.reverse();\n }\n\n // We do not split again on the start and end\n if (Math.abs(parameters[0] - firstParam) < precision * 100)\n parameters = parameters.slice(1);\n if (!parameters.length) return [this];\n\n if (\n Math.abs(parameters[parameters.length - 1] - lastParam) <\n precision * 100\n )\n parameters = parameters.slice(0, -1);\n if (!parameters.length) return [this];\n\n return zip([\n [firstParam, ...parameters],\n [...parameters, lastParam],\n ]).map(([first, last]) => {\n try {\n if (this.geomType === \"BEZIER_CURVE\") {\n const curveCopy = new oc.Geom2d_BezierCurve_1(\n r(this.adaptor()).Bezier().get().Poles_2()\n );\n curveCopy.Segment(first, last);\n return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));\n }\n if (this.geomType === \"BSPLINE_CURVE\") {\n const adapted = r(this.adaptor()).BSpline().get();\n\n const curveCopy = new oc.Geom2d_BSplineCurve_1(\n adapted.Poles_2(),\n adapted.Knots_2(),\n adapted.Multiplicities_2(),\n adapted.Degree(),\n adapted.IsPeriodic()\n );\n curveCopy.Segment(first, last, precision);\n return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));\n }\n\n const trimmed = new oc.Geom2d_TrimmedCurve(\n this.wrapped,\n first,\n last,\n true,\n true\n );\n return new Curve2D(new oc.Handle_Geom2d_Curve_2(trimmed));\n } catch (e) {\n throw new Error(\"Failed to split the curve\");\n }\n });\n }\n}\n","import { Geom2dAdaptor_Curve, GeomAbs_Shape } from \"replicad-opencascadejs\";\nimport { findCurveType } from \"../definitionMaps\";\nimport { getOC } from \"../oclib\";\nimport { GCWithScope } from \"../register\";\nimport { Curve2D } from \"./Curve2D\";\nimport { samePoint } from \"./vectorOperations\";\n\nexport const approximateAsBSpline = (\n adaptor: Geom2dAdaptor_Curve,\n tolerance = 1e-4,\n continuity: \"C0\" | \"C1\" | \"C2\" | \"C3\" = \"C0\",\n maxSegments = 200\n): Curve2D => {\n const oc = getOC();\n const r = GCWithScope();\n\n const continuities: Record<string, GeomAbs_Shape> = {\n C0: oc.GeomAbs_Shape.GeomAbs_C0 as GeomAbs_Shape,\n C1: oc.GeomAbs_Shape.GeomAbs_C1 as GeomAbs_Shape,\n C2: oc.GeomAbs_Shape.GeomAbs_C2 as GeomAbs_Shape,\n C3: oc.GeomAbs_Shape.GeomAbs_C3 as GeomAbs_Shape,\n };\n\n const convert = r(\n new oc.Geom2dConvert_ApproxCurve_2(\n adaptor.ShallowCopy(),\n tolerance,\n continuities[continuity],\n maxSegments,\n 3\n )\n );\n\n return new Curve2D(convert.Curve());\n};\n\nexport const BSplineToBezier = (adaptor: Geom2dAdaptor_Curve): Curve2D[] => {\n if (findCurveType(adaptor.GetType()) !== \"BSPLINE_CURVE\")\n throw new Error(\"You can only convert a Bspline\");\n\n const handle = adaptor.BSpline();\n\n const oc = getOC();\n const convert = new oc.Geom2dConvert_BSplineCurveToBezierCurve_1(handle);\n\n function* bezierCurves(): Generator<Curve2D> {\n const nArcs = convert.NbArcs();\n if (!nArcs) return;\n\n for (let i = 1; i <= nArcs; i++) {\n const arc = convert.Arc(i);\n yield new Curve2D(arc);\n }\n }\n\n const curves = Array.from(bezierCurves());\n convert.delete();\n return curves;\n};\n\nexport interface ApproximationOptions {\n tolerance?: number;\n continuity?: \"C0\" | \"C1\" | \"C2\" | \"C3\";\n maxSegments?: number;\n}\n\nexport function approximateAsSvgCompatibleCurve(\n curves: Curve2D[],\n options: ApproximationOptions = {\n tolerance: 1e-4,\n continuity: \"C0\",\n maxSegments: 300,\n }\n): Curve2D[] {\n const r = GCWithScope();\n\n return curves.flatMap((curve) => {\n const adaptor = r(curve.adaptor());\n const curveType = findCurveType(adaptor.GetType());\n\n if (\n curveType === \"ELLIPSE\" ||\n (curveType === \"CIRCLE\" && samePoint(curve.firstPoint, curve.lastPoint))\n ) {\n return curve.splitAt([0.5]);\n }\n\n if ([\"LINE\", \"ELLIPSE\", \"CIRCLE\"].includes(curveType)) {\n return curve;\n }\n\n if (curveType === \"BEZIER_CURVE\") {\n const b = adaptor.Bezier().get();\n const deg = b.Degree();\n\n if ([1, 2, 3].includes(deg)) {\n return curve;\n }\n }\n\n if (curveType === \"BSPLINE_CURVE\") {\n const c = BSplineToBezier(adaptor);\n return approximateAsSvgCompatibleCurve(c, options);\n }\n\n const bspline = approximateAsBSpline(\n adaptor,\n options.tolerance,\n options.continuity,\n options.maxSegments\n );\n return approximateAsSvgCompatibleCurve(\n BSplineToBezier(r(bspline.adaptor())),\n options\n );\n });\n}\n","import { Geom2dAPI_InterCurveCurve } from \"replicad-opencascadejs\";\nimport { getOC } from \"../oclib\";\nimport { Curve2D } from \"./Curve2D\";\nimport { Point2D } from \"./definitions\";\nimport { samePoint } from \"./vectorOperations\";\n\nfunction* pointsIteration(\n intersector: Geom2dAPI_InterCurveCurve\n): Generator<Point2D> {\n const nPoints = intersector.NbPoints();\n if (!nPoints) return;\n\n for (let i = 1; i <= nPoints; i++) {\n const point = intersector.Point(i);\n yield [point.X(), point.Y()];\n }\n}\n\nfunction* commonSegmentsIteration(\n intersector: Geom2dAPI_InterCurveCurve\n): Generator<Curve2D> {\n const nSegments = intersector.NbSegments();\n if (!nSegments) return;\n\n const oc = getOC();\n\n for (let i = 1; i <= nSegments; i++) {\n const h1 = new oc.Handle_Geom2d_Curve_1();\n const h2 = new oc.Handle_Geom2d_Curve_1();\n try {\n // There seem to be a bug in occt where it returns segments but fails to\n // fetch them.\n intersector.Segment(i, h1, h2);\n } catch (e) {\n continue;\n }\n\n yield new Curve2D(h1);\n h2.delete();\n }\n}\n\nexport const intersectCurves = (\n first: Curve2D,\n second: Curve2D,\n precision = 1e-9\n) => {\n if (first.boundingBox.isOut(second.boundingBox))\n return { intersections: [], commonSegments: [], commonSegmentsPoints: [] };\n\n const oc = getOC();\n const intersector = new oc.Geom2dAPI_InterCurveCurve_1();\n\n let intersections;\n let commonSegments;\n\n try {\n intersector.Init_1(first.wrapped, second.wrapped, precision);\n\n intersections = Array.from(pointsIteration(intersector));\n commonSegments = Array.from(commonSegmentsIteration(intersector));\n } catch (e) {\n console.error(first, second, e);\n throw new Error(\"Intersections failed between curves\");\n } finally {\n intersector.delete();\n }\n\n const segmentsAsPoints = commonSegments\n .filter((c) => samePoint(c.firstPoint, c.lastPoint, precision))\n .map((c) => c.firstPoint);\n\n if (segmentsAsPoints.length) {\n intersections.push(...segmentsAsPoints);\n commonSegments = commonSegments.filter(\n (c) => !samePoint(c.firstPoint, c.lastPoint, precision)\n );\n }\n\n const commonSegmentsPoints = commonSegments.flatMap((c) => [\n c.firstPoint,\n c.lastPoint,\n ]);\n\n return { intersections, commonSegments, commonSegmentsPoints };\n};\n\nexport const selfIntersections = (curve: Curve2D, precision = 1e-9) => {\n const oc = getOC();\n const intersector = new oc.Geom2dAPI_InterCurveCurve_1();\n\n let intersections;\n\n try {\n intersector.Init_1(curve.wrapped, curve.wrapped, precision);\n\n intersections = Array.from(pointsIteration(intersector));\n } catch (e) {\n throw new Error(\"Self intersection failed\");\n } finally {\n intersector.delete();\n }\n\n return intersections;\n};\n","import {\n Geom2dAPI_PointsToBSpline,\n Geom2d_TrimmedCurve,\n Handle_Geom2d_Curve,\n} from \"replicad-opencascadejs\";\nimport { getOC } from \"../oclib.js\";\nimport { GCWithScope, localGC } from \"../register.js\";\n\nimport { Point2D } from \"./definitions.js\";\nimport { axis2d, pnt, vec } from \"./ocWrapper.js\";\n\nimport { Curve2D } from \"./Curve2D\";\nimport {\n add2d,\n distance2d,\n normalize2d,\n samePoint,\n scalarMultiply2d,\n subtract2d,\n} from \"./vectorOperations.js\";\n\n/**\n * Creates a 2D segment curve between two points.\n *\n * @param startPoint - The starting point of the segment.\n * @param endPoint - The ending point of the segment.\n *\n * @returns A Curve2D object representing the segment.\n *\n * @category Planar curves\n */\nexport const make2dSegmentCurve = (\n startPoint: Point2D,\n endPoint: Point2D\n): Curve2D => {\n const oc = getOC();\n const [r, gc] = localGC();\n\n const segment = r(\n new oc.GCE2d_MakeSegment_1(r(pnt(startPoint)), r(pnt(endPoint)))\n ).Value();\n const curve = new Curve2D(segment);\n\n if (!samePoint(curve.firstPoint, startPoint)) {\n curve.reverse();\n }\n\n gc();\n return curve;\n};\n\n/**\n * Creates a 2D arc curve defined by three points.\n *\n * @param startPoint - The starting point of the arc.\n * @param midPoint - The midpoint of the arc.\n * @param endPoint - The ending point of the arc.\n *\n * @returns A Curve2D object representing the arc.\n *\n * @category Planar curves\n */\nexport const make2dThreePointArc = (\n startPoint: Point2D,\n midPoint: Point2D,\n endPoint: Point2D\n): Curve2D => {\n const oc = getOC();\n const [r, gc] = localGC();\n\n const segment = r(\n new oc.GCE2d_MakeArcOfCircle_4(\n r(pnt(startPoint)),\n r(pnt(midPoint)),\n r(pnt(endPoint))\n )\n ).Value();\n gc();\n\n const curve = new Curve2D(segment);\n if (!samePoint(curve.firstPoint, startPoint)) {\n (curve.wrapped.get() as Geom2d_TrimmedCurve).SetTrim(\n curve.lastParameter,\n curve.firstParameter,\n true,\n true\n );\n }\n return curve;\n};\n\n/**\n * Creates a 2D tangent arc curve defined by three points.\n *\n * @param startPoint - The starting point of the arc.\n * @param tangent - The tangent vector at the starting point.\n * @param endPoint - The ending point of the arc.\n *\n * @returns A Curve2D object representing the tangent arc.\n *\n * @category Planar curves\n */\nexport const make2dTangentArc = (\n startPoint: Point2D,\n tangent: Point2D,\n endPoint: Point2D\n): Curve2D => {\n const oc = getOC();\n const [r, gc] = localGC();\n\n const segment = r(\n new oc.GCE2d_MakeArcOfCircle_5(\n r(pnt(startPoint)),\n r(vec(tangent)),\n r(pnt(endPoint))\n )\n ).Value();\n gc();\n\n const curve = new Curve2D(segment);\n if (!samePoint(curve.firstPoint, startPoint)) {\n (curve.wrapped.get() as Geom2d_TrimmedCurve).SetTrim(\n curve.lastParameter,\n curve.firstParameter,\n true,\n true\n );\n }\n return curve;\n};\n\n/**\n * Creates a 2D circle curve.\n *\n * @param radius - The radius of the circle.\n * @param center - The center point of the circle (default is [0, 0]).\n *\n * @returns A Curve2D object representing the circle.\n *\n * @category Planar curves\n */\nexport const make2dCircle = (\n radius: number,\n center: Point2D = [0, 0]\n): Curve2D => {\n const oc = getOC();\n const [r, gc] = localGC();\n\n const segment = r(\n new oc.GCE2d_MakeCircle_7(r(pnt(center)), radius, true)\n ).Value();\n gc();\n\n return new Curve2D(segment as unknown as Handle_Geom2d_Curve);\n};\n\n/**\n * Creates a 2D ellipse curve.\n *\n * @param majorRadius - The major radius of the ellipse.\n * @param minorRadius - The minor radius of the ellipse.\n * @param xDir - The direction vector for the major axis (default is [1, 0]).\n * @param center - The center point of the ellipse (default is [0, 0]).\n * @param direct - Whether the ellipse is direct (default is true).\n *\n * @returns A Curve2D object representing the ellipse.\n *\n * @category Planar curves\n */\nexport const make2dEllipse = (\n majorRadius: number,\n minorRadius: number,\n xDir: Point2D = [1, 0],\n center: Point2D = [0, 0],\n direct = true\n): Curve2D => {\n const oc = getOC();\n const [r, gc] = localGC();\n const ellipse = r(\n new oc.gp_Elips2d_2(\n r(axis2d(center, xDir)),\n majorRadius,\n minorRadius,\n direct\n )\n );\n\n const segment = r(new oc.GCE2d_MakeEllipse_1(ellipse)).Value();\n gc();\n\n return new Curve2D(segment as unknown as Handle_Geom2d_Curve);\n};\n\n/**\n * Creates a 2D ellipse arc curve.\n *\n * @param majorRadius - The major radius of the ellipse.\n * @param minorRadius - The minor radius of the ellipse.\n * @param startAngle - The starting angle of the arc.\n * @param endAngle - The ending angle of the arc.\n * @param center - The center point of the ellipse (default is [0, 0]).\n * @param xDir - The direction vector for the major axis (default is [1, 0]).\n * @param direct - W