mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 00:16:32 +01:00
feat(dxf): add support for dimensions in dxf.
This commit is contained in:
parent
b4631baccb
commit
56c884ccfd
3 changed files with 186 additions and 118 deletions
|
|
@ -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",
|
||||
|
|
|
|||
178
web/app/sketcher/dxf.ts
Normal file
178
web/app/sketcher/dxf.ts
Normal file
|
|
@ -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<SketchObject>[]) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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('<line x1="$" y1="$" x2="$" y2="$" />', [
|
||||
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('<circle cx="$" cy="$" r="$" />', [
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue