RepliCAD

Форк
0
492 строки · 13.1 Кб
1
import { Plane, PlaneName, Point, Vector } from "./geom";
2
import { makePlane } from "./geomHelpers";
3
import { localGC } from "./register";
4
import { DEG2RAD, RAD2DEG } from "./constants";
5
import { distance2d, polarAngle2d, polarToCartesian, Point2D } from "./lib2d";
6
import {
7
  makeLine,
8
  makeThreePointArc,
9
  makeBezierCurve,
10
  makeTangentArc,
11
  makeEllipseArc,
12
  assembleWire,
13
} from "./shapeHelpers.js";
14

15
import {
16
  convertSvgEllipseParams,
17
  SplineConfig,
18
  defaultsSplineConfig,
19
  GenericSketcher,
20
} from "./sketcherlib.js";
21
import { CurveLike, Edge, Wire } from "./shapes.js";
22
import { Handle_Geom_BezierCurve } from "replicad-opencascadejs";
23
import Sketch from "./sketches/Sketch.js";
24

25
/**
26
 * The FaceSketcher allows you to sketch on a plane.
27
 *
28
 * @category Sketching
29
 */
30
export default class Sketcher implements GenericSketcher<Sketch> {
31
  protected plane: Plane;
32
  protected pointer: Vector;
33
  protected firstPoint: Vector;
34
  protected pendingEdges: Edge[];
35
  protected _mirrorWire: boolean;
36

37
  /**
38
   * The sketcher can be defined by a plane, or a simple plane definition,
39
   * with either a point of origin, or the position on the normal axis from
40
   * the coordinates origin
41
   */
42
  constructor(plane: Plane);
43
  constructor(plane?: PlaneName, origin?: Point | number);
44
  constructor(plane?: Plane | PlaneName, origin?: Point) {
45
    this.plane =
46
      plane instanceof Plane ? makePlane(plane) : makePlane(plane, origin);
47

48
    this.pointer = new Vector(this.plane.origin);
49
    this.firstPoint = new Vector(this.plane.origin);
50

51
    this.pendingEdges = [];
52
    this._mirrorWire = false;
53
  }
54

55
  delete(): void {
56
    this.plane.delete();
57
  }
58

59
  protected _updatePointer(newPointer: Vector): void {
60
    this.pointer = newPointer;
61
  }
62

63
  movePointerTo([x, y]: Point2D): this {
64
    if (this.pendingEdges.length)
65
      throw new Error(
66
        "You can only move the pointer if there is no edge defined"
67
      );
68
    this._updatePointer(this.plane.toWorldCoords([x, y]));
69
    this.firstPoint = new Vector(this.pointer);
70
    return this;
71
  }
72

73
  lineTo([x, y]: Point2D): this {
74
    const endPoint = this.plane.toWorldCoords([x, y]);
75
    this.pendingEdges.push(makeLine(this.pointer, endPoint));
76
    this._updatePointer(endPoint);
77
    return this;
78
  }
79

80
  line(xDist: number, yDist: number): this {
81
    const pointer = this.plane.toLocalCoords(this.pointer);
82
    return this.lineTo([xDist + pointer.x, yDist + pointer.y]);
83
  }
84

85
  vLine(distance: number): this {
86
    return this.line(0, distance);
87
  }
88

89
  hLine(distance: number): this {
90
    return this.line(distance, 0);
91
  }
92

93
  vLineTo(yPos: number): this {
94
    const pointer = this.plane.toLocalCoords(this.pointer);
95
    return this.lineTo([pointer.x, yPos]);
96
  }
97

98
  hLineTo(xPos: number): this {
99
    const pointer = this.plane.toLocalCoords(this.pointer);
100
    return this.lineTo([xPos, pointer.y]);
101
  }
102

103
  polarLine(distance: number, angle: number): this {
104
    const angleInRads = angle * DEG2RAD;
105
    const [x, y] = polarToCartesian(distance, angleInRads);
106
    return this.line(x, y);
107
  }
108

109
  polarLineTo([r, theta]: [number, number]): this {
110
    const angleInRads = theta * DEG2RAD;
111
    const point = polarToCartesian(r, angleInRads);
112
    return this.lineTo(point);
113
  }
114

115
  tangentLine(distance: number): this {
116
    const [r, gc] = localGC();
117
    const previousEdge = this.pendingEdges.length
118
      ? this.pendingEdges[this.pendingEdges.length - 1]
119
      : null;
120

121
    if (!previousEdge)
122
      throw new Error("you need a previous edge to create a tangent line");
123

124
    const tangent = r(previousEdge.tangentAt(1));
125
    const endPoint = r(tangent.normalized().multiply(distance)).add(
126
      this.pointer
127
    );
128

129
    this.pendingEdges.push(makeLine(this.pointer, endPoint));
130
    this._updatePointer(endPoint);
131
    gc();
132
    return this;
133
  }
134

135
  threePointsArcTo(end: Point2D, innerPoint: Point2D): this {
136
    const gpoint1 = this.plane.toWorldCoords(innerPoint);
137
    const gpoint2 = this.plane.toWorldCoords(end);
138

139
    this.pendingEdges.push(makeThreePointArc(this.pointer, gpoint1, gpoint2));
140

141
    this._updatePointer(gpoint2);
142
    return this;
143
  }
144

145
  threePointsArc(
146
    xDist: number,
147
    yDist: number,
148
    viaXDist: number,
149
    viaYDist: number
150
  ): this {
151
    const pointer = this.plane.toLocalCoords(this.pointer);
152
    return this.threePointsArcTo(
153
      [pointer.x + xDist, pointer.y + yDist],
154
      [pointer.x + viaXDist, pointer.y + viaYDist]
155
    );
156
  }
157

158
  tangentArcTo(end: Point2D): this {
159
    const endPoint = this.plane.toWorldCoords(end);
160
    const previousEdge = this.pendingEdges[this.pendingEdges.length - 1];
161

162
    this.pendingEdges.push(
163
      makeTangentArc(previousEdge.endPoint, previousEdge.tangentAt(1), endPoint)
164
    );
165

166
    this._updatePointer(endPoint);
167
    return this;
168
  }
169

170
  tangentArc(xDist: number, yDist: number): this {
171
    const pointer = this.plane.toLocalCoords(this.pointer);
172
    return this.tangentArcTo([xDist + pointer.x, yDist + pointer.y]);
173
  }
174

175
  sagittaArcTo(end: Point2D, sagitta: number): this {
176
    const startPoint = this.pointer;
177
    const endPoint = this.plane.toWorldCoords(end);
178

179
    let p = endPoint.add(startPoint);
180
    const midPoint = p.multiply(0.5);
181

182
    p = endPoint.sub(startPoint);
183
    const sagDirection = p.cross(this.plane.zDir).normalized();
184

185
    const sagVector = sagDirection.multiply(sagitta);
186

187
    const sagPoint = midPoint.add(sagVector);
188

189
    this.pendingEdges.push(makeThreePointArc(this.pointer, sagPoint, endPoint));
190
    this._updatePointer(endPoint);
191

192
    return this;
193
  }
194

195
  sagittaArc(xDist: number, yDist: number, sagitta: number): this {
196
    const pointer = this.plane.toLocalCoords(this.pointer);
197
    return this.sagittaArcTo([xDist + pointer.x, yDist + pointer.y], sagitta);
198
  }
199

200
  vSagittaArc(distance: number, sagitta: number): this {
201
    return this.sagittaArc(0, distance, sagitta);
202
  }
203

204
  hSagittaArc(distance: number, sagitta: number): this {
205
    return this.sagittaArc(distance, 0, sagitta);
206
  }
207

208
  bulgeArcTo(end: Point2D, bulge: number): this {
209
    if (!bulge) return this.lineTo(end);
210
    const pointer = this.plane.toLocalCoords(this.pointer);
211
    const halfChord = distance2d([pointer.x, pointer.y], end) / 2;
212
    const bulgeAsSagitta = -bulge * halfChord;
213

214
    return this.sagittaArcTo(end, bulgeAsSagitta);
215
  }
216

217
  bulgeArc(xDist: number, yDist: number, bulge: number): this {
218
    const pointer = this.plane.toLocalCoords(this.pointer);
219
    return this.bulgeArcTo([xDist + pointer.x, yDist + this.pointer.y], bulge);
220
  }
221

222
  vBulgeArc(distance: number, bulge: number): this {
223
    return this.bulgeArc(0, distance, bulge);
224
  }
225

226
  hBulgeArc(distance: number, bulge: number): this {
227
    return this.bulgeArc(distance, 0, bulge);
228
  }
229

230
  ellipseTo(
231
    end: Point2D,
232
    horizontalRadius: number,
233
    verticalRadius: number,
234
    rotation = 0,
235
    longAxis = false,
236
    sweep = false
237
  ): this {
238
    const [r, gc] = localGC();
239
    const start = this.plane.toLocalCoords(this.pointer);
240

241
    let rotationAngle = rotation;
242
    let majorRadius = horizontalRadius;
243
    let minorRadius = verticalRadius;
244

245
    if (horizontalRadius < verticalRadius) {
246
      rotationAngle = rotation + 90;
247
      majorRadius = verticalRadius;
248
      minorRadius = horizontalRadius;
249
    }
250

251
    const { cx, cy, rx, ry, startAngle, endAngle, clockwise } =
252
      convertSvgEllipseParams(
253
        [start.x, start.y],
254
        end,
255
        majorRadius,
256
        minorRadius,
257
        rotationAngle * DEG2RAD,
258
        longAxis,
259
        sweep
260
      );
261

262
    const xDir = r(
263
      new Vector(this.plane.xDir).rotate(
264
        rotationAngle,
265
        this.plane.origin,
266
        this.plane.zDir
267
      )
268
    );
269

270
    const arc = makeEllipseArc(
271
      rx,
272
      ry,
273
      clockwise ? startAngle : endAngle,
274
      clockwise ? endAngle : startAngle,
275
      r(this.plane.toWorldCoords([cx, cy])),
276
      this.plane.zDir,
277
      xDir
278
    );
279

280
    if (!clockwise) {
281
      // This does not work, we may need to hack a bit more within
282
      // makeEllipseArc
283
      arc.wrapped.Reverse();
284
    }
285

286
    this.pendingEdges.push(arc);
287
    this._updatePointer(this.plane.toWorldCoords(end));
288
    gc();
289
    return this;
290
  }
291

292
  ellipse(
293
    xDist: number,
294
    yDist: number,
295
    horizontalRadius: number,
296
    verticalRadius: number,
297
    rotation = 0,
298
    longAxis = false,
299
    sweep = false
300
  ): this {
301
    const pointer = this.plane.toLocalCoords(this.pointer);
302
    return this.ellipseTo(
303
      [xDist + pointer.x, yDist + pointer.y],
304
      horizontalRadius,
305
      verticalRadius,
306
      rotation,
307
      longAxis,
308
      sweep
309
    );
310
  }
311

312
  halfEllipseTo(end: Point2D, verticalRadius: number, sweep = false): this {
313
    const pointer = this.plane.toLocalCoords(this.pointer);
314
    const start: Point2D = [pointer.x, pointer.y];
315

316
    const angle = polarAngle2d(end, start);
317
    const distance = distance2d(end, start);
318

319
    return this.ellipseTo(
320
      end,
321
      distance / 2,
322
      verticalRadius,
323
      angle * RAD2DEG,
324
      false,
325
      sweep
326
    );
327
  }
328

329
  halfEllipse(
330
    xDist: number,
331
    yDist: number,
332
    verticalRadius: number,
333
    sweep = false
334
  ): this {
335
    const pointer = this.plane.toLocalCoords(this.pointer);
336
    return this.halfEllipseTo(
337
      [xDist + pointer.x, yDist + pointer.y],
338
      verticalRadius,
339
      sweep
340
    );
341
  }
342

343
  bezierCurveTo(end: Point2D, controlPoints: Point2D | Point2D[]): this {
344
    let cp: Point2D[];
345
    if (controlPoints.length === 2 && !Array.isArray(controlPoints[0])) {
346
      cp = [controlPoints as Point2D];
347
    } else {
348
      cp = controlPoints as Point2D[];
349
    }
350

351
    const inWorldPoints = cp.map((p) => this.plane.toWorldCoords(p));
352
    const endPoint = this.plane.toWorldCoords(end);
353

354
    this.pendingEdges.push(
355
      makeBezierCurve([this.pointer, ...inWorldPoints, endPoint])
356
    );
357

358
    this._updatePointer(endPoint);
359
    return this;
360
  }
361

362
  quadraticBezierCurveTo(end: Point2D, controlPoint: Point2D): this {
363
    return this.bezierCurveTo(end, [controlPoint]);
364
  }
365

366
  cubicBezierCurveTo(
367
    end: Point2D,
368
    startControlPoint: Point2D,
369
    endControlPoint: Point2D
370
  ): this {
371
    return this.bezierCurveTo(end, [startControlPoint, endControlPoint]);
372
  }
373

374
  smoothSplineTo(end: Point2D, config?: SplineConfig): this {
375
    const [r, gc] = localGC();
376
    const { endTangent, startTangent, startFactor, endFactor } =
377
      defaultsSplineConfig(config);
378

379
    const endPoint = this.plane.toWorldCoords(end);
380
    const previousEdge = this.pendingEdges.length
381
      ? this.pendingEdges[this.pendingEdges.length - 1]
382
      : null;
383

384
    const defaultDistance = r(endPoint.sub(this.pointer)).Length * 0.25;
385

386
    let startPoleDirection: Point;
387
    if (startTangent) {
388
      startPoleDirection = this.plane.toWorldCoords(startTangent);
389
    } else if (!previousEdge) {
390
      startPoleDirection = this.plane.toWorldCoords([1, 0]);
391
    } else if (previousEdge.geomType === "BEZIER_CURVE") {
392
      const rawCurve = (
393
        r(previousEdge.curve).wrapped as CurveLike & {
394
          Bezier: () => Handle_Geom_BezierCurve;
395
        }
396
      )
397
        .Bezier()
398
        .get();
399
      const previousPole = r(new Vector(rawCurve.Pole(rawCurve.NbPoles() - 1)));
400

401
      startPoleDirection = r(this.pointer.sub(previousPole));
402
    } else {
403
      startPoleDirection = r(previousEdge.tangentAt(1));
404
    }
405

406
    const poleDistance = r(
407
      startPoleDirection.normalized().multiply(startFactor * defaultDistance)
408
    );
409
    const startControl = r(this.pointer.add(poleDistance));
410

411
    let endPoleDirection: Point;
412
    if (endTangent === "symmetric") {
413
      endPoleDirection = r(startPoleDirection.multiply(-1));
414
    } else {
415
      endPoleDirection = r(this.plane.toWorldCoords(endTangent));
416
    }
417

418
    const endPoleDistance = r(
419
      endPoleDirection.normalized().multiply(endFactor * defaultDistance)
420
    );
421
    const endControl = r(endPoint.sub(endPoleDistance));
422

423
    this.pendingEdges.push(
424
      makeBezierCurve([this.pointer, startControl, endControl, endPoint])
425
    );
426

427
    this._updatePointer(endPoint);
428
    gc();
429
    return this;
430
  }
431

432
  smoothSpline(
433
    xDist: number,
434
    yDist: number,
435
    splineConfig: SplineConfig = {}
436
  ): this {
437
    const pointer = this.plane.toLocalCoords(this.pointer);
438
    return this.smoothSplineTo(
439
      [xDist + pointer.x, yDist + pointer.y],
440
      splineConfig
441
    );
442
  }
443

444
  protected _mirrorWireOnStartEnd(wire: Wire): Wire {
445
    const startToEndVector = this.pointer.sub(this.firstPoint).normalize();
446
    const normal = startToEndVector.cross(this.plane.zDir);
447

448
    const mirroredWire = wire.clone().mirror(normal, this.pointer);
449

450
    const combinedWire = assembleWire([wire, mirroredWire]);
451

452
    return combinedWire;
453
  }
454

455
  protected buildWire(): Wire {
456
    if (!this.pendingEdges.length)
457
      throw new Error("No lines to convert into a wire");
458

459
    let wire = assembleWire(this.pendingEdges);
460

461
    if (this._mirrorWire) {
462
      wire = this._mirrorWireOnStartEnd(wire);
463
    }
464

465
    return wire;
466
  }
467

468
  protected _closeSketch(): void {
469
    if (!this.pointer.equals(this.firstPoint) && !this._mirrorWire) {
470
      const endpoint = this.plane.toLocalCoords(this.firstPoint);
471
      this.lineTo([endpoint.x, endpoint.y]);
472
    }
473
  }
474

475
  done(): Sketch {
476
    const sketch = new Sketch(this.buildWire(), {
477
      defaultOrigin: this.plane.origin,
478
      defaultDirection: this.plane.zDir,
479
    });
480
    return sketch;
481
  }
482

483
  close(): Sketch {
484
    this._closeSketch();
485
    return this.done();
486
  }
487

488
  closeWithMirror(): Sketch {
489
    this._mirrorWire = true;
490
    return this.close();
491
  }
492
}
493

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

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

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

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