1
0
Fork 0

🚧 Working on rendering

This commit is contained in:
joostdecock 2018-07-14 16:04:39 +00:00
parent a1115d94c6
commit 4f39f5e357
10 changed files with 347 additions and 8 deletions

3
config/config.ts Normal file
View file

@ -0,0 +1,3 @@
export var config = {
precision: 2
}

View file

@ -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
View 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;
}
}

View file

@ -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
View 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()} />`;
}
}

View file

@ -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);
}
}

View file

@ -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
View 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;
}
}

View file

@ -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
View 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));
});