🚧 Working on rendering
This commit is contained in:
parent
a1115d94c6
commit
4f39f5e357
10 changed files with 347 additions and 8 deletions
3
config/config.ts
Normal file
3
config/config.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export var config = {
|
||||
precision: 2
|
||||
}
|
8
index.ts
8
index.ts
|
@ -1,11 +1,17 @@
|
|||
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',
|
||||
version: '1.0.1',
|
||||
config,
|
||||
pattern: Pattern,
|
||||
point: Point,
|
||||
path: Path,
|
||||
utils,
|
||||
bezier
|
||||
}
|
||||
|
||||
|
|
20
lib/attributes.ts
Normal file
20
lib/attributes.ts
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
78
lib/path.ts
Normal file
78
lib/path.ts
Normal file
|
@ -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 `<path ${this.render()} />`;
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
96
lib/renderer.ts
Normal file
96
lib/renderer.ts
Normal file
|
@ -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()}<path ${path.attributes.add('d', path.asPathstring()).render()} />`;
|
||||
}
|
||||
|
||||
|
||||
/** Returns SVG code to open a group */
|
||||
protected openGroup(id: string, attributes?: Attributes): string {
|
||||
let svg = this.nl()+this.nl();
|
||||
svg += `<!-- Start of group #${id} -->`;
|
||||
svg += this.nl();
|
||||
svg += `<g id="${id}">`;
|
||||
this.indent();
|
||||
this.openGroups.push(id);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
/** Returns SVG code to close a group */
|
||||
protected closeGroup(): string {
|
||||
this.outdent();
|
||||
|
||||
return `${this.nl()}</g>${this.nl()}<!-- end of group #${this.openGroups.pop()} -->`;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
47
lib/utils.ts
47
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;
|
||||
}
|
||||
|
||||
|
|
77
tests/utils.test.js
Normal file
77
tests/utils.test.js
Normal file
|
@ -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));
|
||||
|
||||
});
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue