Adopt @tarikjabiri/dxf as dxf writer.

This commit is contained in:
EL JABIRI Tarik 2022-08-19 00:21:07 +01:00 committed by Val Erastov
parent 05e8f5f2df
commit 3b3397063d
4 changed files with 283 additions and 180 deletions

3
.gitignore vendored
View file

@ -13,4 +13,5 @@
/node_modules
/dist
/.vs
/web/docs/
/web/docs/
pnpm-lock.yaml

View file

@ -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",

View file

@ -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<SketchObject>[] = [];
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('<g id="$" fill="$" stroke="$" stroke-width="$">', [layer.name, "none", color, '2']);
out.fline('<g id="$" fill="$" stroke="$" stroke-width="$">', [
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('<line x1="$" y1="$" x2="$" y2="$" />', [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('<line x1="$" y1="$" x2="$" y2="$" />', [
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('<path d="M $ $ A $ $ 0 $ $ $ $" />', [obj.a.x, obj.a.y, r, r, dir, 1, obj.b.x, obj.b.y]);
} else if (obj._class === T.CIRCLE) {
out.fline('<circle cx="$" cy="$" r="$" />', [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('<path d="M $ $ A $ $ 0 $ $ $ $" />', [
obj.a.x,
obj.a.y,
r,
r,
dir,
1,
obj.b.x,
obj.b.y,
]);
} else if (this.isCircle(obj)) {
out.fline('<circle cx="$" cy="$" r="$" />', [
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('</g>');
}
bbox.inc(20);
return _format("<svg viewBox='$ $ $ $'>\n", bbox.bbox) + out.data + "</svg>"
return (
_format("<svg viewBox='$ $ $ $'>\n", bbox.bbox) + out.data + '</svg>'
);
}
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 };
export { BBox };

View file

@ -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',
};
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',
};