1
0
Fork 0
freesewing/src/pattern.js

366 lines
9.6 KiB
JavaScript
Raw Normal View History

import { macroName, debugStyle, round, sampleStyle } from "./utils";
import Part from "./part";
import Point from "./point";
import Path from "./path";
import Snippet from "./snippet";
import Svg from "./svg";
import Hooks from "./hooks";
2018-08-01 18:18:29 +02:00
import pack from "bin-pack";
import Store from "./store";
2018-08-10 15:36:39 +02:00
import * as hooklib from "hooks";
2018-07-23 11:12:06 +00:00
export default function Pattern(config = false) {
// width and height properties
2018-08-01 18:18:29 +02:00
this.width = false;
this.height = false;
2018-07-23 11:12:06 +00:00
// Hooks and Svg instance
2018-08-10 15:36:39 +02:00
for (let k in hooklib) this[k] = hooklib[k];
this.hooks = new Hooks();
2018-08-07 15:23:37 +02:00
Svg.prototype.hooks = this.hooks;
2018-07-23 11:12:06 +00:00
// Data containers
2018-08-09 15:10:15 +02:00
this.settings = {
complete: true,
idPrefix: "fs-",
locale: "en",
units: "metric",
margin: 2
2018-08-09 15:10:15 +02:00
};
this.options = {};
this.store = new Store();
2018-07-23 11:12:06 +00:00
this.parts = {};
2018-07-25 14:53:10 +00:00
// Merge config with defaults
let defaults = {
measurements: {},
options: {}
};
this.config = { ...defaults, ...config };
for (let i in config.options) {
let option = config.options[i];
if (typeof option === "object") {
if (typeof option.pct !== "undefined") this.options[i] = option.pct / 100;
2018-08-31 09:44:12 +02:00
else if (typeof option.mm !== "undefined") this.options[i] = option.mm;
else if (typeof option.deg !== "undefined") this.options[i] = option.deg;
else if (typeof option.count !== "undefined")
this.options[i] = option.count;
else if (typeof option.dflt !== "undefined")
this.options[i] = option.dflt;
2018-08-31 09:44:12 +02:00
else throw "Unknown option type";
} else {
this.options[i] = option;
}
2018-07-23 11:12:06 +00:00
}
// Constructors
this.Part = Part;
this.Point = Point;
this.Path = Path;
this.Snippet = Snippet;
// Context object to inject in part prototype
2018-07-23 11:12:06 +00:00
this.context = {
parts: this.parts,
config: this.config,
2018-07-25 14:53:10 +00:00
settings: this.settings,
options: this.options,
2018-08-05 16:32:38 +02:00
store: this.store
};
this.Part.prototype.context = this.context;
this.Part.prototype.macros = {};
2018-08-07 13:46:38 +02:00
this.Part.prototype.hooks = this.hooks;
2018-07-23 20:14:32 +02:00
}
2018-07-23 11:12:06 +00:00
2018-07-23 20:14:32 +02:00
/**
* @throws Will throw an error when called
*/
Pattern.prototype.draft = function() {
2018-07-23 20:14:32 +02:00
throw Error(
"You have to implement the draft() method in your Pattern instance."
);
};
2018-07-23 11:12:06 +00:00
2018-08-09 15:10:15 +02:00
/**
* Handles pattern sampling
*/
Pattern.prototype.sample = function() {
if (this.settings.sample.type === "option") {
return this.sampleOption(this.settings.sample.option);
} else if (this.settings.sample.type === "measurement") {
return this.sampleMeasurement(this.settings.sample.measurement);
2018-08-10 14:25:26 +02:00
} else if (this.settings.sample.type === "models") {
return this.sampleModels(
this.settings.sample.models,
this.settings.sample.focus || false
);
2018-08-09 15:10:15 +02:00
}
};
2018-08-10 14:25:26 +02:00
Pattern.prototype.sampleParts = function() {
let parts = {};
this.settings.complete = false;
2018-08-10 14:25:26 +02:00
this.settings.paperless = false;
this.draft();
for (let i in this.parts) {
parts[i] = new Part();
parts[i].render = this.parts[i].render;
}
return parts;
};
Pattern.prototype.sampleRun = function(
parts,
anchors,
run,
runs,
extraClass = false
) {
this.draft();
for (let i in this.parts) {
let anchor = false;
let dx = 0;
let dy = 0;
if (this.parts[i].points.anchor) {
if (typeof anchors[i] === "undefined")
anchors[i] = this.parts[i].points.anchor;
else {
if (!anchors[i].sitsOn(this.parts[i].points.anchor)) {
dx = this.parts[i].points.anchor.dx(anchors[i]);
dy = this.parts[i].points.anchor.dy(anchors[i]);
}
}
}
for (let j in this.parts[i].paths) {
parts[i].paths[j + "_" + run] = this.parts[i].paths[j]
.clone()
.attr("style", sampleStyle(run, runs));
if (this.parts[i].points.anchor)
parts[i].paths[j + "_" + run] = parts[i].paths[j + "_" + run].translate(
dx,
dy
);
if (extraClass !== false)
parts[i].paths[j + "_" + run].attributes.add("class", extraClass);
}
}
};
2018-08-09 15:10:15 +02:00
/**
* Handles option sampling
*/
2018-09-06 12:03:18 +02:00
Pattern.prototype.sampleOption = function(optionName) {
2018-08-12 18:50:48 +02:00
let step, val;
let factor = 1;
let anchors = {};
2018-08-10 14:25:26 +02:00
let parts = this.sampleParts();
2018-09-06 12:03:18 +02:00
let option = this.config.options[optionName];
if (typeof option.list === "object") {
console.log("sampling list");
return this.sampleListOption(optionName);
}
2018-09-06 12:03:18 +02:00
if (typeof option.min === "undefined" || typeof option.max === "undefined") {
let min = option * 0.9;
let max = option * 1.1;
option = { min, max };
2018-08-12 18:50:48 +02:00
}
if (typeof option.pct !== "undefined") factor = 100;
val = option.min / factor;
step = (option.max / factor - val) / 9;
for (let run = 1; run < 11; run++) {
2018-09-06 12:03:18 +02:00
this.options[optionName] = val;
this.debug(
debugStyle("info", "🔬 Sample run"),
`Sampling option ${optionName} with value ${round(val)}`
);
this.sampleRun(parts, anchors, run, 10);
2018-08-10 14:25:26 +02:00
val += step;
}
this.parts = parts;
return this;
};
Pattern.prototype.sampleListOption = function(optionName) {
let parts = this.sampleParts();
let option = this.config.options[optionName];
let anchors = {};
let run = 1;
let runs = options.list.length;
for (let val of option.list) {
this.options[optionName] = val;
this.debug(
debugStyle("info", "🔬 Sample run"),
`Sampling option ${optionName} with value ${round(val)}`
);
this.sampleRun(parts, anchors, run, runs);
run++;
}
this.parts = parts;
return this;
};
/**
* Handles measurement sampling
*/
2018-09-06 12:03:18 +02:00
Pattern.prototype.sampleMeasurement = function(measurementName) {
let anchors = {};
2018-08-10 14:25:26 +02:00
let parts = this.sampleParts();
2018-09-06 12:03:18 +02:00
let val = this.settings.measurements[measurementName];
2018-08-12 18:50:48 +02:00
if (val === undefined) throw "Cannot sample a measurement that is undefined";
2018-08-10 14:25:26 +02:00
let step = val / 50;
val = val * 0.9;
for (let run = 1; run < 11; run++) {
2018-09-06 12:03:18 +02:00
this.settings.measurements[measurementName] = val;
this.debug(
debugStyle("info", "🔬 Sample run"),
`Sampling measurement ${measurementName} with value ${round(val)}`
);
this.sampleRun(parts, anchors, run, 10);
2018-08-10 14:25:26 +02:00
val += step;
}
this.parts = parts;
return this;
};
/**
* Handles models sampling
*/
Pattern.prototype.sampleModels = function(models, focus = false) {
let anchors = {};
2018-08-10 14:25:26 +02:00
let parts = this.sampleParts();
let run = 0;
2018-09-22 11:14:10 +02:00
let runs = Object.keys(models).length;
2018-08-10 14:25:26 +02:00
for (let l in models) {
run++;
2018-08-10 14:25:26 +02:00
this.settings.measurements = models[l];
2018-09-06 12:03:18 +02:00
this.debug(debugStyle("info", "🔬 Sample run"), `Sampling model ${l}`);
let className = l === focus ? "sample-focus" : "";
this.sampleRun(parts, anchors, run, runs, className);
2018-08-09 15:10:15 +02:00
}
this.parts = parts;
return this;
2018-08-09 15:10:15 +02:00
};
2018-08-07 15:23:37 +02:00
/** Debug method, exposes debug hook */
2018-08-10 15:36:39 +02:00
Pattern.prototype.debug = function(data) {};
2018-08-01 18:18:29 +02:00
2018-08-07 15:23:37 +02:00
Pattern.prototype.render = function() {
this.svg = new Svg(this);
2018-07-23 11:12:06 +00:00
2018-08-03 14:20:28 +02:00
return this.pack().svg.render(this);
2018-07-23 20:14:32 +02:00
};
2018-07-23 11:12:06 +00:00
Pattern.prototype.on = function(hook, method) {
2018-07-23 20:14:32 +02:00
if (typeof this.hooks._hooks[hook] === "undefined") {
this.hooks._hooks[hook] = [];
}
this.hooks._hooks[hook].push(method);
2018-08-10 15:36:39 +02:00
// Pattern object hooks need to be attached on load
let localHooks = [
"preDraft",
"postDraft",
"preSample",
"postSample",
"debug"
];
if (localHooks.includes(hook)) {
let self = this;
this.hooks.attach(hook, self);
}
2018-07-23 20:14:32 +02:00
};
2018-07-23 11:12:06 +00:00
Pattern.prototype.with = function(plugin) {
2018-09-06 12:03:18 +02:00
this.debug(
debugStyle("success", "🔌 Plugin loaded"),
`${plugin.name} v${plugin.version}`
);
2018-07-23 20:14:32 +02:00
if (plugin.hooks) this.loadPluginHooks(plugin);
if (plugin.macros) this.loadPluginMacros(plugin);
2018-07-23 11:12:06 +00:00
2018-07-23 20:14:32 +02:00
return this;
};
2018-07-23 11:12:06 +00:00
Pattern.prototype.loadPluginHooks = function(plugin) {
2018-07-23 20:14:32 +02:00
for (let hook of this.hooks.all) {
if (typeof plugin.hooks[hook] === "function") {
this.on(hook, plugin.hooks[hook]);
2018-08-14 09:20:47 +02:00
} else if (typeof plugin.hooks[hook] === "object") {
2018-08-11 14:03:06 +02:00
for (let method of plugin.hooks[hook]) {
this.on(hook, method);
}
2018-07-23 11:12:06 +00:00
}
2018-07-23 20:14:32 +02:00
}
};
2018-07-23 11:12:06 +00:00
Pattern.prototype.loadPluginMacros = function(plugin) {
2018-07-23 20:14:32 +02:00
for (let macro in plugin.macros) {
if (typeof plugin.macros[macro] === "function") {
this.macro(macro, plugin.macros[macro]);
2018-07-23 11:12:06 +00:00
}
2018-07-23 20:14:32 +02:00
}
};
Pattern.prototype.macro = function(key, method) {
this.Part.prototype[macroName(key)] = method;
};
2018-08-01 18:18:29 +02:00
/** Packs parts in a 2D space and sets pattern size */
Pattern.prototype.pack = function() {
2018-08-01 18:18:29 +02:00
let bins = [];
for (let key in this.parts) {
let part = this.parts[key];
2018-08-12 16:19:04 +02:00
// Avoid multiple render calls to cause stacking of transforms
part.attributes.set("transform", "");
if (part.render && this.needs(key)) {
2018-08-01 18:18:29 +02:00
part.stack();
bins.push({
id: key,
2018-08-01 18:18:29 +02:00
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;
};
/** Determines whether a part is needed
* This depends on the 'only' setting. People can pass
* the name of a part, or an array of parts
* The absence of only means all parts are needed.
*
* If partName is an array of names, any name needed
* will cause this to return true
*/
Pattern.prototype.needs = function(partName, strict = false) {
if (typeof partName !== "string") {
for (let part of partName) {
if (this.needs(part, strict)) return true;
}
return false;
}
if (typeof this.settings.only === "undefined") {
if (strict) return false;
else return true;
} else if (this.settings.only === partName) return true;
2018-08-16 16:09:47 +02:00
else if (
typeof this.settings.only === "object" &&
this.settings.only.indexOf(partName) !== -1
) {
2018-08-16 16:09:47 +02:00
return true;
} else return false;
};