feat: add dimensions in svg export

This commit is contained in:
EL JABIRI Tarik 2023-12-10 20:31:31 +01:00
parent a54a784e4b
commit f8ae485ecf
7 changed files with 363 additions and 286 deletions

View file

@ -1,7 +0,0 @@
export default function(data, fileName) {
const link = document.getElementById("downloader");
link.href = "data:application/octet-stream;charset=utf-8;base64," + btoa(data);
link.download = fileName;
link.click();
}

View file

@ -0,0 +1,5 @@
import { saveAs } from "file-saver";
export default function (data: string, fileName: string) {
saveAs(new File([data], fileName, { type: "text/plain;charset=utf-8" }));
}

154
package-lock.json generated
View file

@ -11,7 +11,7 @@
"dependencies": {
"@dxfjs/parser": "^0.2.0",
"@react-icons/all-files": "^4.1.0",
"@tarikjabiri/dxf": "^2.8.9",
"@tarikjabiri/dxf": "^3.0.0-alpha.11",
"@tauri-apps/api": "^1.4.0",
"@types/three": "^0.146.0",
"browser-xml2js": "^0.4.19",
@ -19,6 +19,7 @@
"clipper-lib": "^6.4.2",
"core-js": "^3.32.2",
"earcut": "^2.2.4",
"file-saver": "^2.0.5",
"font-awesome": "4.7.0",
"immer": "^9.0.21",
"jsketcher-occ-engine": "1.0.1-f505cdcb9f37685cdadb937bc3b11b9b75f9267c",
@ -53,6 +54,7 @@
"@babel/preset-stage-2": "^7.8.3",
"@babel/preset-typescript": "^7.22.15",
"@tauri-apps/cli": "^1.4.0",
"@types/file-saver": "^2.0.7",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@ -79,6 +81,7 @@
"less-loader": "^11.1.3",
"raw-loader": "^4.0.2",
"style-loader": "^3.3.3",
"ts-loader": "^9.5.1",
"typescript": "^4.9.5",
"url-loader": "^4.1.1",
"wait-on": "^6.0.1",
@ -2451,9 +2454,9 @@
"dev": true
},
"node_modules/@tarikjabiri/dxf": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@tarikjabiri/dxf/-/dxf-2.8.9.tgz",
"integrity": "sha512-ckBkEsWdlFTvqm2ARm23xX3EVouYsnNuZWS7P/oec1WCGTjzdd9jhmtQqyhh7TQSvQo4HunbDv4Vxiv07/8zsQ==",
"version": "3.0.0-alpha.11",
"resolved": "https://registry.npmjs.org/@tarikjabiri/dxf/-/dxf-3.0.0-alpha.11.tgz",
"integrity": "sha512-YSn/wi+sYZ+bFfsImmwu9vLa0QpAxnRgpwo31TPjGUC4CFP4MhsynR/iCcPHvjWrnFrFmT3vZ4oMM8en3tUbcw==",
"engines": {
"node": ">=16",
"pnpm": ">=8"
@ -2752,6 +2755,12 @@
"@types/send": "*"
}
},
"node_modules/@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true
},
"node_modules/@types/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz",
@ -6641,6 +6650,11 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/file-sync-cmp": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz",
@ -11860,6 +11874,138 @@
"tree-kill": "cli.js"
}
},
"node_modules/ts-loader": {
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
"integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0",
"enhanced-resolve": "^5.0.0",
"micromatch": "^4.0.0",
"semver": "^7.3.4",
"source-map": "^0.7.4"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"typescript": "*",
"webpack": "^5.0.0"
}
},
"node_modules/ts-loader/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ts-loader/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ts-loader/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/ts-loader/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/ts-loader/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ts-loader/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-loader/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-loader/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/ts-loader/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ts-loader/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",

View file

@ -44,6 +44,7 @@
"@babel/preset-stage-2": "^7.8.3",
"@babel/preset-typescript": "^7.22.15",
"@tauri-apps/cli": "^1.4.0",
"@types/file-saver": "^2.0.7",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@ -70,6 +71,7 @@
"less-loader": "^11.1.3",
"raw-loader": "^4.0.2",
"style-loader": "^3.3.3",
"ts-loader": "^9.5.1",
"typescript": "^4.9.5",
"url-loader": "^4.1.1",
"wait-on": "^6.0.1",
@ -80,7 +82,7 @@
"dependencies": {
"@dxfjs/parser": "^0.2.0",
"@react-icons/all-files": "^4.1.0",
"@tarikjabiri/dxf": "^2.8.9",
"@tarikjabiri/dxf": "^3.0.0-alpha.11",
"@tauri-apps/api": "^1.4.0",
"@types/three": "^0.146.0",
"browser-xml2js": "^0.4.19",
@ -88,6 +90,7 @@
"clipper-lib": "^6.4.2",
"core-js": "^3.32.2",
"earcut": "^2.2.4",
"file-saver": "^2.0.5",
"font-awesome": "4.7.0",
"immer": "^9.0.21",
"jsketcher-occ-engine": "1.0.1-f505cdcb9f37685cdadb937bc3b11b9b75f9267c",

View file

@ -1,7 +1,7 @@
import Vector, {AXIS, ORIGIN} from 'math/vector';
import {RiCamera2Line} from "react-icons/ri";
import {ViewMode} from "cad/scene/viewer";
import {GiCube, HiCube, HiOutlineCube} from "react-icons/all";
import {GiCube, HiCube, HiOutlineCube} from "react-icons";
const NEG_X = AXIS.X.negate();
const NEG_Y = AXIS.Y.negate();
@ -21,7 +21,7 @@ function faceAt(shells, shell, pos) {
let i = shellIndex;
do {
i = (i + 1) % shells.length;
shell = shells[i];
shell = shells[i];
} while(shellIndex !== i && shell.faces.length === 0);
return shell.faces[0];
} else if (pos < 0) {
@ -32,7 +32,7 @@ function faceAt(shells, shell, pos) {
} while(shellIndex !== i && shell.faces.length === 0);
return shell.faces[shell.faces.length - 1];
} else {
return shell.faces[pos];
return shell.faces[pos];
}
}
@ -41,7 +41,7 @@ function getCurrentSelectedOrFirstFace(ctx) {
if (!face) {
for (const shell of ctx.services.cadRegistry.shells) {
if (shell.faces.length !== 0) {
return shell.faces[0];
return shell.faces[0];
}
}
}
@ -62,7 +62,7 @@ export default [
ctx.services.viewer.zoomOut();
ctx.services.viewer.requestRender();
}
},
},
{
id: 'LookAtFace',
appearance: {

View file

@ -10,12 +10,12 @@ import {
Parser,
PointEntity,
PolylineEntity,
SplineEntity
} from "@dxfjs/parser";
import { Colors, DLine, DxfWriter, point3d, SplineArgs_t, SplineFlags, Units, vec3_t } from '@tarikjabiri/dxf';
SplineEntity,
} from '@dxfjs/parser';
import { dline, Writer, point, SplineFlags, Units } from '@tarikjabiri/dxf';
import { SketchFormat_V3 } from './io';
import { Arc, SketchArcSerializationData } from './shapes/arc';
import { BezierCurve } from "./shapes/bezier-curve";
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';
@ -24,15 +24,16 @@ import { EndPoint, SketchPointSerializationData } from './shapes/point';
import { Segment, SketchSegmentSerializationData } from './shapes/segment';
import { SketchObject, SketchObjectSerializationData } from './shapes/sketch-object';
import { Layer } from './viewer2d';
import { calculateAngle, linep } from '@tarikjabiri/dxf/lib/helpers';
interface IPoint {
x: number,
y: number
x: number;
y: number;
}
interface SketchCircleSerializationData extends SketchObjectSerializationData {
c: SketchPointSerializationData;
r: number
r: number;
}
interface SketchEllipseSerializationData extends SketchObjectSerializationData {
@ -48,34 +49,34 @@ interface ITransform {
origin: IPoint;
}
const {PI, cos, sin, atan2} = Math
const { PI, cos, sin, atan2 } = Math;
export function deg(angle: number): number {
return (angle * 180) / PI
return (angle * 180) / PI;
}
export function rad(angle: number): number {
return (angle * PI) / 180
return (angle * PI) / 180;
}
function polar(origin: IPoint, angle: number, radius: number): IPoint {
return {
x: origin.x + radius * cos(angle),
y: origin.y + radius * sin(angle)
}
y: origin.y + radius * sin(angle),
};
}
function angle(fp: IPoint, sp: IPoint) {
let angle = Math.atan2(sp.y - fp.y, sp.x - fp.x)
if(angle < 0) angle += 2 * Math.PI
return angle
let angle = Math.atan2(sp.y - fp.y, sp.x - fp.x);
if (angle < 0) angle += 2 * Math.PI;
return angle;
}
function translate(p: IPoint, t: IPoint): IPoint {
return {
x: p.x + t.x,
y: p.y + t.y,
}
};
}
function rotatePoint(p: IPoint, t: ITransform): IPoint {
@ -84,8 +85,8 @@ function rotatePoint(p: IPoint, t: ITransform): IPoint {
const oy = p.y - t.origin.y;
return {
x: t.origin.x + (ox * cos(t.rotation) - oy * sin(t.rotation)),
y: t.origin.y + (ox * sin(t.rotation) + oy * cos(t.rotation))
}
y: t.origin.y + (ox * sin(t.rotation) + oy * cos(t.rotation)),
};
}
function applyTransformPoint(p: IPoint, t: ITransform) {
@ -93,24 +94,42 @@ function applyTransformPoint(p: IPoint, t: ITransform) {
}
export class DxfWriterAdapter {
writer: DxfWriter;
writer: Writer;
get modelSpace() {
return this.writer.document.modelSpace;
}
get tables() {
return this.writer.document.tables;
}
get bbox() {
return this.writer.document.modelSpace.bbox();
}
get renderer() {
return this.writer.document.renderer;
}
constructor() {
this.writer = new DxfWriter();
this.writer.setUnits(Units.Millimeters);
this.writer = new Writer();
this.writer.document.setUnits(Units.Millimeters);
const header = this.writer.document.header;
// 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
header.add('$DIMTXT').add(40, 10); // The text height
header.add('$DIMASZ').add(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
header.add('$DIMDEC').add(70, 2); // Number of precision places displayed
header.add('$DIMTIH').add(70, 0); // Text inside horizontal if nonzero
header.add('$DIMTOH').add(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
header.add('$DIMTIX').add(70, 0); // Force text inside extensions if nonzero
header.add('$DIMATFIT').add(70, 0); // Controls dimension text and arrow placement
// For more customization
// this.writer.setVariable('$DIMEXE', { 40: 10 }); // Extension line extension
@ -121,50 +140,54 @@ export class DxfWriterAdapter {
}
private _point(shape: EndPoint) {
this.writer.addPoint(shape.x, shape.y, 0);
this.modelSpace.addPoint(shape);
}
private _segment(shape: Segment) {
this.writer.addLine(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y));
this.modelSpace.addLine({
start: point(shape.a.x, shape.a.y),
end: point(shape.b.x, shape.b.y),
});
}
private _arc(shape: Arc) {
this.writer.addArc(
point3d(shape.c.x, shape.c.y),
shape.r.get(),
deg(shape.getStartAngle()),
deg(shape.getEndAngle())
);
this.modelSpace.addArc({
center: point(shape.c.x, shape.c.y),
radius: shape.r.get(),
startAngle: deg(shape.getStartAngle()),
endAngle: deg(shape.getEndAngle()),
});
}
private _circle(shape: Circle) {
this.writer.addCircle(point3d(shape.c.x, shape.c.y), shape.r.get());
this.modelSpace.addCircle({
center: point(shape.c.x, shape.c.y),
radius: 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
);
this.modelSpace.addEllipse({
center: point(shape.centerX, shape.centerY),
endpoint: point(majorX, majorY),
ratio: shape.radiusY / shape.radiusX,
start: 0,
end: 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,
this.modelSpace.addSpline({
controls: [
point(shape.p0.x, shape.p0.y),
point(shape.p1.x, shape.p1.y),
point(shape.p2.x, shape.p2.y),
point(shape.p3.x, shape.p3.y),
],
flags: SplineFlags.Periodic,
};
this.writer.addSpline(splineArgs);
});
}
private _label(shape: Label) {
@ -174,22 +197,39 @@ export class DxfWriterAdapter {
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,
this.modelSpace.addText({
firstAlignmentPoint: point(lx, ly),
height,
value: shape.text,
});
}
private _vdim(shape: VDimension) {
const dim = this.modelSpace.addLinearDim({
start: point(shape.a.x, shape.a.y),
end: point(shape.b.x, shape.b.y),
angle: 90,
offset: shape.offset,
});
this.renderer.addLinear(dim);
}
private _hdim(shape: HDimension) {
this.writer.addLinearDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: -shape.offset });
const dim = this.modelSpace.addLinearDim({
start: point(shape.a.x, shape.a.y),
end: point(shape.b.x, shape.b.y),
offset: shape.offset,
});
this.renderer.addLinear(dim);
}
private _linearDim(shape: LinearDimension) {
this.writer.addAlignedDim(point3d(shape.a.x, shape.a.y), point3d(shape.b.x, shape.b.y), { offset: shape.offset });
const aligned = this.modelSpace.addAlignedDim({
start: point(shape.a.x, shape.a.y),
end: point(shape.b.x, shape.b.y),
offset: -shape.offset,
});
this.renderer.addAligned(aligned);
}
private _ddim(shape: DiameterDimension) {
@ -197,39 +237,36 @@ export class DxfWriterAdapter {
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));
const dim = this.modelSpace.addRadialDim({
first: point(x, y),
definition: point(shape.obj.c.x, shape.obj.c.y),
leaderLength: 0,
});
this.renderer.addRadial(dim);
}
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));
dline(point(shape.a.a.x, shape.a.a.y), point(shape.a.b.x, shape.a.b.y));
const firstLine = dline(point(shape.b.a.x, shape.b.a.y), point(shape.b.b.x, shape.b.b.y));
const secondLine = dline(point(shape.a.a.x, shape.a.a.y), point(shape.a.b.x, shape.a.b.y));
const fline = linep(firstLine.start, firstLine.end);
const sline = linep(secondLine.start, secondLine.end);
const intersection = fline.intersect(sline);
const angle = calculateAngle(firstLine.start, firstLine.end);
const p = polar(intersection, angle, shape.offset);
const angular = this.modelSpace.addAngularLinesDim({
firstLine,
secondLine,
positionArc: point(p.x, p.y),
});
this.renderer.addAngularLines(angular);
}
export(layers: Layer<SketchObject>[]) {
layers.forEach(layer => {
// this will prevent addLayer from throwing.
if (!this.writer.layer(layer.name))
this.writer.addLayer(layer.name, Colors.Black);
this.writer.setCurrentLayerName(layer.name);
const found = this.tables.layer.get(layer.name);
if (found == null) this.tables.addLayer(layer);
this.modelSpace.currentLayerName = layer.name;
layer.objects.forEach(shape => {
if (shape instanceof EndPoint) this._point(shape);
@ -249,41 +286,51 @@ export class DxfWriterAdapter {
}
stringify(): string {
// reset the current layer to 0, because its preserved in the dxf.
this.writer.setZeroLayerAsCurrent();
this.modelSpace.currentLayerName = this.tables.zeroLayer.name;
return this.writer.stringify();
}
}
export class DxfParserAdapter {
private static _seed = 0; // Used as ids for the shapes.
private _seed = 0; // Used as ids for the shapes.
private _createSketchObject(type: string, data: object) {
return {
id: (DxfParserAdapter._seed++).toString(),
type, role: null, stage: 0, data
}
id: (this._seed++).toString(),
type,
role: null,
stage: 0,
data,
};
}
private _createSketchFormat(obj: DxfGlobalObject): SketchFormat_V3 {
DxfParserAdapter._seed = 0;
const sketch: SketchFormat_V3 = {
version: 3, objects: [], dimensions: [], labels: [],
stages: [], constants: null, metadata: {}
version: 3,
objects: [],
dimensions: [],
labels: [],
stages: [],
constants: null,
metadata: {},
};
const transform: ITransform = {
translation: { x: 0, y: 0 },
rotation: 0,
origin: { x: 0, y: 0 }
}
origin: { x: 0, y: 0 },
};
this.handleEntities(obj.entities, sketch, transform)
this.handleEntities(obj.entities, sketch, transform);
obj.entities.inserts.forEach(i => this._insert(obj.blocks, i, sketch));
return sketch;
}
private handleEntities(entities: Omit<DxfGlobalObject["entities"], "inserts">, sketch: SketchFormat_V3, t: ITransform) {
private handleEntities(
entities: Omit<DxfGlobalObject['entities'], 'inserts'>,
sketch: SketchFormat_V3,
t: ITransform
) {
entities.arcs.forEach(a => sketch.objects.push(this._arc(a, t)));
entities.circles.forEach(c => sketch.objects.push(this._circle(c, t)));
entities.ellipses.forEach(e => sketch.objects.push(this._ellipse(e, t)));
@ -296,18 +343,18 @@ export class DxfParserAdapter {
private _insert(blocks: Block[], i: InsertEntity, sketch: SketchFormat_V3) {
const block = blocks.find(block => {
return block.name === i.blockName || block.name2 === i.blockName
return block.name === i.blockName || block.name2 === i.blockName;
});
if(block) {
if (block) {
const transform: ITransform = {
translation: {
x: block.basePointX + i.x,
y: block.basePointY + i.y
y: block.basePointY + i.y,
},
rotation: rad(i.rotation ?? 0),
origin: { x: i.x, y: i.y },
}
};
this.handleEntities(block.entities, sketch, transform);
block.entities.inserts.forEach(i => this._insert(blocks, i, sketch));
}
@ -315,16 +362,16 @@ export class DxfParserAdapter {
private _spline(s: SplineEntity, t: ITransform) {
const objects = [];
for (let i = 0; i < s.controlPoints.length;) {
for (let i = 0; i < s.controlPoints.length; ) {
const p1 = s.controlPoints[i];
const p2 = s.controlPoints[++i];
const p3 = s.controlPoints[++i];
const p4 = s.controlPoints[++i];
if(p1 && p2 && p3 && p4) {
if(p1.x === p2.x && p3.x === p4.x && p1.y === p2.y && p3.y === p4.y) {
if (p1 && p2 && p3 && p4) {
if (p1.x === p2.x && p3.x === p4.x && p1.y === p2.y && p3.y === p4.y) {
const data: SketchSegmentSerializationData = {
a: applyTransformPoint({ x: p1.x, y: p1.y }, t),
b: applyTransformPoint({ x: p4.x, y: p4.y }, t)
b: applyTransformPoint({ x: p4.x, y: p4.y }, t),
};
objects.push(this._createSketchObject(Segment.prototype.TYPE, data));
} else {
@ -332,7 +379,7 @@ export class DxfParserAdapter {
cp4: applyTransformPoint({ x: p1.x, y: p1.y }, t),
cp3: applyTransformPoint({ x: p2.x, y: p2.y }, t),
cp2: applyTransformPoint({ x: p3.x, y: p3.y }, t),
cp1: applyTransformPoint({ x: p4.x, y: p4.y }, t)
cp1: applyTransformPoint({ x: p4.x, y: p4.y }, t),
};
objects.push(this._createSketchObject(BezierCurve.prototype.TYPE, data));
}
@ -347,28 +394,28 @@ export class DxfParserAdapter {
private _lwPolyline(p: LWPolylineEntity | PolylineEntity, t: ITransform) {
const objects = [];
for (let i = 0; i < p.vertices.length;) {
for (let i = 0; i < p.vertices.length; ) {
const curr = p.vertices[i];
let next = p.vertices[++i];
if(p.flag & 1 && !next) {
if (p.flag & 1 && !next) {
next = p.vertices[0];
}
if(curr && next) {
if(!curr.bulge || curr.bulge === 0) {
if (curr && next) {
if (!curr.bulge || curr.bulge === 0) {
const data: SketchSegmentSerializationData = {
a: applyTransformPoint({ x: curr.x, y: curr.y }, t),
b: applyTransformPoint({x: next.x, y: next.y}, t)
b: applyTransformPoint({ x: next.x, y: next.y }, t),
};
objects.push(this._createSketchObject(Segment.prototype.TYPE, data));
} else {
const beta = angle(curr, next);
const theta = 4 * Math.atan(curr.bulge);
const radius = (Math.hypot(curr.x - next.x, curr.y - next.y) / 2) / Math.sin(theta / 2);
const radius = Math.hypot(curr.x - next.x, curr.y - next.y) / 2 / Math.sin(theta / 2);
const center = polar(curr, beta + (Math.PI - theta) / 2, radius);
const data: SketchArcSerializationData = {
a: applyTransformPoint(curr.bulge > 0 ? {x: curr.x, y: curr.y} : {x: next.x, y: next.y}, t),
b: applyTransformPoint(curr.bulge > 0 ? {x: next.x, y: next.y} : {x: curr.x, y: curr.y}, t),
const data: SketchArcSerializationData = {
a: applyTransformPoint(curr.bulge > 0 ? { x: curr.x, y: curr.y } : { x: next.x, y: next.y }, t),
b: applyTransformPoint(curr.bulge > 0 ? { x: next.x, y: next.y } : { x: curr.x, y: curr.y }, t),
c: applyTransformPoint(center, t),
};
objects.push(this._createSketchObject(Arc.prototype.TYPE, data));
@ -379,8 +426,8 @@ export class DxfParserAdapter {
}
private _arc(a: ArcEntity, t: ITransform) {
const center: IPoint = {x: a.centerX, y: a.centerY};
const data: SketchArcSerializationData = {
const center: IPoint = { x: a.centerX, y: a.centerY };
const data: SketchArcSerializationData = {
a: applyTransformPoint(polar(center, rad(a.startAngle), a.radius), t),
b: applyTransformPoint(polar(center, rad(a.endAngle), a.radius), t),
c: applyTransformPoint(center, t),
@ -391,13 +438,13 @@ export class DxfParserAdapter {
private _circle(c: CircleEntity, t: ITransform) {
const data: SketchCircleSerializationData = {
c: applyTransformPoint({ x: c.centerX, y: c.centerY }, t),
r: c.radius
r: c.radius,
};
return this._createSketchObject(Circle.prototype.TYPE, data);
}
private _ellipse(e: EllipseEntity, t: ITransform) {
const c: IPoint = applyTransformPoint({x: e.centerX, y: e.centerY}, t);
const c: IPoint = applyTransformPoint({ x: e.centerX, y: e.centerY }, t);
let rot = atan2(e.majorAxisY, e.majorAxisX);
const rx = e.majorAxisX / cos(rot);
const ry = e.ratioOfMinorAxisToMajorAxis * rx;
@ -408,8 +455,8 @@ export class DxfParserAdapter {
private _segment(l: LineEntity, t: ITransform) {
const data: SketchSegmentSerializationData = {
a: applyTransformPoint({x: l.startX, y: l.startY}, t),
b: applyTransformPoint({x: l.endX, y: l.endY}, t)
a: applyTransformPoint({ x: l.startX, y: l.startY }, t),
b: applyTransformPoint({ x: l.endX, y: l.endY }, t),
};
return this._createSketchObject(Segment.prototype.TYPE, data);
}
@ -423,7 +470,7 @@ export class DxfParserAdapter {
return new Promise((resolve, reject) => {
new Parser()
.parse(dxfString)
.then(dxfObject => resolve(this._createSketchFormat(dxfObject)))
.then(dxfObject => resolve(this._createSketchFormat(dxfObject)))
.catch(error => reject(error));
});
}

View file

@ -15,19 +15,15 @@ import {
LinearDimension,
VDimension,
} from './shapes/dim';
import Vector from 'math/vector';
import exportTextData from 'gems/exportTextData';
import {
AlgNumConstraint,
ConstraintSerialization,
} from './constr/ANConstraints';
import { AlgNumConstraint, ConstraintSerialization } from './constr/ANConstraints';
import { SketchGenerator } from './generators/sketchGenerator';
import { BoundaryGeneratorSchema } from './generators/boundaryGenerator';
import { ShapesTypes } from './shapes/sketch-types';
import { SketchObject } from './shapes/sketch-object';
import { Label } from 'sketcher/shapes/label';
import { DxfWriterAdapter } from './dxf';
import { DEG_RAD } from 'math/commons';
import { svg } from '@tarikjabiri/dxf/lib/svg';
export interface SketchFormat_V3 {
version: number;
@ -76,11 +72,11 @@ export class IO {
viewer: Viewer;
constructor(viewer) {
constructor(viewer: Viewer) {
this.viewer = viewer;
}
loadSketch(sketchData) {
loadSketch(sketchData: string) {
return this._loadSketch(JSON.parse(sketchData));
}
@ -154,12 +150,7 @@ 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) {
@ -201,9 +192,7 @@ export class IO {
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) {
@ -235,10 +224,7 @@ export class IO {
BoundaryGeneratorSchema
);
this.viewer.parametricManager.addGeneratorToStage(
boundaryGenerator,
this.viewer.parametricManager.groundStage
);
this.viewer.parametricManager.addGeneratorToStage(boundaryGenerator, this.viewer.parametricManager.groundStage);
}
cleanUpData() {
@ -333,11 +319,7 @@ export class IO {
}
getWorkspaceToExport() {
return [
this.viewer.layers,
[this.viewer.labelLayer],
this.viewer.dimLayers
];
return [this.viewer.layers, [this.viewer.labelLayer], this.viewer.dimLayers];
}
getLayersToExport() {
@ -354,107 +336,26 @@ export class IO {
}
svgExport() {
const T = ShapesTypes;
const out = new TextBuilder();
const adapter = new DxfWriterAdapter();
adapter.export(this.getLayersToExport());
const bbox = new BBox();
// TODO: find a better way
const bbox = adapter.bbox;
const factor = (bbox.maxX - bbox.minX) / 125;
adapter.renderer.arrowSize *= factor;
adapter.renderer.extensionOffset *= factor;
adapter.renderer.extensionOverShoot *= factor;
adapter.renderer.textHeight *= factor;
const a = new Vector();
const b = new Vector();
adapter.renderer.draw();
const prettyColors = new PrettyColors();
const toExport = this.getLayersToExport();
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',
]);
for (let i = 0; i < layer.objects.length; ++i) {
const obj = layer.objects[i];
if (obj.TYPE !== T.POINT) bbox.check(obj);
if (obj instanceof Segment) {
out.fline('<line x1="$" y1="$" x2="$" y2="$" />', [
obj.a.x,
obj.a.y,
obj.b.x,
obj.b.y,
]);
} 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;
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 instanceof Circle) {
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) {
} else if (obj instanceof EllipticalArc) {
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;
out.fline('<path d="M $ $ A $ $ $ $ 1 $ $" />', [
obj.a.x,
obj.a.y,
obj.radiusX,
obj.radiusY,
obj.rotation / DEG_RAD,
dir,
obj.b.x,
obj.b.y
]);
} else if (obj instanceof Ellipse) {
out.fline('<ellipse cx="$" cy="$" rx="$" ry="$" transform="rotate($, $ $)" />', [
obj.c.x,
obj.c.y,
obj.radiusX,
obj.radiusY,
obj.rotation / DEG_RAD,
obj.c.x,
obj.c.y,
]);
} else if (obj instanceof BezierCurve) {
out.fline('<path d="M $ $ C $ $ $ $ $ $" />', [
obj.a.x,
obj.a.y,
obj.cp1.x,
obj.cp1.y,
obj.cp2.x,
obj.cp2.y,
obj.b.x,
obj.b.y
]);
}
}
out.line('</g>');
}
bbox.inc(20);
bbox.bbox[2] -= bbox.bbox[0];
bbox.bbox[3] -= bbox.bbox[1];
return (
_format("<svg viewBox='$ $ $ $' transform='scale(1, -1)'>\n", bbox.bbox) + out.data + '</svg>'
);
return svg(adapter.writer.document);
}
dxfExport() {
const adapter = new DxfWriterAdapter();
adapter.export(this.getLayersToExport());
adapter.renderer.draw();
return adapter.stringify();
}
}
@ -463,8 +364,7 @@ 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++;
@ -474,14 +374,7 @@ 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];
@ -508,19 +401,13 @@ function TextBuilder() {
/** @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 = ShapesTypes;
this.checkLayers = function (layers) {
for (let l = 0; l < layers.length; ++l)
for (let i = 0; i < layers[l].objects.length; ++i)
this.check(layers[l].objects[i]);
for (let i = 0; i < layers[l].objects.length; ++i) this.check(layers[l].objects[i]);
};
this.check = function (obj) {
@ -534,11 +421,7 @@ function BBox() {
} else if (obj.TYPE === T.CIRCLE) {
this.checkCircBounds(obj.c.x, obj.c.y, obj.r.get());
} else if (obj.TYPE === T.ELLIPSE || obj.TYPE === T.ELL_ARC) {
this.checkCircBounds(
obj.centerX,
obj.centerY,
Math.max(obj.radiusX, obj.radiusY)
);
this.checkCircBounds(obj.centerX, obj.centerY, Math.max(obj.radiusX, obj.radiusY));
} else if (obj) {
obj.accept(o => {
if (o.TYPE == T.POINT) {