🚧 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
|
||||||
|
}
|
14
index.ts
14
index.ts
|
@ -1,12 +1,18 @@
|
||||||
import { Pattern } from './lib/pattern'
|
import { Pattern } from './lib/pattern'
|
||||||
import { Point } from './lib/point'
|
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'
|
import bezier from 'bezier-js'
|
||||||
|
|
||||||
var Freesewing = {
|
var Freesewing = {
|
||||||
version: '0.0.1',
|
version: '1.0.1',
|
||||||
pattern: Pattern,
|
config,
|
||||||
point: Point,
|
pattern: Pattern,
|
||||||
bezier
|
point: Point,
|
||||||
|
path: Path,
|
||||||
|
utils,
|
||||||
|
bezier
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Freesewing;
|
export default Freesewing;
|
||||||
|
|
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 { Point } from './point'
|
||||||
|
import { Path } from './path'
|
||||||
|
import { Attributes } from './attributes'
|
||||||
|
|
||||||
export class Part {
|
export class Part {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -6,12 +8,14 @@ export class Part {
|
||||||
points: {
|
points: {
|
||||||
[index: string]: Point;
|
[index: string]: Point;
|
||||||
}
|
}
|
||||||
|
paths: { [index: string]: Path; } = {};
|
||||||
|
attributes = new Attributes();
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.render = (id.substr(0,1) === '_') ? false : true;
|
this.render = (id.substr(0,1) === '_') ? false : true;
|
||||||
this.points = {};
|
this.points = {'origin': new Point(0,0)};
|
||||||
|
|
||||||
return this;
|
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 { PatternConfig, PatternOption } from './types'
|
||||||
import { Part } from './part'
|
import { Part } from './part'
|
||||||
|
import { Renderer } from './renderer'
|
||||||
import { Option } from './option'
|
import { Option } from './option'
|
||||||
|
|
||||||
export class Pattern {
|
export class Pattern {
|
||||||
config: PatternConfig;
|
config: PatternConfig;
|
||||||
parts: {[propName: string]: Part};
|
parts: {
|
||||||
|
[index: string]: Part;
|
||||||
|
}
|
||||||
options: {[propName: string]: number};
|
options: {[propName: string]: number};
|
||||||
|
|
||||||
constructor(config: PatternConfig) {
|
constructor(config: PatternConfig) {
|
||||||
|
@ -27,4 +30,9 @@ export class Pattern {
|
||||||
draft(config: object): void {
|
draft(config: object): void {
|
||||||
throw Error('You have to implement the draft() method in your Pattern instance.');
|
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';
|
import { round, rad2deg, deg2rad } from './utils';
|
||||||
|
|
||||||
const PRECISION = 2;
|
const PRECISION = 2;
|
||||||
// A poor man's rad2deg()
|
|
||||||
|
|
||||||
export class Point {
|
export class Point {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
|
||||||
|
|
||||||
constructor(x: number, y: number) {
|
constructor(x: number, y: number) {
|
||||||
this.x = round(x);
|
this.x = round(x);
|
||||||
this.y = round(y);
|
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 */
|
/** Rounds a value to PRECISION */
|
||||||
export function round(value: number): number {
|
export function round(value: number): number {
|
||||||
return Math.round(value * 1e2) / 1e2;
|
return Math.round(value * 1e2) / 1e2;
|
||||||
|
@ -13,3 +15,48 @@ export function deg2rad(degrees: number): number {
|
||||||
return degrees / 57.29577951308232;
|
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