Skip to content

Commit 56c884c

Browse files
tarikjabirixibyte
authored andcommitted
feat(dxf): add support for dimensions in dxf.
1 parent b4631ba commit 56c884c

File tree

3 files changed

+186
-118
lines changed

3 files changed

+186
-118
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"buffer": "^6.0.3"
7575
},
7676
"dependencies": {
77-
"@tarikjabiri/dxf": "^2.3.0",
77+
"@tarikjabiri/dxf": "^2.3.2",
7878
"@types/three": "^0.143.0",
7979
"classnames": "2.2.5",
8080
"clipper-lib": "6.2.1",

web/app/sketcher/dxf.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Colors, DLine, DxfWriter, point3d, SplineArgs_t, SplineFlags, Units, vec3_t } from '@tarikjabiri/dxf';
2+
import { Arc } from './shapes/arc';
3+
import { BezierCurve } from './shapes/bezier-curve';
4+
import { Circle } from './shapes/circle';
5+
import { AngleBetweenDimension, DiameterDimension, HDimension, LinearDimension, VDimension } from './shapes/dim';
6+
import { Ellipse } from './shapes/ellipse';
7+
import { Label } from './shapes/label';
8+
import { EndPoint } from './shapes/point';
9+
import { Segment } from './shapes/segment';
10+
import { SketchObject } from './shapes/sketch-object';
11+
import { Layer } from './viewer2d';
12+
13+
function deg2rad(a: number) {
14+
return (a * Math.PI) / 180;
15+
}
16+
17+
export class DxfWriterAdapter {
18+
writer: DxfWriter;
19+
20+
constructor() {
21+
this.writer = new DxfWriter();
22+
this.writer.setUnits(Units.Millimeters);
23+
24+
// Dimensions customization
25+
// I hard coded these values but I am not sure about them
26+
this.writer.setVariable('$DIMTXT', { 40: 10 }); // The text height
27+
this.writer.setVariable('$DIMASZ', { 40: 10 }); // Dimensioning arrow size
28+
29+
// Theses for preserving the look like jsketcher
30+
this.writer.setVariable('$DIMDEC', { 70: 2 }); // Number of precision places displayed
31+
this.writer.setVariable('$DIMTIH', { 70: 0 }); // Text inside horizontal if nonzero
32+
this.writer.setVariable('$DIMTOH', { 70: 0 }); // Text outside horizontal if nonzero
33+
// Do not force text inside extensions
34+
this.writer.setVariable('$DIMTIX', { 70: 0 }); // Force text inside extensions if nonzero
35+
this.writer.setVariable('$DIMATFIT', { 70: 0 }); // Controls dimension text and arrow placement
36+
37+
// For more customization
38+
// this.writer.setVariable('$DIMEXE', { 40: 10 }); // Extension line extension
39+
// this.writer.setVariable('$DIMCLRD', { 70: Colors.Yellow }); // Dimension line color
40+
// this.writer.setVariable('$DIMCLRE', { 70: Colors.Red }); // Dimension extension line color
41+
// this.writer.setVariable('$DIMCLRT', { 70: Colors.Green }); // Dimension text color
42+
// this.writer.setVariable('$DIMTIX', { 70: 1 }); // Force text inside extensions if nonzero
43+
}
44+
45+
private _point(shape: EndPoint) {
46+
this.writer.addPoint(shape.x, shape.y, 0);
47+
}
48+
49+
private _segment(shape: Segment) {
50+
this.writer.addLine(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y));
51+
}
52+
53+
private _arc(shape: Arc) {
54+
this.writer.addArc(
55+
point3d(shape.c.x, shape.c.y),
56+
shape.r.get(),
57+
deg2rad(shape.getStartAngle()),
58+
deg2rad(shape.getEndAngle())
59+
);
60+
}
61+
62+
private _circle(shape: Circle) {
63+
this.writer.addCircle(point3d(shape.c.x, shape.c.y), shape.r.get());
64+
}
65+
66+
private _ellipse(shape: Ellipse) {
67+
const majorX = Math.cos(shape.rotation) * shape.radiusX;
68+
const majorY = Math.sin(shape.rotation) * shape.radiusX;
69+
this.writer.addEllipse(
70+
point3d(shape.centerX, shape.centerY),
71+
point3d(majorX, majorY),
72+
shape.radiusY / shape.radiusX,
73+
0,
74+
2 * Math.PI
75+
);
76+
}
77+
78+
private _bezier(shape: BezierCurve) {
79+
const controlPoints: vec3_t[] = [
80+
point3d(shape.p0.x, shape.p0.y),
81+
point3d(shape.p1.x, shape.p1.y),
82+
point3d(shape.p2.x, shape.p2.y),
83+
point3d(shape.p3.x, shape.p3.y),
84+
];
85+
const splineArgs: SplineArgs_t = {
86+
controlPoints,
87+
flags: SplineFlags.Periodic,
88+
};
89+
this.writer.addSpline(splineArgs);
90+
}
91+
92+
private _label(shape: Label) {
93+
const m = shape.assignedObject.labelCenter;
94+
if (!m) return;
95+
const height = shape.textHelper.fontSize;
96+
const h = shape.textHelper.textMetrics.width / 2;
97+
const lx = m.x - h + shape.offsetX;
98+
const ly = m.y + shape.marginOffset + shape.offsetY;
99+
this.writer.addText(point3d(lx, ly), height, shape.text);
100+
}
101+
102+
private _vdim(shape: VDimension) {
103+
this.writer.addLinearDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), {
104+
angle: 90, // Make it vertical
105+
offset: -shape.offset,
106+
});
107+
}
108+
109+
private _hdim(shape: HDimension) {
110+
this.writer.addLinearDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: -shape.offset });
111+
}
112+
113+
private _linearDim(shape: LinearDimension) {
114+
this.writer.addAlignedDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: shape.offset });
115+
}
116+
117+
private _ddim(shape: DiameterDimension) {
118+
// I remarked that the DiameterDimension looks like Radius dimension so I used RadialDim
119+
const radius = shape.obj.distanceA ? shape.obj.distanceA() : shape.obj.r.get();
120+
const x = shape.obj.c.x + radius * Math.cos(shape.angle);
121+
const y = shape.obj.c.y + radius * Math.sin(shape.angle);
122+
this.writer.addRadialDim(point3d(x, y), point3d(shape.obj.c.x, shape.obj.c.y));
123+
}
124+
125+
private _bwdim(shape: AngleBetweenDimension) {
126+
// This is not working as expected.
127+
const s: DLine = {
128+
start: point3d(shape.a.a.x, shape.a.a.y),
129+
end: point3d(shape.a.b.x, shape.a.b.y),
130+
};
131+
const f: DLine = {
132+
start: point3d(shape.b.a.x, shape.b.a.y),
133+
end: point3d(shape.b.b.x, shape.b.b.y),
134+
};
135+
const c = point3d(shape.a.a.x, shape.a.a.y);
136+
const offset = shape.offset;
137+
const dyf = f.end.y - c.y;
138+
const dys = s.end.y - c.y;
139+
const df = Math.sqrt(Math.pow(f.end.x - c.x, 2) + Math.pow(f.end.y - c.y, 2));
140+
const ds = Math.sqrt(Math.pow(s.end.x - c.x, 2) + Math.pow(s.end.y - c.y, 2));
141+
const alphaf = Math.acos(dyf / df);
142+
const alphas = Math.acos(dys / ds);
143+
const alpham = Math.abs(alphaf - alphas) / 2 + (alphaf > alphas ? alphas : alphaf);
144+
const xm = c.x + offset*Math.cos(alpham)
145+
const ym = c.y + offset*Math.sin(alpham)
146+
this.writer.addAngularLinesDim(f, s, point3d(xm, ym));
147+
}
148+
149+
export(layers: Layer<SketchObject>[]) {
150+
layers.forEach(layer => {
151+
// this will prevent addLayer from throwing.
152+
if (!this.writer.tables.layerTable.exist(layer.name))
153+
this.writer.addLayer(layer.name, Colors.Black, 'Continuous');
154+
this.writer.setCurrentLayerName(layer.name);
155+
156+
layer.objects.forEach(shape => {
157+
if (shape instanceof EndPoint) this._point(shape);
158+
else if (shape instanceof Segment) this._segment(shape);
159+
else if (shape instanceof Arc) this._arc(shape);
160+
else if (shape instanceof Circle) this._circle(shape);
161+
else if (shape instanceof Ellipse) this._ellipse(shape);
162+
else if (shape instanceof BezierCurve) this._bezier(shape);
163+
else if (shape instanceof Label) this._label(shape);
164+
else if (shape instanceof VDimension) this._vdim(shape);
165+
else if (shape instanceof HDimension) this._hdim(shape);
166+
else if (shape instanceof LinearDimension) this._linearDim(shape);
167+
else if (shape instanceof DiameterDimension) this._ddim(shape);
168+
else if (shape instanceof AngleBetweenDimension) this._bwdim(shape);
169+
});
170+
});
171+
}
172+
173+
stringify(): string {
174+
// reset the current layer to 0, because its preserved in the dxf.
175+
this.writer.setCurrentLayerName('0');
176+
return this.writer.stringify();
177+
}
178+
}

web/app/sketcher/io.ts

Lines changed: 7 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,7 @@ import { BoundaryGeneratorSchema } from './generators/boundaryGenerator';
2626
import { ShapesTypes } from './shapes/sketch-types';
2727
import { SketchObject } from './shapes/sketch-object';
2828
import { Label } from 'sketcher/shapes/label';
29-
import {
30-
Colors,
31-
DxfWriter,
32-
point3d,
33-
SplineArgs_t,
34-
SplineFlags,
35-
Units,
36-
vec3_t,
37-
} from '@tarikjabiri/dxf';
38-
import { DEG_RAD } from 'math/commons';
29+
import { DxfWriterAdapter } from './dxf';
3930

4031
export interface SketchFormat_V3 {
4132
version: number;
@@ -357,34 +348,6 @@ export class IO {
357348
return toExport;
358349
}
359350

360-
isArc(obj: SketchObject): obj is Arc {
361-
return obj.TYPE === ShapesTypes.ARC;
362-
}
363-
364-
isSegment(obj: SketchObject): obj is Segment {
365-
return obj.TYPE === ShapesTypes.SEGMENT;
366-
}
367-
368-
isCircle(obj: SketchObject): obj is Circle {
369-
return obj.TYPE === ShapesTypes.CIRCLE;
370-
}
371-
372-
isPoint(obj: SketchObject): obj is EndPoint {
373-
return obj.TYPE === ShapesTypes.POINT;
374-
}
375-
376-
isEllipse(obj: SketchObject): obj is Ellipse {
377-
return obj.TYPE === ShapesTypes.ELLIPSE;
378-
}
379-
380-
isBezier(obj: SketchObject): obj is BezierCurve {
381-
return obj.TYPE === ShapesTypes.BEZIER;
382-
}
383-
384-
isLabel(obj: SketchObject): obj is Label {
385-
return obj.TYPE === ShapesTypes.LABEL;
386-
}
387-
388351
svgExport() {
389352
const T = ShapesTypes;
390353
const out = new TextBuilder();
@@ -408,14 +371,14 @@ export class IO {
408371
for (let i = 0; i < layer.objects.length; ++i) {
409372
const obj = layer.objects[i];
410373
if (obj.TYPE !== T.POINT) bbox.check(obj);
411-
if (this.isSegment(obj)) {
374+
if (obj instanceof Segment) {
412375
out.fline('<line x1="$" y1="$" x2="$" y2="$" />', [
413376
obj.a.x,
414377
obj.a.y,
415378
obj.b.x,
416379
obj.b.y,
417380
]);
418-
} else if (this.isArc(obj)) {
381+
} else if (obj instanceof Arc) {
419382
a.set(obj.a.x - obj.c.x, obj.a.y - obj.c.y, 0);
420383
b.set(obj.b.x - obj.c.x, obj.b.y - obj.c.y, 0);
421384
const dir = a.cross(b).z > 0 ? 0 : 1;
@@ -430,7 +393,7 @@ export class IO {
430393
obj.b.x,
431394
obj.b.y,
432395
]);
433-
} else if (this.isCircle(obj)) {
396+
} else if (obj instanceof Circle) {
434397
out.fline('<circle cx="$" cy="$" r="$" />', [
435398
obj.c.x,
436399
obj.c.y,
@@ -448,82 +411,9 @@ export class IO {
448411
}
449412

450413
dxfExport() {
451-
const dxf: DxfWriter = new DxfWriter();
452-
const layersToExport = this.getLayersToExport();
453-
dxf.setUnits(Units.Millimeters);
454-
455-
layersToExport.forEach(layer => {
456-
const dxfLayer = dxf.addLayer(layer.name, Colors.Green, 'Continuous');
457-
dxf.setCurrentLayerName(dxfLayer.name);
458-
459-
layer.objects.forEach(obj => {
460-
console.debug('exporting object', obj);
461-
462-
if (this.isPoint(obj)) {
463-
dxf.addPoint(obj.x, obj.y, 0);
464-
} else if (this.isSegment(obj)) {
465-
dxf.addLine(
466-
point3d(obj.a.x, obj.a.y, 0),
467-
point3d(obj.b.x, obj.b.y, 0)
468-
);
469-
} else if (this.isArc(obj)) {
470-
dxf.addArc(
471-
point3d(obj.c.x, obj.c.y, 0),
472-
obj.r.get(),
473-
obj.getStartAngle() / DEG_RAD,
474-
obj.getEndAngle() / DEG_RAD
475-
);
476-
} else if (this.isCircle(obj)) {
477-
dxf.addCircle(point3d(obj.c.x, obj.c.y, 0), obj.r.get());
478-
} else if (this.isEllipse(obj)) {
479-
const majorX = Math.cos(obj.rotation) * obj.radiusX;
480-
const majorY = Math.sin(obj.rotation) * obj.radiusX;
481-
dxf.addEllipse(
482-
point3d(obj.centerX, obj.centerY, 0),
483-
point3d(majorX, majorY, 0),
484-
obj.radiusY / obj.radiusX,
485-
0,
486-
2 * Math.PI
487-
);
488-
} else if (this.isBezier(obj)) {
489-
const controlPoints: vec3_t[] = [
490-
point3d(obj.p0.x, obj.p0.y, 0),
491-
point3d(obj.p1.x, obj.p1.y, 0),
492-
point3d(obj.p2.x, obj.p2.y, 0),
493-
point3d(obj.p3.x, obj.p3.y, 0),
494-
];
495-
const splineArgs: SplineArgs_t = {
496-
controlPoints,
497-
flags: SplineFlags.Closed | SplineFlags.Periodic, // 3
498-
};
499-
dxf.addSpline(splineArgs);
500-
} else if (this.isLabel(obj)) {
501-
const m = obj.assignedObject.labelCenter;
502-
if (!m) {
503-
return;
504-
}
505-
const height = obj.textHelper.textMetrics.height as number;
506-
const h = obj.textHelper.textMetrics.width / 2;
507-
const lx = m.x - h + obj.offsetX;
508-
const ly = m.y + obj.marginOffset + obj.offsetY;
509-
510-
dxf.addText(point3d(lx, ly, 0), height, obj.text);
511-
} else if (
512-
obj.TYPE === ShapesTypes.DIM ||
513-
obj.TYPE === ShapesTypes.HDIM ||
514-
obj.TYPE === ShapesTypes.VDIM
515-
) {
516-
// I want to add dimensions but there is no access for them here 🤔
517-
// dxf.addAlignedDim()
518-
// dxf.addDiameterDim()
519-
// dxf.addRadialDim()
520-
}
521-
});
522-
});
523-
524-
// reset the current layer to 0, because its preserved in the dxf.
525-
dxf.setCurrentLayerName('0');
526-
return dxf.stringify();
414+
const adapter = new DxfWriterAdapter();
415+
adapter.export(this.getLayersToExport());
416+
return adapter.stringify();
527417
}
528418
}
529419

0 commit comments

Comments
 (0)