From 56c884ccfd551e0b2dcbee0fc0a0eff62fb6a338 Mon Sep 17 00:00:00 2001 From: EL JABIRI Tarik Date: Sat, 27 Aug 2022 07:14:39 +0100 Subject: [PATCH] feat(dxf): add support for dimensions in dxf. --- package.json | 2 +- web/app/sketcher/dxf.ts | 178 ++++++++++++++++++++++++++++++++++++++++ web/app/sketcher/io.ts | 124 ++-------------------------- 3 files changed, 186 insertions(+), 118 deletions(-) create mode 100644 web/app/sketcher/dxf.ts diff --git a/package.json b/package.json index b4d6a3c4..e8d6adee 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "buffer": "^6.0.3" }, "dependencies": { - "@tarikjabiri/dxf": "^2.3.0", + "@tarikjabiri/dxf": "^2.3.2", "@types/three": "^0.143.0", "classnames": "2.2.5", "clipper-lib": "6.2.1", diff --git a/web/app/sketcher/dxf.ts b/web/app/sketcher/dxf.ts new file mode 100644 index 00000000..28e2fd32 --- /dev/null +++ b/web/app/sketcher/dxf.ts @@ -0,0 +1,178 @@ +import { Colors, DLine, DxfWriter, point3d, SplineArgs_t, SplineFlags, Units, vec3_t } from '@tarikjabiri/dxf'; +import { Arc } from './shapes/arc'; +import { BezierCurve } from './shapes/bezier-curve'; +import { Circle } from './shapes/circle'; +import { AngleBetweenDimension, DiameterDimension, HDimension, LinearDimension, VDimension } from './shapes/dim'; +import { Ellipse } from './shapes/ellipse'; +import { Label } from './shapes/label'; +import { EndPoint } from './shapes/point'; +import { Segment } from './shapes/segment'; +import { SketchObject } from './shapes/sketch-object'; +import { Layer } from './viewer2d'; + +function deg2rad(a: number) { + return (a * Math.PI) / 180; +} + +export class DxfWriterAdapter { + writer: DxfWriter; + + constructor() { + this.writer = new DxfWriter(); + this.writer.setUnits(Units.Millimeters); + + // Dimensions customization + // I hard coded these values but I am not sure about them + this.writer.setVariable('$DIMTXT', { 40: 10 }); // The text height + this.writer.setVariable('$DIMASZ', { 40: 10 }); // Dimensioning arrow size + + // Theses for preserving the look like jsketcher + this.writer.setVariable('$DIMDEC', { 70: 2 }); // Number of precision places displayed + this.writer.setVariable('$DIMTIH', { 70: 0 }); // Text inside horizontal if nonzero + this.writer.setVariable('$DIMTOH', { 70: 0 }); // Text outside horizontal if nonzero + // Do not force text inside extensions + this.writer.setVariable('$DIMTIX', { 70: 0 }); // Force text inside extensions if nonzero + this.writer.setVariable('$DIMATFIT', { 70: 0 }); // Controls dimension text and arrow placement + + // For more customization + // this.writer.setVariable('$DIMEXE', { 40: 10 }); // Extension line extension + // this.writer.setVariable('$DIMCLRD', { 70: Colors.Yellow }); // Dimension line color + // this.writer.setVariable('$DIMCLRE', { 70: Colors.Red }); // Dimension extension line color + // this.writer.setVariable('$DIMCLRT', { 70: Colors.Green }); // Dimension text color + // this.writer.setVariable('$DIMTIX', { 70: 1 }); // Force text inside extensions if nonzero + } + + private _point(shape: EndPoint) { + this.writer.addPoint(shape.x, shape.y, 0); + } + + private _segment(shape: Segment) { + this.writer.addLine(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y)); + } + + private _arc(shape: Arc) { + this.writer.addArc( + point3d(shape.c.x, shape.c.y), + shape.r.get(), + deg2rad(shape.getStartAngle()), + deg2rad(shape.getEndAngle()) + ); + } + + private _circle(shape: Circle) { + this.writer.addCircle(point3d(shape.c.x, shape.c.y), shape.r.get()); + } + + private _ellipse(shape: Ellipse) { + const majorX = Math.cos(shape.rotation) * shape.radiusX; + const majorY = Math.sin(shape.rotation) * shape.radiusX; + this.writer.addEllipse( + point3d(shape.centerX, shape.centerY), + point3d(majorX, majorY), + shape.radiusY / shape.radiusX, + 0, + 2 * Math.PI + ); + } + + private _bezier(shape: BezierCurve) { + const controlPoints: vec3_t[] = [ + point3d(shape.p0.x, shape.p0.y), + point3d(shape.p1.x, shape.p1.y), + point3d(shape.p2.x, shape.p2.y), + point3d(shape.p3.x, shape.p3.y), + ]; + const splineArgs: SplineArgs_t = { + controlPoints, + flags: SplineFlags.Periodic, + }; + this.writer.addSpline(splineArgs); + } + + private _label(shape: Label) { + const m = shape.assignedObject.labelCenter; + if (!m) return; + const height = shape.textHelper.fontSize; + const h = shape.textHelper.textMetrics.width / 2; + const lx = m.x - h + shape.offsetX; + const ly = m.y + shape.marginOffset + shape.offsetY; + this.writer.addText(point3d(lx, ly), height, shape.text); + } + + private _vdim(shape: VDimension) { + this.writer.addLinearDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { + angle: 90, // Make it vertical + offset: -shape.offset, + }); + } + + private _hdim(shape: HDimension) { + this.writer.addLinearDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: -shape.offset }); + } + + private _linearDim(shape: LinearDimension) { + this.writer.addAlignedDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: shape.offset }); + } + + private _ddim(shape: DiameterDimension) { + // I remarked that the DiameterDimension looks like Radius dimension so I used RadialDim + const radius = shape.obj.distanceA ? shape.obj.distanceA() : shape.obj.r.get(); + const x = shape.obj.c.x + radius * Math.cos(shape.angle); + const y = shape.obj.c.y + radius * Math.sin(shape.angle); + this.writer.addRadialDim(point3d(x, y), point3d(shape.obj.c.x, shape.obj.c.y)); + } + + private _bwdim(shape: AngleBetweenDimension) { + // This is not working as expected. + const s: DLine = { + start: point3d(shape.a.a.x, shape.a.a.y), + end: point3d(shape.a.b.x, shape.a.b.y), + }; + const f: DLine = { + start: point3d(shape.b.a.x, shape.b.a.y), + end: point3d(shape.b.b.x, shape.b.b.y), + }; + const c = point3d(shape.a.a.x, shape.a.a.y); + const offset = shape.offset; + const dyf = f.end.y - c.y; + const dys = s.end.y - c.y; + const df = Math.sqrt(Math.pow(f.end.x - c.x, 2) + Math.pow(f.end.y - c.y, 2)); + const ds = Math.sqrt(Math.pow(s.end.x - c.x, 2) + Math.pow(s.end.y - c.y, 2)); + const alphaf = Math.acos(dyf / df); + const alphas = Math.acos(dys / ds); + const alpham = Math.abs(alphaf - alphas) / 2 + (alphaf > alphas ? alphas : alphaf); + const xm = c.x + offset*Math.cos(alpham) + const ym = c.y + offset*Math.sin(alpham) + this.writer.addAngularLinesDim(f, s, point3d(xm, ym)); + } + + export(layers: Layer[]) { + layers.forEach(layer => { + // this will prevent addLayer from throwing. + if (!this.writer.tables.layerTable.exist(layer.name)) + this.writer.addLayer(layer.name, Colors.Black, 'Continuous'); + this.writer.setCurrentLayerName(layer.name); + + layer.objects.forEach(shape => { + if (shape instanceof EndPoint) this._point(shape); + else if (shape instanceof Segment) this._segment(shape); + else if (shape instanceof Arc) this._arc(shape); + else if (shape instanceof Circle) this._circle(shape); + else if (shape instanceof Ellipse) this._ellipse(shape); + else if (shape instanceof BezierCurve) this._bezier(shape); + else if (shape instanceof Label) this._label(shape); + else if (shape instanceof VDimension) this._vdim(shape); + else if (shape instanceof HDimension) this._hdim(shape); + else if (shape instanceof LinearDimension) this._linearDim(shape); + else if (shape instanceof DiameterDimension) this._ddim(shape); + else if (shape instanceof AngleBetweenDimension) this._bwdim(shape); + }); + }); + } + + stringify(): string { + // reset the current layer to 0, because its preserved in the dxf. + this.writer.setCurrentLayerName('0'); + return this.writer.stringify(); + } +} \ No newline at end of file diff --git a/web/app/sketcher/io.ts b/web/app/sketcher/io.ts index 00af35b9..f52a1edb 100644 --- a/web/app/sketcher/io.ts +++ b/web/app/sketcher/io.ts @@ -26,16 +26,7 @@ import { BoundaryGeneratorSchema } from './generators/boundaryGenerator'; import { ShapesTypes } from './shapes/sketch-types'; import { SketchObject } from './shapes/sketch-object'; import { Label } from 'sketcher/shapes/label'; -import { - Colors, - DxfWriter, - point3d, - SplineArgs_t, - SplineFlags, - Units, - vec3_t, -} from '@tarikjabiri/dxf'; -import { DEG_RAD } from 'math/commons'; +import { DxfWriterAdapter } from './dxf'; export interface SketchFormat_V3 { version: number; @@ -357,34 +348,6 @@ export class IO { return toExport; } - isArc(obj: SketchObject): obj is Arc { - return obj.TYPE === ShapesTypes.ARC; - } - - isSegment(obj: SketchObject): obj is Segment { - return obj.TYPE === ShapesTypes.SEGMENT; - } - - isCircle(obj: SketchObject): obj is Circle { - return obj.TYPE === ShapesTypes.CIRCLE; - } - - isPoint(obj: SketchObject): obj is EndPoint { - return obj.TYPE === ShapesTypes.POINT; - } - - isEllipse(obj: SketchObject): obj is Ellipse { - return obj.TYPE === ShapesTypes.ELLIPSE; - } - - isBezier(obj: SketchObject): obj is BezierCurve { - return obj.TYPE === ShapesTypes.BEZIER; - } - - isLabel(obj: SketchObject): obj is Label { - return obj.TYPE === ShapesTypes.LABEL; - } - svgExport() { const T = ShapesTypes; const out = new TextBuilder(); @@ -408,14 +371,14 @@ export class IO { for (let i = 0; i < layer.objects.length; ++i) { const obj = layer.objects[i]; if (obj.TYPE !== T.POINT) bbox.check(obj); - if (this.isSegment(obj)) { + if (obj instanceof Segment) { out.fline('', [ obj.a.x, obj.a.y, obj.b.x, obj.b.y, ]); - } else if (this.isArc(obj)) { + } else if (obj instanceof Arc) { a.set(obj.a.x - obj.c.x, obj.a.y - obj.c.y, 0); b.set(obj.b.x - obj.c.x, obj.b.y - obj.c.y, 0); const dir = a.cross(b).z > 0 ? 0 : 1; @@ -430,7 +393,7 @@ export class IO { obj.b.x, obj.b.y, ]); - } else if (this.isCircle(obj)) { + } else if (obj instanceof Circle) { out.fline('', [ obj.c.x, obj.c.y, @@ -448,82 +411,9 @@ export class IO { } dxfExport() { - const dxf: DxfWriter = new DxfWriter(); - const layersToExport = this.getLayersToExport(); - dxf.setUnits(Units.Millimeters); - - layersToExport.forEach(layer => { - const dxfLayer = dxf.addLayer(layer.name, Colors.Green, 'Continuous'); - dxf.setCurrentLayerName(dxfLayer.name); - - layer.objects.forEach(obj => { - console.debug('exporting object', obj); - - if (this.isPoint(obj)) { - dxf.addPoint(obj.x, obj.y, 0); - } else if (this.isSegment(obj)) { - dxf.addLine( - point3d(obj.a.x, obj.a.y, 0), - point3d(obj.b.x, obj.b.y, 0) - ); - } else if (this.isArc(obj)) { - dxf.addArc( - point3d(obj.c.x, obj.c.y, 0), - obj.r.get(), - obj.getStartAngle() / DEG_RAD, - obj.getEndAngle() / DEG_RAD - ); - } else if (this.isCircle(obj)) { - dxf.addCircle(point3d(obj.c.x, obj.c.y, 0), obj.r.get()); - } else if (this.isEllipse(obj)) { - const majorX = Math.cos(obj.rotation) * obj.radiusX; - const majorY = Math.sin(obj.rotation) * obj.radiusX; - dxf.addEllipse( - point3d(obj.centerX, obj.centerY, 0), - point3d(majorX, majorY, 0), - obj.radiusY / obj.radiusX, - 0, - 2 * Math.PI - ); - } else if (this.isBezier(obj)) { - const controlPoints: vec3_t[] = [ - point3d(obj.p0.x, obj.p0.y, 0), - point3d(obj.p1.x, obj.p1.y, 0), - point3d(obj.p2.x, obj.p2.y, 0), - point3d(obj.p3.x, obj.p3.y, 0), - ]; - const splineArgs: SplineArgs_t = { - controlPoints, - flags: SplineFlags.Closed | SplineFlags.Periodic, // 3 - }; - dxf.addSpline(splineArgs); - } else if (this.isLabel(obj)) { - const m = obj.assignedObject.labelCenter; - if (!m) { - return; - } - const height = obj.textHelper.textMetrics.height as number; - const h = obj.textHelper.textMetrics.width / 2; - const lx = m.x - h + obj.offsetX; - const ly = m.y + obj.marginOffset + obj.offsetY; - - dxf.addText(point3d(lx, ly, 0), height, obj.text); - } else if ( - obj.TYPE === ShapesTypes.DIM || - obj.TYPE === ShapesTypes.HDIM || - obj.TYPE === ShapesTypes.VDIM - ) { - // I want to add dimensions but there is no access for them here 🤔 - // dxf.addAlignedDim() - // dxf.addDiameterDim() - // dxf.addRadialDim() - } - }); - }); - - // reset the current layer to 0, because its preserved in the dxf. - dxf.setCurrentLayerName('0'); - return dxf.stringify(); + const adapter = new DxfWriterAdapter(); + adapter.export(this.getLayersToExport()); + return adapter.stringify(); } }