Skip to content

Commit

Permalink
feat: add support for dxf blocks (xibyte#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarikjabiri authored Mar 7, 2023
1 parent 563ff9a commit 322d47f
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"@dxfjs/parser": "^0.0.3",
"@dxfjs/parser": "^0.2.0",
"@tarikjabiri/dxf": "^2.6.2",
"@types/three": "^0.146.0",
"browser-xml2js": "^0.4.19",
Expand Down
168 changes: 136 additions & 32 deletions web/app/sketcher/dxf.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { ArcEntity, CircleEntity, DxfGlobalObject, EllipseEntity, LineEntity, LWPolylineEntity, Parser, PointEntity } from '@dxfjs/parser';
import {
ArcEntity,
Block,
CircleEntity,
DxfGlobalObject,
EllipseEntity,
InsertEntity,
LineEntity,
LWPolylineEntity,
Parser,
PointEntity,
PolylineEntity,
SplineEntity
} from "@dxfjs/parser";
import { Colors, DLine, DxfWriter, point3d, SplineArgs_t, SplineFlags, Units, vec3_t } 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';
Expand All @@ -29,6 +42,12 @@ interface SketchEllipseSerializationData extends SketchObjectSerializationData {
rot: number;
}

interface ITransform {
translation: IPoint;
rotation: number;
origin: IPoint;
}

const {PI, cos, sin, atan2} = Math

export function deg(angle: number): number {
Expand All @@ -52,6 +71,27 @@ function angle(fp: IPoint, sp: IPoint) {
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 {
const { cos, sin } = Math;
const ox = p.x - t.origin.x;
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))
}
}

function applyTransformPoint(p: IPoint, t: ITransform) {
return rotatePoint(translate(p, t.translation), t);
}

export class DxfWriterAdapter {
writer: DxfWriter;

Expand Down Expand Up @@ -225,26 +265,87 @@ export class DxfParserAdapter {
}
}

private _createSketchFormat(dxfObject: DxfGlobalObject): SketchFormat_V3 {
private _createSketchFormat(obj: DxfGlobalObject): SketchFormat_V3 {
DxfParserAdapter._seed = 0;
const sketch: SketchFormat_V3 = {
version: 3, objects: [], dimensions: [], labels: [],
stages: [], constants: null, metadata: {}
};
const transform: ITransform = {
translation: { x: 0, y: 0 },
rotation: 0,
origin: { x: 0, y: 0 }
}

const {arcs, circles, ellipses, lines, points, lwPolylines} = dxfObject.entities

arcs.forEach(a => sketch.objects.push(this._arc(a)));
circles.forEach(c => sketch.objects.push(this._circle(c)));
ellipses.forEach(e => sketch.objects.push(this._ellipse(e)));
lines.forEach(l => sketch.objects.push(this._segment(l)));
points.forEach(p => sketch.objects.push(this._point(p)));
lwPolylines.forEach(p => sketch.objects.push(...this._lwPolyline(p)));
this.handleEntities(obj.entities, sketch, transform)
obj.entities.inserts.forEach(i => this._insert(obj.blocks, i, sketch));

return sketch;
}

private _lwPolyline(p: LWPolylineEntity) {
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)));
entities.lines.forEach(l => sketch.objects.push(this._segment(l, t)));
entities.points.forEach(p => sketch.objects.push(this._point(p, t)));
entities.lwPolylines.forEach(p => sketch.objects.push(...this._lwPolyline(p, t)));
entities.polylines.forEach(p => sketch.objects.push(...this._polyline(p, t)));
entities.splines.forEach(s => sketch.objects.push(...this._spline(s, t)));
}

private _insert(blocks: Block[], i: InsertEntity, sketch: SketchFormat_V3) {
const block = blocks.find(block => {
return block.name === i.blockName || block.name2 === i.blockName
});

if(block) {
const transform: ITransform = {
translation: {
x: block.basePointX + i.x,
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));
}
}

private _spline(s: SplineEntity, t: ITransform) {
const objects = [];
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) {
const data: SketchSegmentSerializationData = {
a: applyTransformPoint({ x: p1.x, y: p1.y }, t),
b: applyTransformPoint({ x: p4.x, y: p4.y }, t)
};
objects.push(this._createSketchObject(Segment.prototype.TYPE, data));
} else {
const data = {
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)
};
objects.push(this._createSketchObject(BezierCurve.prototype.TYPE, data));
}
}
}
return objects;
}

private _polyline(p: PolylineEntity, t: ITransform) {
return this._lwPolyline(p, t);
}

private _lwPolyline(p: LWPolylineEntity | PolylineEntity, t: ITransform) {
const objects = [];
for (let i = 0; i < p.vertices.length;) {
const curr = p.vertices[i];
Expand All @@ -256,8 +357,8 @@ export class DxfParserAdapter {
if(curr && next) {
if(!curr.bulge || curr.bulge === 0) {
const data: SketchSegmentSerializationData = {
a: {x: curr.x, y: curr.y},
b: {x: next.x, y: next.y}
a: applyTransformPoint({ x: curr.x, y: curr.y }, t),
b: applyTransformPoint({x: next.x, y: next.y}, t)
};
objects.push(this._createSketchObject(Segment.prototype.TYPE, data));
} else {
Expand All @@ -266,9 +367,9 @@ export class DxfParserAdapter {
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: curr.bulge > 0 ? {x: curr.x, y: curr.y} : {x: next.x, y: next.y},
b: curr.bulge > 0 ? {x: next.x, y: next.y} : {x: curr.x, y: curr.y},
c: center,
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));
}
Expand All @@ -277,41 +378,44 @@ export class DxfParserAdapter {
return objects;
}

private _arc(a: ArcEntity) {
private _arc(a: ArcEntity, t: ITransform) {
const center: IPoint = {x: a.centerX, y: a.centerY};
const data: SketchArcSerializationData = {
a: polar(center, rad(a.startAngle), a.radius),
b: polar(center, rad(a.endAngle), a.radius),
c: center,
a: applyTransformPoint(polar(center, rad(a.startAngle), a.radius), t),
b: applyTransformPoint(polar(center, rad(a.endAngle), a.radius), t),
c: applyTransformPoint(center, t),
};
return this._createSketchObject(Arc.prototype.TYPE, data);
}

private _circle(c: CircleEntity) {
const center: IPoint = {x: c.centerX, y: c.centerY};
const data: SketchCircleSerializationData = { c: center, r: c.radius };
private _circle(c: CircleEntity, t: ITransform) {
const data: SketchCircleSerializationData = {
c: applyTransformPoint({ x: c.centerX, y: c.centerY }, t),
r: c.radius
};
return this._createSketchObject(Circle.prototype.TYPE, data);
}

private _ellipse(e: EllipseEntity) {
const c: IPoint = {x: e.centerX, y: e.centerY};
const rot = atan2(e.majorAxisY, e.majorAxisX);
private _ellipse(e: EllipseEntity, t: ITransform) {
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;
rot += t.rotation;
const data: SketchEllipseSerializationData = { c, rx, ry, rot };
return this._createSketchObject(Ellipse.prototype.TYPE, data);
}

private _segment(l: LineEntity) {
private _segment(l: LineEntity, t: ITransform) {
const data: SketchSegmentSerializationData = {
a: {x: l.startX, y: l.startY},
b: {x: l.endX, y: l.endY}
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);
}

private _point(p: PointEntity) {
const data: SketchPointSerializationData = { x: p.x, y: p.y };
private _point(p: PointEntity, t: ITransform) {
const data: SketchPointSerializationData = applyTransformPoint(p, t);
return this._createSketchObject(EndPoint.prototype.TYPE, data);
}

Expand Down

0 comments on commit 322d47f

Please sign in to comment.