diff --git a/config/config.ts b/config/config.ts
new file mode 100644
index 00000000000..ffc07605908
--- /dev/null
+++ b/config/config.ts
@@ -0,0 +1,3 @@
+export var config = {
+ precision: 2
+}
diff --git a/index.ts b/index.ts
index 111f8b4d36b..011eec6d081 100644
--- a/index.ts
+++ b/index.ts
@@ -1,12 +1,18 @@
import { Pattern } from './lib/pattern'
import { Point } from './lib/point'
+import { Path } from './lib/path'
+import { config } from './config/config'
+import * as utils from './lib/utils'
import bezier from 'bezier-js'
var Freesewing = {
- version: '0.0.1',
- pattern: Pattern,
- point: Point,
- bezier
+ version: '1.0.1',
+ config,
+ pattern: Pattern,
+ point: Point,
+ path: Path,
+ utils,
+ bezier
}
export default Freesewing;
diff --git a/lib/attributes.ts b/lib/attributes.ts
new file mode 100644
index 00000000000..88c5b584c6e
--- /dev/null
+++ b/lib/attributes.ts
@@ -0,0 +1,20 @@
+export class Attributes {
+ list: {name: string, value: string}[] = [];
+
+ /** Adds an attribute */
+ add(name: string, value: string): Attributes {
+ this.list.push({name, value});
+
+ return this;
+ }
+
+ /** Returns SVG code for attributes */
+ render(): string {
+ let svg = '';
+ for (let a of this.list) {
+ svg += ` ${a.name}="${a.value}"`;
+ }
+
+ return svg;
+ }
+}
diff --git a/lib/part.ts b/lib/part.ts
index ebf99edf360..48d0eef2eb4 100644
--- a/lib/part.ts
+++ b/lib/part.ts
@@ -1,4 +1,6 @@
import { Point } from './point'
+import { Path } from './path'
+import { Attributes } from './attributes'
export class Part {
id: string;
@@ -6,12 +8,14 @@ export class Part {
points: {
[index: string]: Point;
}
+ paths: { [index: string]: Path; } = {};
+ attributes = new Attributes();
[propName: string]: any;
constructor(id: string) {
this.id = id;
this.render = (id.substr(0,1) === '_') ? false : true;
- this.points = {};
+ this.points = {'origin': new Point(0,0)};
return this;
}
diff --git a/lib/path.ts b/lib/path.ts
new file mode 100644
index 00000000000..cd01d67af24
--- /dev/null
+++ b/lib/path.ts
@@ -0,0 +1,78 @@
+import { Point } from './point'
+import { Attributes } from './attributes'
+
+
+export class Path {
+ constructor() {
+ return this;
+ }
+
+ ops: {
+ type: "move" | "line" | "curve" | "close";
+ to?: Point;
+ cp1?: Point;
+ cp2?: Point;
+ }[] = [];
+ attributes: Attributes = new Attributes();
+
+ /** Adds a move operation to Point to */
+ move(to: Point): Path {
+ this.ops.push({type: "move", to});
+
+ return this;
+ }
+
+ /** Adds a line operation to Point to */
+ line(to: Point): Path {
+ this.ops.push({type: "line", to});
+
+ return this;
+ }
+
+ /** Adds a line operation to Point to */
+ curve(cp1: Point, cp2: Point, to: Point): Path {
+ this.ops.push({type: "curve", cp1, cp2, to});
+
+ return this;
+ }
+
+ /** Adds a close operation */
+ close(): Path {
+ this.ops.push({type: "close"});
+
+ return this;
+ }
+
+ /** Returns SVG pathstring for this path */
+ asPathstring() {
+ let d = '';
+ for(let op of this.ops) {
+ switch (op.type) {
+ case 'move':
+ d += `M ${op.to!.x},${op.to!.y}`;
+ break;
+ case 'line':
+ d += ` L ${op.to!.x},${op.to!.y}`;
+ break;
+ case 'curve':
+ d += ` C ${op.cp1!.x},${op.cp1!.y} ${op.cp2!.x},${op.cp2!.y} ${op.to!.x},${op.to!.y}`;
+ break;
+ case 'close':
+ d += ' z';
+ break;
+ default:
+ throw `${op.type} is not a valid path command`;
+ break;
+ }
+ }
+
+ return d;
+ }
+
+ /** Returns SVG code for this path */
+ render(): string {
+ this.attributes.add('d', this.asPathstring());
+ return ``;
+
+ }
+}
diff --git a/lib/pattern.ts b/lib/pattern.ts
index 9611d68bbee..fdc29a05734 100644
--- a/lib/pattern.ts
+++ b/lib/pattern.ts
@@ -1,10 +1,13 @@
import { PatternConfig, PatternOption } from './types'
import { Part } from './part'
+import { Renderer } from './renderer'
import { Option } from './option'
export class Pattern {
config: PatternConfig;
- parts: {[propName: string]: Part};
+ parts: {
+ [index: string]: Part;
+ }
options: {[propName: string]: number};
constructor(config: PatternConfig) {
@@ -27,4 +30,9 @@ export class Pattern {
draft(config: object): void {
throw Error('You have to implement the draft() method in your Pattern instance.');
}
+
+ render(pattern: Pattern): string {
+ let r = new Renderer();
+ return r.render(pattern);
+ }
}
diff --git a/lib/point.ts b/lib/point.ts
index 7ed8d6683c0..008bbd6cb8e 100644
--- a/lib/point.ts
+++ b/lib/point.ts
@@ -1,12 +1,12 @@
import { round, rad2deg, deg2rad } from './utils';
-
const PRECISION = 2;
-// A poor man's rad2deg()
+
export class Point {
x: number;
y: number;
+
constructor(x: number, y: number) {
this.x = round(x);
this.y = round(y);
diff --git a/lib/renderer.ts b/lib/renderer.ts
new file mode 100644
index 00000000000..26e1519e5ad
--- /dev/null
+++ b/lib/renderer.ts
@@ -0,0 +1,96 @@
+import { Part } from './part'
+import { Path } from './path'
+import { Pattern } from './pattern'
+import { Attributes } from './attributes'
+
+export class Renderer {
+ svg = '';
+ readonly TAB = ' ';
+ tabs: number = 0;
+ freeId: number = 1;
+ openGroups: string[] = [];
+
+ constructor() {
+ return this;
+ }
+
+ /** Renders a draft object as SVG */
+ render(pattern: Pattern, final: boolean = false): string {
+ let svg = this.openGroup('draftContainer');
+ for (let partId in pattern.parts) {
+ let part = pattern.parts[partId];
+ if (part.render) {
+ svg += this.openGroup(part.id, part.attributes);
+ svg += this.renderPart(part);
+ svg += this.closeGroup();
+ }
+ }
+ svg += this.closeGroup();
+
+ return svg;
+ }
+
+ /** Returns SVG code for a Part object */
+ renderPart(part: Part): string {
+ let svg = '';
+ for (let pathId in part.paths) {
+ let path = part.paths[pathId];
+ if(path.render) svg += this.renderPath(path);
+ }
+ // includes
+ // text on path
+ // notes
+ // dimensions
+ // texts
+ // snippets
+
+ return svg;
+ }
+
+ /** Returns SVG code for a Path object */
+ renderPath(path: Path): string {
+ return `${this.nl()}`;
+ }
+
+
+ /** Returns SVG code to open a group */
+ protected openGroup(id: string, attributes?: Attributes): string {
+ let svg = this.nl()+this.nl();
+ svg += ``;
+ svg += this.nl();
+ svg += ``;
+ this.indent();
+ this.openGroups.push(id);
+
+ return svg;
+ }
+
+ /** Returns SVG code to close a group */
+ protected closeGroup(): string {
+ this.outdent();
+
+ return `${this.nl()}${this.nl()}`;
+ }
+
+ /** Returns a linebreak + identation */
+ protected nl(): string {
+ return "\n"+this.TAB;
+ }
+
+ /** Increases indentation by 1 */
+ protected indent(): void {
+ this.tabs += 1;
+ }
+
+ /** Decreases indentation by 1 */
+ protected outdent(): void {
+ this.tabs -= 1;
+ }
+
+ /** Returns an unused ID */
+ protected getUid() {
+ this.freeId += 1;
+
+ return this.freeId;
+ }
+}
diff --git a/lib/utils.ts b/lib/utils.ts
index adb0b5f9222..5c26a696aa2 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,3 +1,5 @@
+import { Point } from './point'
+
/** Rounds a value to PRECISION */
export function round(value: number): number {
return Math.round(value * 1e2) / 1e2;
@@ -13,3 +15,48 @@ export function deg2rad(degrees: number): number {
return degrees / 57.29577951308232;
}
+/** Find intersection of two (endless) lines */
+export function beamsCross(a1: Point, a2: Point, b1: Point, b2: Point): Point | false {
+ let slopeA = a1.slope(a2);
+ let slopeB = b1.slope(b2);
+ if(slopeA === slopeB) return false; // Parallel lines
+
+ if(a1.x === a2.x) return new Point(a1.x, slopeB * a1.x + (b1.y - (slopeB * b1.x))); // Vertical line A
+ else if(b1.x === b2.x) return new Point(b1.x, slopeA * b1.x + (a1.y - (slopeA * a1.x))); // Vertical line B
+ else {
+ // Swap points if line A or B goes from right to left
+ if(a1.x > a2.x) {
+ let tmp = a1.copy();
+ a1 = a2.copy();
+ a2 = tmp;
+ }
+ if(b1.x > b2.x) {
+ let tmp = b1.copy();
+ b1 = b2.copy();
+ b2 = tmp;
+ }
+ // Find y intercept
+ let iA = a1.y - (slopeA * a1.x);
+ let iB = b1.y - (slopeB * b1.x);
+
+ // Find intersection
+ let x = (iB - iA) / (slopeA - slopeB);
+ let y = slopeA * x *iA;
+
+ return new Point(x, y);
+ }
+}
+
+/** Find intersection of two line segments */
+export function linesCross(a1: Point, a2: Point, b1: Point, b2: Point): Point | false {
+ let p = beamsCross(a1,a2,b1,b2);
+ if(p) {
+ let lenA = a1.dist(a2);
+ let lenB = b1.dist(b2);
+ let lenC = a1.dist(p) + p.dist(a2);
+ let lenD = b1.dist(p) + p.dist(b2);
+ if (round(lenA) == round(lenC) && round(lenB) == round(lenD)) return p;
+ }
+ return false;
+}
+
diff --git a/tests/utils.test.js b/tests/utils.test.js
new file mode 100644
index 00000000000..36f480185d9
--- /dev/null
+++ b/tests/utils.test.js
@@ -0,0 +1,77 @@
+var expect = require('chai').expect;
+var Point = require('../dist/lib/point').Point;
+var utils = require('../dist/lib/utils');
+
+it('should round a value', () => {
+ expect(utils.round(1.2345)).to.equal(1.23);
+ expect(utils.round(-9.876)).to.equal(-9.88);
+ expect(utils.round(12)).to.equal(12);
+});
+
+it('should convert radians to degrees', () => {
+ expect(utils.rad2deg(Math.PI/2)).to.equal(90);
+ expect(utils.rad2deg(Math.PI)).to.equal(180);
+ expect(utils.rad2deg(Math.PI*2)).to.equal(360);
+});
+
+it('should convert degrees to radians', () => {
+ expect(utils.deg2rad(90)).to.equal(Math.PI/2);
+ expect(utils.deg2rad(180)).to.equal(Math.PI);
+ expect(utils.deg2rad(360)).to.equal(Math.PI*2);
+});
+
+it('should return a line intersection', () => {
+ let a = new Point(-10,-10);
+ let b = new Point(10,-10);
+ let c = new Point(10,10);
+ let d = new Point(-10,10);
+ let result = utils.beamsCross(a,c,b,d);
+ expect(result.x).to.equal(0);
+ expect(result.y).to.equal(0);
+ expect(utils.beamsCross(a,b,c,d)).to.be.false;
+
+ // Debug
+ let e = new Point(0,0);
+ let f = new Point(25,25);
+ let g = new Point(0,60);
+ let h = new Point(25,35);
+ // Should be 30,30, but is 30,0
+ console.log(utils.beamsCross(e,f,g,h));
+ //$p->newPoint(2,25,25);
+ //$p->newPoint(3,0,60);
+ //$p->newPoint(4,25,35);
+ //$this->assertFalse($p->linesCross(1,2,3,4));
+ //$this->assertFalse($p->beamsCross(1,3,2,4));
+ //
+ //$expect = new \Freesewing\Point();
+ //$expect->setX(30);
+ //$expect->setY(30);
+ //$this->assertEquals($p->beamsCross(1,2,3,4), $expect);
+ //
+ //$p->newPoint(4,10,-10);
+ //$expect->setX(7.5);
+ //$expect->setY(7.5);
+ //$this->assertEquals($p->linesCross(1,2,3,4), $expect);
+ //
+});
+
+it('should return a line segment intersection', () => {
+ let a = new Point(-10,-10);
+ let b = new Point(10,-10);
+ let c = new Point(10,10);
+ let d = new Point(-10,10);
+ let result = utils.linesCross(a,c,b,d);
+ expect(result.x).to.equal(0);
+ expect(result.y).to.equal(0);
+ expect(utils.linesCross(a,b,c,d)).to.be.false;
+ let e = new Point(5,-5);
+ expect(utils.linesCross(a,b,c,e)).to.be.false;
+
+ let f = new Point(1,10);
+ let g = new Point(0,49);
+ let h = new Point(-20,40);
+ let i = new Point(20,40);
+ console.log(utils.beamsCross(g,f,h,i));
+
+});
+