🚧 Bin packing integration
This commit is contained in:
parent
6f52ccfd2e
commit
9a60dc5756
6 changed files with 130 additions and 2 deletions
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -836,6 +836,11 @@
|
||||||
"live-server": "1.2.0"
|
"live-server": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bin-pack": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bin-pack/-/bin-pack-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-wqAU7b8L7XCjKSBi7UZXe5YSBnk="
|
||||||
|
},
|
||||||
"binary-extensions": {
|
"binary-extensions": {
|
||||||
"version": "1.11.0",
|
"version": "1.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bezier-js": "^2.2.13",
|
"bezier-js": "^2.2.13",
|
||||||
|
"bin-pack": "1.0.2",
|
||||||
"hooks": "^0.3.2"
|
"hooks": "^0.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
46
src/part.js
46
src/part.js
|
@ -13,6 +13,8 @@ function part(id) {
|
||||||
this.snippets = {};
|
this.snippets = {};
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.freeId = 0;
|
this.freeId = 0;
|
||||||
|
this.topLeft = false;
|
||||||
|
this.bottomRight = false;
|
||||||
this.render = id.substr(0, 1) === "_" ? false : true;
|
this.render = id.substr(0, 1) === "_" ? false : true;
|
||||||
this.points.origin = new point(0, 0);
|
this.points.origin = new point(0, 0);
|
||||||
for (let k in hooklib) this[k] = hooklib[k];
|
for (let k in hooklib) this[k] = hooklib[k];
|
||||||
|
@ -50,8 +52,52 @@ part.prototype.getUid = function() {
|
||||||
return "" + this.freeId;
|
return "" + this.freeId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Returns a value formatted for units provided in settings */
|
||||||
part.prototype.units = function(value) {
|
part.prototype.units = function(value) {
|
||||||
return units(value, this.context.settings.units);
|
return units(value, this.context.settings.units);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Calculates the part's bounding box and sets it */
|
||||||
|
part.prototype.boundary = function() {
|
||||||
|
if (this.topLeft) return this; // Cached
|
||||||
|
|
||||||
|
let topLeft = new point(Infinity, Infinity);
|
||||||
|
let bottomRight = new point(-Infinity, -Infinity);
|
||||||
|
for (let key in this.paths) {
|
||||||
|
let path = this.paths[key].boundary();
|
||||||
|
if (path.render) {
|
||||||
|
if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x;
|
||||||
|
if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y;
|
||||||
|
if (path.bottomRight.x > bottomRight.x)
|
||||||
|
bottomRight.x = path.bottomRight.x;
|
||||||
|
if (path.bottomRight.y > bottomRight.y)
|
||||||
|
bottomRight.y = path.bottomRight.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add 10mm margin
|
||||||
|
this.topLeft = new point(topLeft.x - 10, topLeft.y - 10);
|
||||||
|
this.bottomRight = new point(bottomRight.x + 10, bottomRight.y + 10);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Stacks part so that its top left corner is in (0,0) */
|
||||||
|
part.prototype.stack = function() {
|
||||||
|
if (this.topLeft.x === 0 && this.topLeft.y === 0) return this;
|
||||||
|
|
||||||
|
this.boundary().attr(
|
||||||
|
"transform",
|
||||||
|
`translate(${this.topLeft.x * -1}, ${this.topLeft.y * -1})`
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Adds an attribute. This is here to make this call chainable in assignment */
|
||||||
|
part.prototype.attr = function(name, value) {
|
||||||
|
this.attributes.add(name, value);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
export default part;
|
export default part;
|
||||||
|
|
38
src/path.js
38
src/path.js
|
@ -1,9 +1,12 @@
|
||||||
import attributes from "./attributes";
|
import attributes from "./attributes";
|
||||||
|
import point from "./point";
|
||||||
import Bezier from "bezier-js";
|
import Bezier from "bezier-js";
|
||||||
import { pathOffset, pathLength } from "./utils";
|
import { pathOffset, pathLength } from "./utils";
|
||||||
|
|
||||||
function path() {
|
function path() {
|
||||||
this.render = true;
|
this.render = true;
|
||||||
|
this.topLeft = false;
|
||||||
|
this.bottomRight = false;
|
||||||
this.attributes = new attributes();
|
this.attributes = new attributes();
|
||||||
this.ops = [];
|
this.ops = [];
|
||||||
}
|
}
|
||||||
|
@ -114,4 +117,39 @@ path.prototype.end = function() {
|
||||||
if (op.type === "close") return this.start();
|
if (op.type === "close") return this.start();
|
||||||
else return op.to;
|
else return op.to;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Finds the bounding box of a path */
|
||||||
|
path.prototype.boundary = function() {
|
||||||
|
if (this.topLeft) return this; // Cached
|
||||||
|
|
||||||
|
let current;
|
||||||
|
let topLeft = new point(Infinity, Infinity);
|
||||||
|
let bottomRight = new point(-Infinity, -Infinity);
|
||||||
|
for (let i in this.ops) {
|
||||||
|
let op = this.ops[i];
|
||||||
|
if (op.type === "move" || op.type === "line") {
|
||||||
|
if (op.to.x < topLeft.x) topLeft.x = op.to.x;
|
||||||
|
if (op.to.y < topLeft.y) topLeft.y = op.to.y;
|
||||||
|
if (op.to.x > bottomRight.x) bottomRight.x = op.to.x;
|
||||||
|
if (op.to.y > bottomRight.y) bottomRight.y = op.to.y;
|
||||||
|
} else if (op.type === "curve") {
|
||||||
|
let bb = new Bezier(
|
||||||
|
{ x: current.x, y: current.y },
|
||||||
|
{ x: op.cp1.x, y: op.cp1.y },
|
||||||
|
{ x: op.cp2.x, y: op.cp2.y },
|
||||||
|
{ x: op.to.x, y: op.to.y }
|
||||||
|
).bbox();
|
||||||
|
if (bb.x.min < topLeft.x) topLeft.x = bb.x.min;
|
||||||
|
if (bb.y.min < topLeft.y) topLeft.y = bb.y.min;
|
||||||
|
if (bb.x.max > bottomRight.x) bottomRight.x = bb.x.max;
|
||||||
|
if (bb.y.max > bottomRight.y) bottomRight.y = bb.y.max;
|
||||||
|
}
|
||||||
|
if (op.to) current = op.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topLeft = topLeft;
|
||||||
|
this.bottomRight = bottomRight;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
export default path;
|
export default path;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import attributes from "./attributes";
|
||||||
import { macroName } from "./utils";
|
import { macroName } from "./utils";
|
||||||
import part from "./part";
|
import part from "./part";
|
||||||
import point from "./point";
|
import point from "./point";
|
||||||
|
@ -5,6 +6,7 @@ import path from "./path";
|
||||||
import snippet from "./snippet";
|
import snippet from "./snippet";
|
||||||
import svg from "./svg";
|
import svg from "./svg";
|
||||||
import hooks from "./hooks";
|
import hooks from "./hooks";
|
||||||
|
import pack from "bin-pack";
|
||||||
|
|
||||||
export default function pattern(config = false) {
|
export default function pattern(config = false) {
|
||||||
// Allow no-config patterns
|
// Allow no-config patterns
|
||||||
|
@ -26,6 +28,8 @@ export default function pattern(config = false) {
|
||||||
throw "Could not create pattern: You should define at least one part in your pattern config";
|
throw "Could not create pattern: You should define at least one part in your pattern config";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.width = false;
|
||||||
|
this.height = false;
|
||||||
// Constructors
|
// Constructors
|
||||||
this.point = point;
|
this.point = point;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
@ -77,6 +81,7 @@ pattern.prototype.draft = function() {
|
||||||
|
|
||||||
pattern.prototype.render = function() {
|
pattern.prototype.render = function() {
|
||||||
this.hooks.attach("preRenderSvg", this.svg);
|
this.hooks.attach("preRenderSvg", this.svg);
|
||||||
|
|
||||||
this.hooks.attach("postRenderSvg", this.svg);
|
this.hooks.attach("postRenderSvg", this.svg);
|
||||||
//this.hooks.attach('insertText', this.svg);
|
//this.hooks.attach('insertText', this.svg);
|
||||||
|
|
||||||
|
@ -122,3 +127,29 @@ pattern.prototype.macro = function(key, method) {
|
||||||
this.hooks.attach(name, part);
|
this.hooks.attach(name, part);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Packs parts in a 2D space and sets pattern size */
|
||||||
|
pattern.prototype.pack = function() {
|
||||||
|
let bins = [];
|
||||||
|
for (let key in this.parts) {
|
||||||
|
let part = this.parts[key];
|
||||||
|
if (part.render) {
|
||||||
|
part.stack();
|
||||||
|
bins.push({
|
||||||
|
id: part.id,
|
||||||
|
width: part.bottomRight.x - part.topLeft.x,
|
||||||
|
height: part.bottomRight.y - part.topLeft.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let size = pack(bins, { inPlace: true });
|
||||||
|
for (let bin of bins) {
|
||||||
|
let part = this.parts[bin.id];
|
||||||
|
if (bin.x !== 0 || bin.y !== 0)
|
||||||
|
part.attr("transform", `translate (${bin.x}, ${bin.y})`);
|
||||||
|
}
|
||||||
|
this.width = size.width;
|
||||||
|
this.height = size.height;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
11
src/svg.js
11
src/svg.js
|
@ -41,6 +41,11 @@ svg.prototype.insertText = function() {};
|
||||||
/** Renders a draft object as SVG */
|
/** Renders a draft object as SVG */
|
||||||
svg.prototype.render = function(pattern) {
|
svg.prototype.render = function(pattern) {
|
||||||
this.preRenderSvg();
|
this.preRenderSvg();
|
||||||
|
// this needs to run after the preSvgRender hook as it might add stuff
|
||||||
|
pattern.pack();
|
||||||
|
this.attributes.add("width", pattern.width + "mm");
|
||||||
|
this.attributes.add("height", pattern.height + "mm");
|
||||||
|
this.attributes.add("viewBox", `0 0 ${pattern.width} ${pattern.height}`);
|
||||||
this.svg = this.prefix;
|
this.svg = this.prefix;
|
||||||
this.svg += this.renderComments(this.header);
|
this.svg += this.renderComments(this.header);
|
||||||
this.svg += this.renderSvgTag(pattern);
|
this.svg += this.renderSvgTag(pattern);
|
||||||
|
@ -204,11 +209,13 @@ svg.prototype.renderSnippet = function(snippet) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Returns SVG code to open a group */
|
/** Returns SVG code to open a group */
|
||||||
svg.prototype.openGroup = function(id) {
|
svg.prototype.openGroup = function(id, attributes = false) {
|
||||||
let svg = this.nl() + this.nl();
|
let svg = this.nl() + this.nl();
|
||||||
svg += `<!-- Start of group #${id} -->`;
|
svg += `<!-- Start of group #${id} -->`;
|
||||||
svg += this.nl();
|
svg += this.nl();
|
||||||
svg += `<g id="${id}">`;
|
svg += `<g id="${id}"`;
|
||||||
|
if (attributes) svg += ` ${attributes.render()}`;
|
||||||
|
svg += ">";
|
||||||
this.indent();
|
this.indent();
|
||||||
this.openGroups.push(id);
|
this.openGroups.push(id);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue