RepliCAD

Форк
0
425 строк · 11.7 Кб
1
import {
2
  BoundingBox2d,
3
  make2dCircle,
4
  make2dEllipse,
5
  make2dInerpolatedBSplineCurve,
6
  Point2D,
7
  stitchCurves,
8
} from "./lib2d";
9
import {
10
  Blueprint,
11
  cut2D,
12
  intersect2D,
13
  DrawingInterface,
14
  fuse2D,
15
  polysidesBlueprint,
16
  roundedRectangleBlueprint,
17
  ScaleMode,
18
  Shape2D,
19
  Blueprints,
20
} from "./blueprints";
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";
30

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";
36

37
export class Drawing implements DrawingInterface {
38
  private innerShape: Shape2D;
39

40
  constructor(innerShape: Shape2D = null) {
41
    this.innerShape = innerShape;
42
  }
43

44
  clone(): Drawing {
45
    return new Drawing(this.innerShape?.clone() || null);
46
  }
47

48
  get boundingBox(): BoundingBox2d {
49
    if (!this.innerShape) return new BoundingBox2d();
50
    return this.innerShape.boundingBox;
51
  }
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));
55
  }
56

57
  get repr(): string {
58
    if (this.innerShape === null) return "=== empty shape";
59
    return this.innerShape.repr;
60
  }
61

62
  rotate(angle: number, center?: Point2D): Drawing {
63
    if (!this.innerShape) return new Drawing();
64
    return new Drawing(this.innerShape.rotate(angle, center));
65
  }
66

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));
72
  }
73

74
  scale(scaleFactor: number, center?: Point2D): Drawing {
75
    if (!this.innerShape) return new Drawing();
76
    return new Drawing(this.innerShape.scale(scaleFactor, center));
77
  }
78

79
  mirror(
80
    centerOrDirection: Point2D,
81
    origin?: Point2D,
82
    mode?: "center" | "plane"
83
  ): Drawing {
84
    if (!this.innerShape) return new Drawing();
85
    return new Drawing(this.innerShape.mirror(centerOrDirection, origin, mode));
86
  }
87

88
  /**
89
   * Builds a new drawing by cuting another drawing into this one
90
   *
91
   * @category Drawing Modifications
92
   */
93
  cut(other: Drawing): Drawing {
94
    return new Drawing(cut2D(this.innerShape, other.innerShape));
95
  }
96

97
  /**
98
   * Builds a new drawing by merging another drawing into this one
99
   *
100
   * @category Drawing Modifications
101
   */
102
  fuse(other: Drawing): Drawing {
103
    return new Drawing(fuse2D(this.innerShape, other.innerShape));
104
  }
105

106
  /**
107
   * Builds a new drawing by intersection this drawing with another
108
   *
109
   * @category Drawing Modifications
110
   */
111
  intersect(other: Drawing): Drawing {
112
    return new Drawing(intersect2D(this.innerShape, other.innerShape));
113
  }
114

115
  /**
116
   * Creates a new drawing with some corners filletted, as specified by the
117
   * radius and the corner finder function
118
   *
119
   * @category Drawing Modifications
120
   */
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));
124
  }
125

126
  /**
127
   * Creates a new drawing with some corners filletted, as specified by the
128
   * radius and the corner finder function
129
   *
130
   * @category Drawing Modifications
131
   */
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));
135
  }
136

137
  sketchOnPlane(inputPlane: Plane): SketchInterface | Sketches;
138
  sketchOnPlane(
139
    inputPlane?: PlaneName,
140
    origin?: Point | number
141
  ): SketchInterface | Sketches;
142
  sketchOnPlane(
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);
148
  }
149

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);
153
  }
154

155
  toSVG(margin?: number): string {
156
    return this.innerShape?.toSVG(margin) || "";
157
  }
158

159
  toSVGViewBox(margin = 1): string {
160
    return this.innerShape?.toSVGViewBox(margin) || "";
161
  }
162

163
  toSVGPaths(): string[] | string[][] {
164
    return this.innerShape?.toSVGPaths() || [];
165
  }
166

167
  offset(distance: number): Drawing {
168
    return new Drawing(offset(this.innerShape, distance));
169
  }
170

171
  get blueprint(): Blueprint {
172
    if (!(this.innerShape instanceof Blueprint)) {
173
      if (
174
        this.innerShape instanceof Blueprints &&
175
        this.innerShape.blueprints.length === 1 &&
176
        this.innerShape.blueprints[0] instanceof Blueprint
177
      ) {
178
        return this.innerShape.blueprints[0];
179
      }
180
      throw new Error("This drawing is not a blueprint");
181
    }
182
    return this.innerShape;
183
  }
184
}
185

186
export class DrawingPen
187
  extends BaseSketcher2d
188
  implements GenericSketcher<Drawing>
189
{
190
  constructor(origin: Point2D = [0, 0]) {
191
    super();
192
    this.pointer = origin;
193
    this.firstPoint = origin;
194

195
    this.pendingCurves = [];
196
  }
197

198
  done(): Drawing {
199
    return new Drawing(new Blueprint(this.pendingCurves));
200
  }
201

202
  close(): Drawing {
203
    this._closeSketch();
204
    return this.done();
205
  }
206

207
  closeWithMirror(): Drawing {
208
    this._closeWithMirror();
209
    return this.close();
210
  }
211

212
  /**
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.
216
   */
217
  closeWithCustomCorner(
218
    radius: number,
219
    mode: "fillet" | "chamfer" = "fillet"
220
  ): Drawing {
221
    this._closeSketch();
222
    this._customCornerLastWithFirst(radius, mode);
223

224
    return this.done();
225
  }
226
}
227

228
/**
229
 * Creates a drawing pen to programatically draw in 2D.
230
 *
231
 * @category Drawing
232
 */
233
export function draw(initialPoint?: Point2D): DrawingPen {
234
  const pen = new DrawingPen();
235
  if (initialPoint) {
236
    pen.movePointerTo(initialPoint);
237
  }
238
  return pen;
239
}
240

241
/**
242
 * Creates the `Drawing` of a rectangle with (optional) rounded corners.
243
 *
244
 * The rectangle is centered on [0, 0]
245
 *
246
 * @category Drawing
247
 */
248
export function drawRoundedRectangle(
249
  width: number,
250
  height: number,
251
  r: number | { rx?: number; ry?: number } = 0
252
): Drawing {
253
  return new Drawing(roundedRectangleBlueprint(width, height, r));
254
}
255
export const drawRectangle = drawRoundedRectangle;
256

257
/**
258
 * Creates the `Drawing` of a circle as one single curve.
259
 *
260
 * The circle is centered on [0, 0]
261
 *
262
 * @category Drawing
263
 */
264
export function drawSingleCircle(radius: number): Drawing {
265
  return new Drawing(new Blueprint([make2dCircle(radius)]));
266
}
267

268
/**
269
 * Creates the `Drawing` of an ellipse as one single curve.
270
 *
271
 * The ellipse is centered on [0, 0], with axes aligned with the coordinates.
272
 *
273
 * @category Drawing
274
 */
275
export function drawSingleEllipse(
276
  majorRadius: number,
277
  minorRadius: number
278
): Drawing {
279
  const [minor, major] = [majorRadius, minorRadius].sort((a, b) => a - b);
280
  const direction: Point2D = major === majorRadius ? [1, 0] : [0, 1];
281

282
  return new Drawing(new Blueprint([make2dEllipse(major, minor, direction)]));
283
}
284

285
/**
286
 * Creates the `Drawing` of a circle.
287
 *
288
 * The circle is centered on [0, 0]
289
 *
290
 * @category Drawing
291
 */
292
export function drawCircle(radius: number): Drawing {
293
  return draw()
294
    .movePointerTo([-radius, 0])
295
    .sagittaArc(2 * radius, 0, radius)
296
    .sagittaArc(-2 * radius, 0, radius)
297
    .close();
298
}
299

300
/**
301
 * Creates the `Drawing` of an ellipse.
302
 *
303
 * The ellipse is centered on [0, 0], with axes aligned with the coordinates.
304
 *
305
 * @category Drawing
306
 */
307
export function drawEllipse(majorRadius: number, minorRadius: number): Drawing {
308
  return draw()
309
    .movePointerTo([-majorRadius, 0])
310
    .halfEllipse(2 * majorRadius, 0, minorRadius)
311
    .halfEllipse(-2 * majorRadius, 0, minorRadius)
312
    .close();
313
}
314

315
/**
316
 * Creates the `Drawing` of an polygon in a defined plane
317
 *
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
320
 *
321
 * @category Drawing
322
 */
323
export function drawPolysides(
324
  radius: number,
325
  sidesCount: number,
326
  sagitta = 0
327
): Drawing {
328
  return new Drawing(polysidesBlueprint(radius, sidesCount, sagitta));
329
}
330

331
/**
332
 * Creates the `Drawing` of a text, in a defined font size and a font familiy
333
 * (which will be the default).
334
 *
335
 * @category Drawing
336
 */
337
export function drawText(
338
  text: string,
339
  { startX = 0, startY = 0, fontSize = 16, fontFamily = "default" } = {}
340
): Drawing {
341
  return new Drawing(
342
    textBlueprints(text, { startX, startY, fontSize, fontFamily })
343
  );
344
}
345

346
/**
347
 * Creates the `Drawing` by interpolating points as a curve
348
 *
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.
351
 *
352
 * @category Drawing
353
 */
354
export const drawPointsInterpolation = (
355
  points: Point2D[],
356
  approximationConfig: BSplineApproximationConfig = {}
357
): Drawing => {
358
  return new Drawing(
359
    new Blueprint([make2dInerpolatedBSplineCurve(points, approximationConfig)])
360
  );
361
};
362

363
/**
364
 * Creates the `Drawing` of parametric function
365
 *
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.
368
 *
369
 * @category Drawing
370
 */
371
export const drawParametricFunction = (
372
  func: (t: number) => Point2D,
373
  { pointsCount = 400, start = 0, stop = 1 } = {},
374
  approximationConfig: BSplineApproximationConfig = {}
375
): Drawing => {
376
  const stepSize = (stop - start) / pointsCount;
377
  const points = [...Array(pointsCount + 1).keys()].map((t) => {
378
    return func(start + t * stepSize);
379
  });
380

381
  return drawPointsInterpolation(points, approximationConfig);
382
};
383

384
export function drawFaceOutline(face: Face): Drawing {
385
  const outerWire = face.clone().outerWire();
386
  const curves = outerWire.edges.map((e) => edgeToCurve(e, face));
387

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]);
391

392
  return new Drawing(new Blueprints(stitchedCurves));
393
}
394

395
const edgesToDrawing = (edges: Edge[]): Drawing => {
396
  const planeFace = (
397
    drawRectangle(1000, 1000).sketchOnPlane() as Sketch
398
  ).face();
399

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]);
404

405
  return new Drawing(new Blueprints(stitchedCurves));
406
};
407

408
export function drawProjection(
409
  shape: AnyShape,
410
  projectionCamera: ProjectionPlane | ProjectionCamera = "front"
411
): { visible: Drawing; hidden: Drawing } {
412
  let camera: ProjectionCamera;
413
  if (projectionCamera instanceof ProjectionCamera) {
414
    camera = projectionCamera;
415
  } else {
416
    camera = lookFromPlane(projectionCamera);
417
  }
418

419
  const { visible, hidden } = makeProjectedEdges(shape, camera);
420

421
  return {
422
    visible: edgesToDrawing(visible),
423
    hidden: edgesToDrawing(hidden),
424
  };
425
}
426

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.