Skip to content

Commit

Permalink
feat: add svg export (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarikjabiri authored Nov 26, 2023
1 parent d8a3b44 commit 2c02b10
Show file tree
Hide file tree
Showing 26 changed files with 824 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ coverage
**/*.dwl2
**/*.err
**/*.py
**/*.svg
19 changes: 13 additions & 6 deletions examples/dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ const green = writer.document.tables.addLayer({

modelSpace.currentLayerName = green.name;

modelSpace.addAlignedDim({
const aligned = modelSpace.addAlignedDim({
start: point(),
end: point(100, 100),
offset: 10,
dimStyleName: writer.document.tables.dimStyleStandard.options.name
});

writer.document.renderer.aligned(aligned);

modelSpace.addLinearDim({
start: point(),
end: point(100, 100),
offset: 10,
angle: 0,
dimStyleName: writer.document.tables.dimStyleStandard.options.name
});

writer.document.tables.dimStyleStandard.options.DIMTXT = 2.5;
Expand All @@ -36,14 +40,17 @@ const dim = modelSpace.addAngularLinesDim({
measurement: 0.785398,
dimStyleName: writer.document.tables.dimStyleStandard.options.name,
});

writer.document.renderer.angularLines(dim);
writer.document.tables.dimStyleStandard.reactors.add(330, dim.handle);

modelSpace.addAngularPointsDim({
center: point(100),
first: point(100, 100),
second: point(200),
middle: point(134.9199, 93.7049),
center: point(),
first: point(20, 10),
second: point(10, 20),
definition: point(14, 14),
middle: point(15, 15),
measurement: 0.6435011087932843,
dimStyleName: writer.document.tables.dimStyleStandard.options.name
});

modelSpace.addRadialDim({
Expand Down
1 change: 1 addition & 0 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ import "./paper-space";
import "./polyline";
import "./quick-start";
import "./rectangle";
import "./svg";
import "./table";
import "./text";
45 changes: 45 additions & 0 deletions examples/svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Colors, Writer, dline, point } from "@/index";
import { fileURLToPath, save } from "./utils";
import { svg } from "@/svg";

const writer = new Writer();
const modelSpace = writer.document.modelSpace;

const green = writer.document.tables.addLayer({
name: "Green",
colorNumber: Colors.Green,
});

const blue = writer.document.tables.addLayer({
name: "Blue",
colorNumber: Colors.Blue,
});

modelSpace.currentLayerName = green.name;

modelSpace.addLine({ start: point(), end: point(20, 20) });
modelSpace.addLine({ start: point(0, 2.5), end: point(20, 2.5) });
modelSpace.addLine({ start: point(0, 5), end: point(5, 10) });

modelSpace.currentLayerName = blue.name;

const aligned = modelSpace.addAlignedDim({
start: point(),
end: point(20, 20),
offset: 5,
});

writer.document.renderer.aligned(aligned);


const angular = modelSpace.addAngularLinesDim({
firstLine: dline(point(0, 2.5), point(20, 2.5)),
secondLine: dline(point(0, 5), point(5, 10)),
positionArc: point(15, 10),
middle: point(6.575676, 6.259268),
measurement: 0.785398,
});

writer.document.renderer.angularLines(angular);

save(svg(writer.document), fileURLToPath(import.meta.url), ".svg");
4 changes: 2 additions & 2 deletions examples/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { basename } from "path";

export { fileURLToPath } from "url";

export function save(content: string, filename: string) {
export function save(content: string, filename: string, ext = ".dxf") {
mkdirSync("output", { recursive: true });
const _name = `output/${basename(filename).replace(".ts", ".dxf")}`;
const _name = `output/${basename(filename).replace(".ts", ext)}`;
writeFileSync(_name, content);
}
5 changes: 5 additions & 0 deletions src/blocks/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export class Blocks implements Taggable, WithSeeder {
return b;
}

get(name?: string) {
if (name == null) return;
return this.blocks.find(b => b.name === name);
}

addPaperSpace() {
const name = `*Paper_Space${this.paperSpaceSeed++}`;
return this.addBlock({ name });
Expand Down
10 changes: 9 additions & 1 deletion src/document.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { BBox, Seeder, TagsManager, Units, point2d } from "./utils";
import { BlockOptions, Blocks } from "./blocks";
import { OmitBlockRecord, OmitSeeder, Stringifiable, WithSeeder } from "./types";
import {
OmitBlockRecord,
OmitSeeder,
Stringifiable,
WithSeeder,
} from "./types";
import { Classes } from "./classes";
import { DimensionRenderer } from "./entities";
import { Entities } from "./entities";
import { Header } from "./header";
import { Objects } from "./objects";
Expand All @@ -15,6 +21,7 @@ export class Document implements Stringifiable, WithSeeder {
readonly entities: Entities;
readonly tables: Tables;
readonly objects: Objects;
readonly renderer: DimensionRenderer;

units: number;

Expand All @@ -34,6 +41,7 @@ export class Document implements Stringifiable, WithSeeder {
this.blocks = new Blocks(this);
this.entities = new Entities(this);
this.objects = new Objects(this);
this.renderer = new DimensionRenderer(this);

this.units = Units.Unitless;
}
Expand Down
20 changes: 14 additions & 6 deletions src/entities/dimension/aligned.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dimension, DimensionOptions, DimensionType } from "./dimension";
import { TagsManager, angle, point, polar } from "@/utils";
import { Point3D } from "@/types";
import { linep } from "@/helpers";

export interface AlignedDimensionOptions extends DimensionOptions {
insertion?: Point3D;
Expand All @@ -14,13 +15,16 @@ export class AlignedDimension extends Dimension {
start: Point3D;
end: Point3D;

readonly offset: number;

constructor(options: AlignedDimensionOptions) {
super(options);
this.dimensionType = DimensionType.Aligned;
this.insertion = options.insertion;
this.start = options.start;
this.end = options.end;
this.offset(options.offset);
this.offset = options.offset ?? 0;
this._offset();
}

protected override tagifyChild(mg: TagsManager): void {
Expand All @@ -31,10 +35,14 @@ export class AlignedDimension extends Dimension {
mg.point(this.end, 4);
}

private offset(v?: number) {
if (v == null) return;
const { start, end } = this;
const middle = point((start.x + end.x) / 2, (start.y + end.y) / 2);
this.definition = polar(middle, angle(this.start, this.end) - 90, v);
private _offset() {
const { offset } = this;
if (offset == null) return;
const sign = Math.sign(this.offset);
const a = angle(this.start, this.end) + 90 * sign;
const start = polar(this.start, a, this.offset);
this.definition = polar(this.end, a, this.offset);
const middle = linep(start, this.definition).middle;
this.middle = point(middle.x, middle.y);
}
}
1 change: 1 addition & 0 deletions src/entities/dimension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./diameter";
export * from "./dimension";
export * from "./linear";
export * from "./radial";
export * from "./render";
49 changes: 49 additions & 0 deletions src/entities/dimension/render/arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Point2D, Point3D, WithSeeder } from "@/types";
import { Seeder, point, point2d } from "@/utils";
import { Solid } from "@/entities";
import { transform } from "@/helpers";

export interface DimensionArrowOptions extends WithSeeder {
size?: number;
rotation?: number;
position?: Point3D;
}

export function arrow(options: DimensionArrowOptions) {
return new DimensionArrow(options).entity();
}

export class DimensionArrow {
readonly seeder: Seeder;
size: number;
rotation: number;
position: Point3D;

constructor(options: DimensionArrowOptions) {
this.seeder = options.seeder;
this.size = options.size ?? 2.5;
this.rotation = options.rotation ?? 0;
this.position = options.position ?? point();
}

entity() {
const { size: s, seeder } = this;
const h = this.size / 3 / 2;
return new Solid({
seeder,
first: this.position,
second: this._transform(point2d(-s, -h)),
third: this._transform(point2d(-s, h)),
});
}

private _transform(target: Point2D) {
const result = transform({
target,
center: this.position,
angle: this.rotation,
translation: this.position,
});
return point(result.x, result.y);
}
}
2 changes: 2 additions & 0 deletions src/entities/dimension/render/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./arrow";
export * from "./renderer";
119 changes: 119 additions & 0 deletions src/entities/dimension/render/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { AlignedDimension, AngularLinesDimension, AttachmentPoint, arrow } from "@/entities";
import { ArcPrimitive, LinePrimitive, PI, calculateAngle, linep } from "@/helpers";
import { Block, Blocks } from "@/blocks";
import { Seeder, angle, deg, point, polar } from "@/utils";
import { Tables } from "@/tables";
import { WithSeeder } from "@/types";

function round(v: number, accuracy = 0.01) {
const EPSILON = Number.EPSILON || Math.pow(2, -52);
const temp = 1 / accuracy;
return Math.round((v + EPSILON) * temp) / temp;
}

export interface DimensionRendererOptions extends WithSeeder {
blocks: Blocks;
tables: Tables;
}

export class DimensionRenderer implements WithSeeder {
readonly seeder: Seeder;
readonly blocks: Blocks;
readonly tables: Tables;

private _seed: number;
private get _blockName() {
return `*D${++this._seed}`;
}

constructor(options: DimensionRendererOptions) {
this.seeder = options.seeder;
this.blocks = options.blocks;
this.tables = options.tables;
this._seed = 0;
}

aligned(dim: AlignedDimension) {
const { seeder } = this.blocks;
const rotation = calculateAngle(dim.start, dim.end);
const sign = Math.sign(dim.offset);
const block = this.blocks.addBlock({ name: this._blockName });
block.currentLayerName = dim.layerName || "0";
const angle = rotation + (PI / 2) * sign;
const start = polar(dim.start, deg(angle), dim.offset);
const end = polar(dim.end, deg(angle), dim.offset);
block.push(arrow({ seeder, rotation: rotation - PI, position: start }));
block.push(arrow({ seeder, rotation, position: end }));
linep(start, end).trimStart(2.5).trimEnd(2.5).write(block);
this._trimExpand(linep(dim.start, start), block);
this._trimExpand(linep(dim.end, end), block);
const layerName = this.tables.addDefpointsLayer();
block.addPoint({ ...dim.start, layerName });
block.addPoint({ ...dim.end, layerName });

const distance = round(linep(dim.start, dim.end).length);
const middle = linep(start, end).middle;

dim.textRotation = deg(rotation);
dim.measurement = distance;
dim.middle = polar(middle, 90 * sign, 1.25);
block.addMText({
insertionPoint: dim.middle,
height: 2.5,
value: distance.toString(),
rotation: deg(rotation),
attachmentPoint: AttachmentPoint.BottomCenter,
});

dim.blockName = block.name;
}

angularLines(dim: AngularLinesDimension) {
const { seeder } = this.blocks;
const block = this.blocks.addBlock({ name: this._blockName });
block.currentLayerName = dim.layerName || "0";
const fline = linep(dim.firstLine.start, dim.firstLine.end);
const sline = linep(dim.secondLine.start, dim.secondLine.end);
const intersection = fline.intersect(sline);
if (intersection == null) return;
const center = point(intersection.x, intersection.y);
const radius = linep(center, dim.positionArc).length;
const startAngle = angle(dim.firstLine.start, dim.firstLine.end);
const endAngle = angle(dim.secondLine.start, dim.secondLine.end);
const arc = new ArcPrimitive({ center, endAngle, startAngle, radius });
const tarc = arc.trimStart(2.5, true).trimEnd(2.5, true);
tarc.write(block);
block.push(arrow({
seeder,
rotation: calculateAngle(tarc.start, arc.start),
position: arc.start
}));
block.push(arrow({
seeder,
rotation: calculateAngle(tarc.end, arc.end),
position: arc.end
}));
dim.middle = polar(arc.middle, arc.middleAngle, 1.25);
block.addMText({
insertionPoint: dim.middle,
height: 2.5,
value: `${arc.angle.toFixed(2)}°`,
rotation: arc.middleAngle - 90,
attachmentPoint: AttachmentPoint.BottomCenter,
});

if (fline.length <= radius) {
this._trimExpand(linep(fline.end, arc.start), block);
}

if (sline.length <= radius) {
this._trimExpand(linep(sline.end, arc.end), block);
}

dim.blockName = block.name;
}

private _trimExpand(line: LinePrimitive, block: Block) {
line.trimStart(0.625).expandEnd(1.25).write(block);
}
}
1 change: 1 addition & 0 deletions src/entities/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export class EntitiesManager implements Taggable, WithSeeder {

push(entity?: Entity) {
if (entity == null) return;
if (entity.layerName == null) entity.layerName = this.currentLayerName;
this.entities.push(entity);
}

Expand Down
2 changes: 1 addition & 1 deletion src/helpers/angles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TOW_PI, periodic } from "@/helpers";
import { Point2D } from "@/index";
import { Point2D } from "@/types";

export function pdeg(value: number) {
return periodic(value, 0, 360);
Expand Down
Loading

0 comments on commit 2c02b10

Please sign in to comment.