diff --git a/.gitignore b/.gitignore index ab81b2cb..f337e6af 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ /node_modules /dist /.vs -/web/docs/ \ No newline at end of file +/web/docs/ +pnpm-lock.yaml \ No newline at end of file diff --git a/package.json b/package.json index b0469981..42fd5fac 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@types/three": "^0.143.0", "classnames": "2.2.5", "clipper-lib": "6.2.1", - "dxf-writer": "^1.18.2", + "@tarikjabiri/dxf": "^2.3.0", "earcut": "2.1.1", "font-awesome": "4.7.0", "immer": "^9.0.12", diff --git a/web/app/sketcher/io.ts b/web/app/sketcher/io.ts index cd7bf7a0..ddbd9c9b 100644 --- a/web/app/sketcher/io.ts +++ b/web/app/sketcher/io.ts @@ -1,64 +1,72 @@ -import {Generator} from './id-generator' -import {Viewer} from './viewer2d' -import {Arc} from './shapes/arc' -import {EndPoint} from './shapes/point' -import {Segment} from './shapes/segment' -import {Circle} from './shapes/circle' -import {Ellipse} from './shapes/ellipse' -import {EllipticalArc} from './shapes/elliptical-arc' -import {BezierCurve} from './shapes/bezier-curve' +import { Generator } from './id-generator'; +import { Layer, Viewer } from './viewer2d'; +import { Arc } from './shapes/arc'; +import { EndPoint } from './shapes/point'; +import { Segment } from './shapes/segment'; +import { Circle } from './shapes/circle'; +import { Ellipse } from './shapes/ellipse'; +import { EllipticalArc } from './shapes/elliptical-arc'; +import { BezierCurve } from './shapes/bezier-curve'; import { AngleBetweenDimension, DiameterDimension, Dimension, HDimension, LinearDimension, - VDimension -} from './shapes/dim' + VDimension, +} from './shapes/dim'; import Vector from 'math/vector'; import exportTextData from 'gems/exportTextData'; -import {AlgNumConstraint, ConstraintSerialization} from "./constr/ANConstraints"; -import {SketchGenerator} from "./generators/sketchGenerator"; -import {BoundaryGeneratorSchema} from "./generators/boundaryGenerator"; -import {SketchTypes} from "./shapes/sketch-types"; -import {SketchObject} from "./shapes/sketch-object"; -import {Label} from "sketcher/shapes/label"; -import Drawing from 'dxf-writer'; -import {DEG_RAD} from "math/commons"; - +import { + AlgNumConstraint, + ConstraintSerialization, +} from './constr/ANConstraints'; +import { SketchGenerator } from './generators/sketchGenerator'; +import { BoundaryGeneratorSchema } from './generators/boundaryGenerator'; +import { ShapesTypes, SketchTypes } 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 { Point } from './shapes/primitives'; export interface SketchFormat_V3 { - version: number; objects: { - id: string, - type: string, - role: string, - stage: number, - data: any + id: string; + type: string; + role: string; + stage: number; + data: any; }[]; dimensions: { - id: string, - type: string, - data: any + id: string; + type: string; + data: any; }[]; labels: { - id: string, - type: string, - data: any + id: string; + type: string; + data: any; }[]; stages: { - generators: { - typeId: string + typeId: string; }[]; constraints: ConstraintSerialization[]; - }[]; constants: { @@ -67,18 +75,13 @@ export interface SketchFormat_V3 { metadata: any; - boundary?: ExternalBoundary - -} - -class ExternalBoundary { - + boundary?: ExternalBoundary; } +class ExternalBoundary {} export class IO { - - static exportTextData = exportTextData + static exportTextData = exportTextData; viewer: Viewer; @@ -86,7 +89,6 @@ export class IO { this.viewer = viewer; } - loadSketch(sketchData) { return this._loadSketch(JSON.parse(sketchData)); } @@ -96,7 +98,6 @@ export class IO { } _loadSketch(sketch: SketchFormat_V3) { - this.cleanUpData(); this.viewer.parametricManager.startTransaction(); @@ -145,10 +146,9 @@ export class IO { sketchLayer.add(skobj); skobj.stabilize(this.viewer); } - } catch (e) { console.error(e); - console.error("Failed loading " + obj.type + " " + obj.id); + console.error('Failed loading ' + obj.type + ' ' + obj.id); } } @@ -163,7 +163,12 @@ export class IO { } else if (type === VDimension.prototype.TYPE) { skobj = LinearDimension.load(VDimension, obj.id, obj.data, index); } else if (type === LinearDimension.prototype.TYPE) { - skobj = LinearDimension.load(LinearDimension, obj.id, obj.data, index); + skobj = LinearDimension.load( + LinearDimension, + obj.id, + obj.data, + index + ); } else if (type === DiameterDimension.prototype.TYPE) { skobj = DiameterDimension.load(obj.id, obj.data, index); } else if (type === AngleBetweenDimension.prototype.TYPE) { @@ -172,10 +177,9 @@ export class IO { if (skobj !== null) { this.viewer.dimLayer.add(skobj); } - } catch (e) { console.error(e); - console.error("Failed loading " + obj.type + " " + obj.id); + console.error('Failed loading ' + obj.type + ' ' + obj.id); } } @@ -190,10 +194,9 @@ export class IO { if (skobj !== null) { this.viewer.labelLayer.add(skobj); } - } catch (e) { console.error(e); - console.error("Failed loading " + obj.type + " " + obj.id); + console.error('Failed loading ' + obj.type + ' ' + obj.id); } } } @@ -204,19 +207,21 @@ export class IO { for (const constr of dataStage.constraints) { try { const constraint = AlgNumConstraint.read(constr, index); - stage.addConstraint(constraint) + stage.addConstraint(constraint); } catch (e) { console.error(e); - console.error("skipping errant constraint: " + constr && constr.typeId); + console.error( + 'skipping errant constraint: ' + constr && constr.typeId + ); } } for (const gen of dataStage.generators) { try { const generator = SketchGenerator.read(gen, index); - stage.addGenerator(generator) + stage.addGenerator(generator); } catch (e) { console.error(e); - console.error("skipping errant generator: " + gen && gen.typeId); + console.error('skipping errant generator: ' + gen && gen.typeId); } } } @@ -225,22 +230,24 @@ export class IO { if (constants !== undefined) { this.viewer.parametricManager.$constantDefinition.next(constants); } - } finally { this.viewer.parametricManager.finishTransaction(); this.viewer.parametricManager.notify(); } - } - createBoundaryObjects(boundary) { + const boundaryGenerator = new SketchGenerator( + { + boundaryData: boundary, + }, + BoundaryGeneratorSchema + ); - const boundaryGenerator = new SketchGenerator({ - boundaryData: boundary - }, BoundaryGeneratorSchema); - - this.viewer.parametricManager.addGeneratorToStage(boundaryGenerator, this.viewer.parametricManager.groundStage); + this.viewer.parametricManager.addGeneratorToStage( + boundaryGenerator, + this.viewer.parametricManager.groundStage + ); } cleanUpData() { @@ -255,11 +262,9 @@ export class IO { this.viewer.parametricManager.reset(); this.viewer.parametricManager.notify(); - } _serializeSketch(metadata) { - const sketch: SketchFormat_V3 = { version: 3, objects: [], @@ -267,7 +272,7 @@ export class IO { labels: [], stages: [], constants: this.viewer.parametricManager.constantDefinition, - metadata + metadata, }; for (const layer of this.viewer.layers) { @@ -287,7 +292,7 @@ export class IO { type: obj.TYPE, role: obj.role, stage: this.viewer.parametricManager.getStageIndex(obj.stage), - data: obj.write() + data: obj.write(), }); } catch (e) { console.error(e); @@ -301,13 +306,12 @@ export class IO { into.push({ id: obj.id, type: obj.TYPE, - data: obj.write() + data: obj.write(), }); } catch (e) { console.error(e); } } - } pushObjectsFromLayer(this.viewer.dimLayer, sketch.dimensions); pushObjectsFromLayer(this.viewer.labelLayer, sketch.labels); @@ -343,19 +347,46 @@ export class IO { getLayersToExport() { const ws = this.getWorkspaceToExport(); - const toExport = []; + const toExport: Layer[] = []; for (let t = 0; t < ws.length; ++t) { const layers = ws[t]; for (let l = 0; l < layers.length; ++l) { const layer = layers[l]; - toExport.push(layer) + toExport.push(layer); } } return toExport; } - svgExport() { + 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 = SketchTypes; const out = new TextBuilder(); @@ -369,93 +400,129 @@ export class IO { for (let l = 0; l < toExport.length; ++l) { const layer = toExport[l]; const color = prettyColors.next(); - out.fline('', [layer.name, "none", color, '2']); + out.fline('', [ + layer.name, + 'none', + color, + '2', + ]); for (let i = 0; i < layer.objects.length; ++i) { const obj = layer.objects[i]; - if (obj._class !== T.POINT) bbox.check(obj); - if (obj._class === T.SEGMENT) { - out.fline('', [obj.a.x, obj.a.y, obj.b.x, obj.b.y]); - } else if (obj._class === T.ARC) { + if (obj.TYPE !== T.POINT) bbox.check(obj); + if (this.isSegment(obj)) { + out.fline('', [ + obj.a.x, + obj.a.y, + obj.b.x, + obj.b.y, + ]); + } else if (this.isArc(obj)) { 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; const r = obj.r.get(); - out.fline('', [obj.a.x, obj.a.y, r, r, dir, 1, obj.b.x, obj.b.y]); - } else if (obj._class === T.CIRCLE) { - out.fline('', [obj.c.x, obj.c.y, obj.r.get()]); - // } else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) { + out.fline('', [ + obj.a.x, + obj.a.y, + r, + r, + dir, + 1, + obj.b.x, + obj.b.y, + ]); + } else if (this.isCircle(obj)) { + out.fline('', [ + obj.c.x, + obj.c.y, + obj.r.get(), + ]); + // } else if (obj.TYPE === T.DIM || obj.TYPE === T.HDIM || obj.TYPE === T.VDIM) { } } out.line(''); } bbox.inc(20); - return _format("\n", bbox.bbox) + out.data + "" + return ( + _format("\n", bbox.bbox) + out.data + '' + ); } - dxfExport() { + dxfExport() { + const dxf: DxfWriter = new DxfWriter(); + const layersToExport = this.getLayersToExport(); + dxf.setUnits(Units.Millimeters); - // defines as any since library doesn't provide types correctly - const d: any = new Drawing(); + layersToExport.forEach(layer => { + const dxfLayer = dxf.addLayer(layer.name, Colors.Green, 'Continuous'); + dxf.setCurrentLayerName(dxfLayer.name); - const T = SketchTypes; + layer.objects.forEach(obj => { + console.debug('exporting object', obj); - const toExport = this.getLayersToExport(); + 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; - d.setUnits('Millimeters'); + dxf.addText(point3d(lx, ly, 0), height, obj.text); + } else if ( + obj.TYPE === SketchTypes.DIM || + obj.TYPE === SketchTypes.HDIM || + obj.TYPE === SketchTypes.VDIM + ) { + // I want to add dimensions but there is no access for them here 🤔 + // dxf.addAlignedDim() + // dxf.addDiameterDim() + // dxf.addRadialDim() + } + }); + }); - for (let l = 0; l < toExport.length; l++) { - const layer = toExport[l]; - const dxfLayerId = l + '_green'; - d.addLayer(dxfLayerId, Drawing.ACI.GREEN, 'CONTINUOUS'); - d.setActiveLayer(dxfLayerId); - - for (let i = 0; i < layer.objects.length; ++i) { - const obj = layer.objects[i]; - console.debug("exporting object", obj); - - if (obj._class === T.POINT) { - d.drawPoint(obj.x, obj.y); - } else if (obj._class === T.SEGMENT) { - d.drawLine(obj.a.x, obj.a.y, obj.b.x, obj.b.y); - } else if (obj._class === T.ARC) { - d.drawArc( - obj.c.x, - obj.c.y, - obj.r.get(), - (obj.getStartAngle() / DEG_RAD), - (obj.getEndAngle() / DEG_RAD)); - } else if (obj._class === T.CIRCLE) { - d.drawCircle(obj.c.x, obj.c.y, obj.r.get()); - } else if (obj._class === T.ELLIPSE) { - const majorX = Math.cos(obj.rotation) * obj.radiusX; - const majorY = Math.sin(obj.rotation) * obj.radiusX; - d.drawEllipse(obj.centerX, obj.centerY, majorX, majorY, obj.radiusY / obj.radiusX, 0, 2 * Math.PI); - } else if (obj._class === T.BEZIER) { - d.drawSpline([ - [obj.p0.x, obj.p0.y], - [obj.p1.x, obj.p1.y], - [obj.p2.x, obj.p2.y], - [obj.p3.x, obj.p3.y], - ], 3) - } else if (obj._class === T.LABEL) { - const m = obj.assignedObject.labelCenter; - if (!m) { - return; - } - const height = obj.textHelper.textMetrics.height; - const h = obj.textHelper.textMetrics.width/2; - const lx = m.x - h + obj.offsetX; - const ly = m.y + obj.marginOffset + obj.offsetY; - - d.drawText(lx, ly, height, 0, obj.text); - - } else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) { - } - } - } - //console.log(d.toDxfString()); - d.generateAutocadExtras(); - return d.toDxfString(); + return dxf.stringify(); } } @@ -463,7 +530,8 @@ function _format(str, args) { if (args.length == 0) return str; let i = 0; return str.replace(/\$/g, function () { - if (args === undefined || args[i] === undefined) throw "format arguments mismatch"; + if (args === undefined || args[i] === undefined) + throw 'format arguments mismatch'; let val = args[i]; if (typeof val === 'number') val = val.toPrecision(); i++; @@ -473,34 +541,46 @@ function _format(str, args) { /** @constructor */ function PrettyColors() { - const colors = ["#000000", "#00008B", "#006400", "#8B0000", "#FF8C00", "#E9967A"]; + const colors = [ + '#000000', + '#00008B', + '#006400', + '#8B0000', + '#FF8C00', + '#E9967A', + ]; let colIdx = 0; this.next = function () { return colors[colIdx++ % colors.length]; - } + }; } /** @constructor */ function TextBuilder() { - this.data = ""; + this.data = ''; this.fline = function (chunk, args) { - this.data += _format(chunk, args) + "\n" + this.data += _format(chunk, args) + '\n'; }; this.line = function (chunk) { - this.data += chunk + "\n" + this.data += chunk + '\n'; }; this.number = function (n) { - this.data += n.toPrecision() + this.data += n.toPrecision(); }; this.numberln = function (n) { - this.number(n) - this.data += "\n" - } + this.number(n); + this.data += '\n'; + }; } /** @constructor */ function BBox() { - const bbox = [Number.MAX_VALUE, Number.MAX_VALUE, - Number.MAX_VALUE, - Number.MAX_VALUE]; + const bbox = [ + Number.MAX_VALUE, + Number.MAX_VALUE, + -Number.MAX_VALUE, + -Number.MAX_VALUE, + ]; const T = SketchTypes; @@ -511,25 +591,29 @@ function BBox() { }; this.check = function (obj) { - if (obj._class === T.SEGMENT) { + if (obj.TYPE === T.SEGMENT) { this.checkBounds(obj.a.x, obj.a.y); this.checkBounds(obj.b.x, obj.b.y); - } else if (obj._class === T.POINT) { + } else if (obj.TYPE === T.POINT) { this.checkBounds(obj.x, obj.y); - } else if (obj._class === T.ARC) { + } else if (obj.TYPE === T.ARC) { this.checkCircBounds(obj.c.x, obj.c.y, obj.r.get()); - } else if (obj._class === T.CIRCLE) { + } else if (obj.TYPE === T.CIRCLE) { this.checkCircBounds(obj.c.x, obj.c.y, obj.r.get()); - } else if (obj._class === T.ELLIPSE || obj._class === T.ELL_ARC) { - this.checkCircBounds(obj.centerX, obj.centerY, Math.max(obj.radiusX, obj.radiusY)); + } else if (obj.TYPE === T.ELLIPSE || obj.TYPE === T.ELL_ARC) { + this.checkCircBounds( + obj.centerX, + obj.centerY, + Math.max(obj.radiusX, obj.radiusY) + ); } else if (obj) { - obj.accept((o) => { - if (o._class == T.POINT) { + obj.accept(o => { + if (o.TYPE == T.POINT) { this.checkBounds(o.x, o.y); } return true; }); - // } else if (obj._class === T.DIM || obj._class === T.HDIM || obj._class === T.VDIM) { + // } else if (obj.TYPE === T.DIM || obj.TYPE === T.HDIM || obj.TYPE === T.VDIM) { } }; @@ -569,4 +653,4 @@ function BBox() { this.bbox = bbox; } -export { BBox }; \ No newline at end of file +export { BBox }; diff --git a/web/app/sketcher/shapes/sketch-types.js b/web/app/sketcher/shapes/sketch-types.js index ca641b10..d09cacd9 100644 --- a/web/app/sketcher/shapes/sketch-types.js +++ b/web/app/sketcher/shapes/sketch-types.js @@ -1,16 +1,34 @@ export const SketchTypes = { - POINT : 'TCAD.TWO.EndPoint', - SEGMENT : 'TCAD.TWO.Segment', - ARC : 'TCAD.TWO.Arc', - CIRCLE : 'TCAD.TWO.Circle', - ELLIPSE : 'TCAD.TWO.Ellipse', - ELL_ARC : 'TCAD.TWO.EllipticalArc', - BEZIER : 'TCAD.TWO.BezierCurve', - NURBS : 'TCAD.TWO.NurbsObject', - DIM : 'TCAD.TWO.Dimension', - HDIM : 'TCAD.TWO.HDimension', - VDIM : 'TCAD.TWO.VDimension', - DDIM : 'TCAD.TWO.DiameterDimension', - ANGLE_BW : 'TCAD.TWO.AngleBetweenDimension', - LABEL : 'TCAD.TWO.Label', -}; \ No newline at end of file + POINT: 'TCAD.TWO.EndPoint', + SEGMENT: 'TCAD.TWO.Segment', + ARC: 'TCAD.TWO.Arc', + CIRCLE: 'TCAD.TWO.Circle', + ELLIPSE: 'TCAD.TWO.Ellipse', + ELL_ARC: 'TCAD.TWO.EllipticalArc', + BEZIER: 'TCAD.TWO.BezierCurve', + NURBS: 'TCAD.TWO.NurbsObject', + DIM: 'TCAD.TWO.Dimension', + HDIM: 'TCAD.TWO.HDimension', + VDIM: 'TCAD.TWO.VDimension', + DDIM: 'TCAD.TWO.DiameterDimension', + ANGLE_BW: 'TCAD.TWO.AngleBetweenDimension', + LABEL: 'TCAD.TWO.Label', +}; + +// This for using property TYPE because _class was marked deprecated. +export const ShapesTypes = { + POINT: 'Point', + SEGMENT: 'Segment', + ARC: 'Arc', + CIRCLE: 'Circle', + ELLIPSE: 'Ellipse', + ELL_ARC: 'EllipticalArc', + BEZIER: 'BezierCurve', + NURBS: 'NurbsObject', + DIM: 'Dimension', + HDIM: 'HDimension', + VDIM: 'VDimension', + DDIM: 'DiameterDimension', + ANGLE_BW: 'AngleBetweenDimension', + LABEL: 'Label', +};