From 322d47f80255d01b8bfcbaa110f0e790e91fa0d1 Mon Sep 17 00:00:00 2001 From: EL JABIRI Tarik Date: Tue, 7 Mar 2023 01:07:32 +0100 Subject: [PATCH] feat: add support for dxf blocks (#200) --- package.json | 2 +- web/app/sketcher/dxf.ts | 168 ++++++++++++++++++++++++++++++++-------- 2 files changed, 137 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index b0886a7f..6328d428 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/web/app/sketcher/dxf.ts b/web/app/sketcher/dxf.ts index 64f9ae55..7e48ab3c 100644 --- a/web/app/sketcher/dxf.ts +++ b/web/app/sketcher/dxf.ts @@ -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'; @@ -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 { @@ -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; @@ -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, 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]; @@ -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 { @@ -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)); } @@ -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); }