5
make2dInerpolatedBSplineCurve,
16
roundedRectangleBlueprint,
21
import { Plane, PlaneName, Point } from "./geom";
22
import type { AnyShape, Edge, Face } from "./shapes";
23
import { BaseSketcher2d } from "./Sketcher2d";
24
import type { SketchInterface, Sketches, Sketch } from "./sketches";
25
import type { GenericSketcher } from "./sketcherlib";
26
import { textBlueprints } from "./text";
27
import { lookFromPlane, ProjectionCamera } from "./projection/ProjectionCamera";
28
import type { ProjectionPlane } from "./projection/ProjectionCamera";
29
import { makeProjectedEdges } from "./projection/makeProjectedEdges";
31
import offset from "./blueprints/offset";
32
import { CornerFinder } from "./finders/cornerFinder";
33
import { fillet2D, chamfer2D } from "./blueprints/customCorners";
34
import { edgeToCurve } from "./curves";
35
import { BSplineApproximationConfig } from "./shapeHelpers";
37
export class Drawing implements DrawingInterface {
38
private innerShape: Shape2D;
40
constructor(innerShape: Shape2D = null) {
41
this.innerShape = innerShape;
45
return new Drawing(this.innerShape?.clone() || null);
48
get boundingBox(): BoundingBox2d {
49
if (!this.innerShape) return new BoundingBox2d();
50
return this.innerShape.boundingBox;
52
stretch(ratio: number, direction: Point2D, origin: Point2D): Drawing {
53
if (!this.innerShape) return new Drawing();
54
return new Drawing(this.innerShape.stretch(ratio, direction, origin));
58
if (this.innerShape === null) return "=== empty shape";
59
return this.innerShape.repr;
62
rotate(angle: number, center?: Point2D): Drawing {
63
if (!this.innerShape) return new Drawing();
64
return new Drawing(this.innerShape.rotate(angle, center));
67
translate(xDist: number, yDist: number): Drawing;
68
translate(translationVector: Point2D): Drawing;
69
translate(xDistOrPoint: number | Point2D, yDist = 0): Drawing {
70
if (!this.innerShape) return new Drawing();
71
return new Drawing(this.innerShape.translate(xDistOrPoint as any, yDist));
74
scale(scaleFactor: number, center?: Point2D): Drawing {
75
if (!this.innerShape) return new Drawing();
76
return new Drawing(this.innerShape.scale(scaleFactor, center));
80
centerOrDirection: Point2D,
82
mode?: "center" | "plane"
84
if (!this.innerShape) return new Drawing();
85
return new Drawing(this.innerShape.mirror(centerOrDirection, origin, mode));
89
* Builds a new drawing by cuting another drawing into this one
91
* @category Drawing Modifications
93
cut(other: Drawing): Drawing {
94
return new Drawing(cut2D(this.innerShape, other.innerShape));
98
* Builds a new drawing by merging another drawing into this one
100
* @category Drawing Modifications
102
fuse(other: Drawing): Drawing {
103
return new Drawing(fuse2D(this.innerShape, other.innerShape));
107
* Builds a new drawing by intersection this drawing with another
109
* @category Drawing Modifications
111
intersect(other: Drawing): Drawing {
112
return new Drawing(intersect2D(this.innerShape, other.innerShape));
116
* Creates a new drawing with some corners filletted, as specified by the
117
* radius and the corner finder function
119
* @category Drawing Modifications
121
fillet(radius: number, filter?: (c: CornerFinder) => CornerFinder): Drawing {
122
const finder = filter && filter(new CornerFinder());
123
return new Drawing(fillet2D(this.innerShape, radius, finder));
127
* Creates a new drawing with some corners filletted, as specified by the
128
* radius and the corner finder function
130
* @category Drawing Modifications
132
chamfer(radius: number, filter?: (c: CornerFinder) => CornerFinder): Drawing {
133
const finder = filter && filter(new CornerFinder());
134
return new Drawing(chamfer2D(this.innerShape, radius, finder));
137
sketchOnPlane(inputPlane: Plane): SketchInterface | Sketches;
139
inputPlane?: PlaneName,
140
origin?: Point | number
141
): SketchInterface | Sketches;
143
inputPlane?: PlaneName | Plane,
144
origin?: Point | number
145
): SketchInterface | Sketches {
146
if (!this.innerShape) throw new Error("Trying to sketch an empty drawing");
147
return this.innerShape.sketchOnPlane(inputPlane, origin);
150
sketchOnFace(face: Face, scaleMode: ScaleMode): SketchInterface | Sketches {
151
if (!this.innerShape) throw new Error("Trying to sketch an empty drawing");
152
return this.innerShape.sketchOnFace(face, scaleMode);
155
toSVG(margin?: number): string {
156
return this.innerShape?.toSVG(margin) || "";
159
toSVGViewBox(margin = 1): string {
160
return this.innerShape?.toSVGViewBox(margin) || "";
163
toSVGPaths(): string[] | string[][] {
164
return this.innerShape?.toSVGPaths() || [];
167
offset(distance: number): Drawing {
168
return new Drawing(offset(this.innerShape, distance));
171
get blueprint(): Blueprint {
172
if (!(this.innerShape instanceof Blueprint)) {
174
this.innerShape instanceof Blueprints &&
175
this.innerShape.blueprints.length === 1 &&
176
this.innerShape.blueprints[0] instanceof Blueprint
178
return this.innerShape.blueprints[0];
180
throw new Error("This drawing is not a blueprint");
182
return this.innerShape;
186
export class DrawingPen
187
extends BaseSketcher2d
188
implements GenericSketcher<Drawing>
190
constructor(origin: Point2D = [0, 0]) {
192
this.pointer = origin;
193
this.firstPoint = origin;
195
this.pendingCurves = [];
199
return new Drawing(new Blueprint(this.pendingCurves));
207
closeWithMirror(): Drawing {
208
this._closeWithMirror();
213
* Stop drawing, make sure the sketch is closed (by adding a straight line to
214
* from the last point to the first), change the corner between the last and the
215
* first segments and returns the sketch.
217
closeWithCustomCorner(
219
mode: "fillet" | "chamfer" = "fillet"
222
this._customCornerLastWithFirst(radius, mode);
229
* Creates a drawing pen to programatically draw in 2D.
233
export function draw(initialPoint?: Point2D): DrawingPen {
234
const pen = new DrawingPen();
236
pen.movePointerTo(initialPoint);
242
* Creates the `Drawing` of a rectangle with (optional) rounded corners.
244
* The rectangle is centered on [0, 0]
248
export function drawRoundedRectangle(
251
r: number | { rx?: number; ry?: number } = 0
253
return new Drawing(roundedRectangleBlueprint(width, height, r));
255
export const drawRectangle = drawRoundedRectangle;
258
* Creates the `Drawing` of a circle as one single curve.
260
* The circle is centered on [0, 0]
264
export function drawSingleCircle(radius: number): Drawing {
265
return new Drawing(new Blueprint([make2dCircle(radius)]));
269
* Creates the `Drawing` of an ellipse as one single curve.
271
* The ellipse is centered on [0, 0], with axes aligned with the coordinates.
275
export function drawSingleEllipse(
279
const [minor, major] = [majorRadius, minorRadius].sort((a, b) => a - b);
280
const direction: Point2D = major === majorRadius ? [1, 0] : [0, 1];
282
return new Drawing(new Blueprint([make2dEllipse(major, minor, direction)]));
286
* Creates the `Drawing` of a circle.
288
* The circle is centered on [0, 0]
292
export function drawCircle(radius: number): Drawing {
294
.movePointerTo([-radius, 0])
295
.sagittaArc(2 * radius, 0, radius)
296
.sagittaArc(-2 * radius, 0, radius)
301
* Creates the `Drawing` of an ellipse.
303
* The ellipse is centered on [0, 0], with axes aligned with the coordinates.
307
export function drawEllipse(majorRadius: number, minorRadius: number): Drawing {
309
.movePointerTo([-majorRadius, 0])
310
.halfEllipse(2 * majorRadius, 0, minorRadius)
311
.halfEllipse(-2 * majorRadius, 0, minorRadius)
316
* Creates the `Drawing` of an polygon in a defined plane
318
* The sides of the polygon can be arcs of circle with a defined sagitta.
319
* The radius defines the out radius of the polygon without sagitta
323
export function drawPolysides(
328
return new Drawing(polysidesBlueprint(radius, sidesCount, sagitta));
332
* Creates the `Drawing` of a text, in a defined font size and a font familiy
333
* (which will be the default).
337
export function drawText(
339
{ startX = 0, startY = 0, fontSize = 16, fontFamily = "default" } = {}
342
textBlueprints(text, { startX, startY, fontSize, fontFamily })
347
* Creates the `Drawing` by interpolating points as a curve
349
* The drawing will be a spline approximating the points. Note that the
350
* degree should be at maximum 3 if you need to export the drawing as an SVG.
354
export const drawPointsInterpolation = (
356
approximationConfig: BSplineApproximationConfig = {}
359
new Blueprint([make2dInerpolatedBSplineCurve(points, approximationConfig)])
364
* Creates the `Drawing` of parametric function
366
* The drawing will be a spline approximating the function. Note that the
367
* degree should be at maximum 3 if you need to export the drawing as an SVG.
371
export const drawParametricFunction = (
372
func: (t: number) => Point2D,
373
{ pointsCount = 400, start = 0, stop = 1 } = {},
374
approximationConfig: BSplineApproximationConfig = {}
376
const stepSize = (stop - start) / pointsCount;
377
const points = [...Array(pointsCount + 1).keys()].map((t) => {
378
return func(start + t * stepSize);
381
return drawPointsInterpolation(points, approximationConfig);
384
export function drawFaceOutline(face: Face): Drawing {
385
const outerWire = face.clone().outerWire();
386
const curves = outerWire.edges.map((e) => edgeToCurve(e, face));
388
const stitchedCurves = stitchCurves(curves).map((s) => new Blueprint(s));
389
if (stitchedCurves.length === 0) return new Drawing();
390
if (stitchedCurves.length === 1) return new Drawing(stitchedCurves[0]);
392
return new Drawing(new Blueprints(stitchedCurves));
395
const edgesToDrawing = (edges: Edge[]): Drawing => {
397
drawRectangle(1000, 1000).sketchOnPlane() as Sketch
400
const curves = edges.map((e) => edgeToCurve(e, planeFace));
401
const stitchedCurves = stitchCurves(curves).map((s) => new Blueprint(s));
402
if (stitchedCurves.length === 0) return new Drawing();
403
if (stitchedCurves.length === 1) return new Drawing(stitchedCurves[0]);
405
return new Drawing(new Blueprints(stitchedCurves));
408
export function drawProjection(
410
projectionCamera: ProjectionPlane | ProjectionCamera = "front"
411
): { visible: Drawing; hidden: Drawing } {
412
let camera: ProjectionCamera;
413
if (projectionCamera instanceof ProjectionCamera) {
414
camera = projectionCamera;
416
camera = lookFromPlane(projectionCamera);
419
const { visible, hidden } = makeProjectedEdges(shape, camera);
422
visible: edgesToDrawing(visible),
423
hidden: edgesToDrawing(hidden),