2022-08-28 02:14:39 +02:00
|
|
|
import { Attributes } from './attributes.mjs'
|
2022-08-26 18:51:02 +02:00
|
|
|
import pack from 'bin-pack'
|
2022-08-13 15:11:33 +02:00
|
|
|
import {
|
2022-09-09 20:20:38 +02:00
|
|
|
addNonEnumProp,
|
2022-08-13 15:11:33 +02:00
|
|
|
macroName,
|
|
|
|
sampleStyle,
|
2022-08-13 18:33:06 +02:00
|
|
|
addPartConfig,
|
2022-08-14 16:59:51 +02:00
|
|
|
mergeDependencies,
|
2022-08-26 18:51:02 +02:00
|
|
|
} from './utils.mjs'
|
2022-08-28 02:14:39 +02:00
|
|
|
import { Part } from './part.mjs'
|
2022-09-12 20:10:22 +02:00
|
|
|
import { Stack } from './stack.mjs'
|
2022-08-28 02:14:39 +02:00
|
|
|
import { Point } from './point.mjs'
|
|
|
|
import { Path } from './path.mjs'
|
|
|
|
import { Snippet } from './snippet.mjs'
|
|
|
|
import { Svg } from './svg.mjs'
|
|
|
|
import { Store } from './store.mjs'
|
|
|
|
import { Hooks } from './hooks.mjs'
|
2022-08-31 20:21:27 +02:00
|
|
|
import { version } from '../data.mjs'
|
2022-09-09 20:29:31 +02:00
|
|
|
import { loadPatternDefaults } from './config.mjs'
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
export function Pattern(config) {
|
|
|
|
// Non-enumerable properties
|
|
|
|
addNonEnumProp(this, 'plugins', {})
|
|
|
|
addNonEnumProp(this, 'width', 0)
|
|
|
|
addNonEnumProp(this, 'height', 0)
|
2022-09-12 20:10:22 +02:00
|
|
|
addNonEnumProp(this, 'autoLayout', { stacks: {} })
|
2022-09-09 20:20:38 +02:00
|
|
|
addNonEnumProp(this, 'is', '')
|
|
|
|
addNonEnumProp(this, 'hooks', new Hooks())
|
|
|
|
addNonEnumProp(this, 'Point', Point)
|
|
|
|
addNonEnumProp(this, 'Path', Path)
|
|
|
|
addNonEnumProp(this, 'Snippet', Snippet)
|
|
|
|
addNonEnumProp(this, 'Attributes', Attributes)
|
|
|
|
addNonEnumProp(this, 'macros', {})
|
|
|
|
addNonEnumProp(this, '__parts', {})
|
|
|
|
addNonEnumProp(this, '__inject', {})
|
|
|
|
addNonEnumProp(this, '__dependencies', {})
|
|
|
|
addNonEnumProp(this, '__resolvedDependencies', {})
|
|
|
|
addNonEnumProp(this, '__draftOrder', [])
|
|
|
|
addNonEnumProp(this, '__hide', {})
|
|
|
|
|
|
|
|
// Enumerable properties
|
2022-09-10 15:04:57 +02:00
|
|
|
this.config = config // Design config
|
|
|
|
this.parts = {} // Drafted parts container
|
2022-09-12 20:10:22 +02:00
|
|
|
this.stacks = {} // Drafted stacks container
|
2022-09-10 15:04:57 +02:00
|
|
|
this.store = new Store() // Store for sharing data across parts
|
2022-08-28 02:14:39 +02:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
return this
|
|
|
|
}
|
2022-08-28 02:14:39 +02:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
/*
|
|
|
|
* We allow late-stage updating of the design config (adding parts for example)
|
|
|
|
* so we need to do the things we used to do in the contructor at a later stage.
|
|
|
|
* This methods does that, and resolves the design config + user settings
|
|
|
|
* Defer some things that used to happen in the constructor to
|
|
|
|
* and in doing so creating a pattern we can draft
|
|
|
|
*/
|
|
|
|
Pattern.prototype.init = function () {
|
|
|
|
// Resolve configuration
|
|
|
|
this.__resolveParts() // Resolves parts
|
|
|
|
.__resolveDependencies() // Resolves dependencies
|
|
|
|
.__resolveDraftOrder() // Resolves draft order
|
|
|
|
.__loadPlugins() // Loads plugins
|
|
|
|
.__filterOptionalMeasurements() // Removes required m's from optional list
|
|
|
|
.__loadConfigData() // Makes config data available in store
|
|
|
|
.__loadOptionDefaults() // Merges default options with user provided ones
|
|
|
|
|
|
|
|
// Say hello
|
|
|
|
this.store.log.info(
|
|
|
|
`New \`${this.store.get('data.name', 'No Name')}:` +
|
2022-09-10 15:04:57 +02:00
|
|
|
`${this.store.get(
|
|
|
|
'data.version',
|
|
|
|
'No version'
|
|
|
|
)}\` pattern using \`@freesewing/core:${version}\``
|
2022-09-09 20:20:38 +02:00
|
|
|
)
|
|
|
|
this.store.log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`)
|
2022-08-07 17:29:33 +02:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
return this
|
|
|
|
}
|
2022-01-28 18:18:37 +01:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadConfigData = function () {
|
|
|
|
if (this.config.data) this.store.set('data', this.config.data)
|
2020-07-18 16:48:29 +02:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-05 14:04:15 +02:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__createPartWithContext = function (name) {
|
2018-12-17 11:49:08 +01:00
|
|
|
// Context object to add to Part closure
|
2022-09-09 20:20:38 +02:00
|
|
|
const part = new Part()
|
|
|
|
part.name = name
|
2022-09-12 20:10:22 +02:00
|
|
|
part.stack = this.__parts[name]?.stack || name
|
2022-09-09 20:20:38 +02:00
|
|
|
part.context = {
|
2018-07-23 11:12:06 +00:00
|
|
|
parts: this.parts,
|
2018-07-24 08:34:26 +02:00
|
|
|
config: this.config,
|
2018-07-25 14:53:10 +00:00
|
|
|
settings: this.settings,
|
2018-12-17 11:49:08 +01:00
|
|
|
store: this.store,
|
2020-07-11 18:04:29 +02:00
|
|
|
macros: this.macros,
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
for (const macro in this.macros) {
|
|
|
|
part[macroName(macro)] = this.macros[macro]
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2022-09-09 20:20:38 +02:00
|
|
|
|
|
|
|
return part
|
2018-12-17 11:49:08 +01:00
|
|
|
}
|
2018-09-30 16:25:03 +02:00
|
|
|
|
2022-09-13 17:56:01 +02:00
|
|
|
Pattern.prototype.__createStackWithContext = function (name) {
|
|
|
|
// Context object to add to Stack closure
|
|
|
|
const stack = new Stack()
|
|
|
|
stack.name = name
|
|
|
|
stack.context = {
|
|
|
|
config: this.config,
|
|
|
|
settings: this.settings,
|
|
|
|
store: this.store,
|
|
|
|
}
|
|
|
|
|
|
|
|
return stack
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
// Merges default for options with user-provided options
|
|
|
|
Pattern.prototype.__loadOptionDefaults = function () {
|
|
|
|
if (Object.keys(this.config.options).length < 1) return this
|
|
|
|
for (const [name, option] of Object.entries(this.config.options)) {
|
|
|
|
// Don't overwrite user-provided settings.options
|
|
|
|
if (typeof this.settings.options[name] === 'undefined') {
|
|
|
|
if (typeof option === 'object') {
|
|
|
|
if (typeof option.pct !== 'undefined') this.settings.options[name] = option.pct / 100
|
|
|
|
else if (typeof option.mm !== 'undefined') this.settings.options[name] = option.mm
|
|
|
|
else if (typeof option.deg !== 'undefined') this.settings.options[name] = option.deg
|
|
|
|
else if (typeof option.count !== 'undefined') this.settings.options[name] = option.count
|
|
|
|
else if (typeof option.bool !== 'undefined') this.settings.options[name] = option.bool
|
|
|
|
else if (typeof option.dflt !== 'undefined') this.settings.options[name] = option.dflt
|
|
|
|
else {
|
|
|
|
let err = 'Unknown option type: ' + JSON.stringify(option)
|
|
|
|
this.store.log.error(err)
|
|
|
|
throw new Error(err)
|
|
|
|
}
|
|
|
|
} else this.settings.options[name] = option
|
2022-08-09 20:17:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-08-27 09:28:36 +02:00
|
|
|
/* Utility method to get the (initialized) config */
|
2022-08-10 16:24:25 +02:00
|
|
|
Pattern.prototype.getConfig = function () {
|
|
|
|
this.init()
|
|
|
|
return this.config
|
|
|
|
}
|
|
|
|
|
2022-08-27 09:28:36 +02:00
|
|
|
/* Utility method to get the (initialized) part list */
|
|
|
|
Pattern.prototype.getPartList = function () {
|
|
|
|
this.init()
|
2022-09-14 15:02:39 +02:00
|
|
|
return Object.keys(this.config.parts)
|
2022-08-27 09:28:36 +02:00
|
|
|
}
|
|
|
|
|
2021-09-15 20:20:59 +02:00
|
|
|
function snappedOption(option, pattern) {
|
|
|
|
const conf = pattern.config.options[option]
|
|
|
|
const abs = conf.toAbs(pattern.settings.options[option], pattern.settings)
|
|
|
|
// Handle units-specific config
|
2021-10-08 17:32:12 +02:00
|
|
|
if (!Array.isArray(conf.snap) && conf.snap.metric && conf.snap.imperial)
|
|
|
|
conf.snap = conf.snap[pattern.settings.units]
|
2021-09-15 20:20:59 +02:00
|
|
|
// Simple steps
|
|
|
|
if (typeof conf.snap === 'number') return Math.ceil(abs / conf.snap) * conf.snap
|
|
|
|
// List of snaps
|
|
|
|
if (Array.isArray(conf.snap) && conf.snap.length > 1) {
|
2021-10-08 17:32:12 +02:00
|
|
|
for (const snap of conf.snap
|
|
|
|
.sort((a, b) => a - b)
|
|
|
|
.map((snap, i) => {
|
|
|
|
const margin =
|
|
|
|
i < conf.snap.length - 1
|
|
|
|
? (conf.snap[Number(i) + 1] - snap) / 2 // Look forward
|
|
|
|
: (snap - conf.snap[i - 1]) / 2 // Final snap, look backward
|
|
|
|
|
|
|
|
return {
|
|
|
|
min: snap - margin,
|
|
|
|
max: snap + Number(margin),
|
|
|
|
snap,
|
|
|
|
}
|
|
|
|
}))
|
2021-10-16 12:28:45 +02:00
|
|
|
if (abs <= snap.max && abs >= snap.min) return snap.snap
|
2021-09-15 20:20:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return abs
|
|
|
|
}
|
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.runHooks = function (hookName, data = false) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (data === false) data = this
|
|
|
|
let hooks = this.hooks[hookName]
|
2018-12-09 14:17:46 +01:00
|
|
|
if (hooks.length > 0) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Running \`${hookName}\` hooks`)
|
2018-12-09 14:17:46 +01:00
|
|
|
for (let hook of hooks) {
|
2019-08-03 15:03:33 +02:00
|
|
|
hook.method(data, hook.data)
|
2018-12-09 14:17:46 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-09-30 16:25:03 +02:00
|
|
|
|
2022-08-09 20:17:35 +02:00
|
|
|
/*
|
|
|
|
* Allows adding a part at run-time
|
|
|
|
*/
|
2022-08-27 09:28:36 +02:00
|
|
|
Pattern.prototype.addPart = function (part) {
|
2022-08-09 20:17:35 +02:00
|
|
|
if (typeof part?.draft === 'function') {
|
2022-08-13 15:11:33 +02:00
|
|
|
if (part.name) {
|
|
|
|
this.config.parts[part.name] = part
|
2022-08-13 18:33:06 +02:00
|
|
|
// Add part-level config to config
|
2022-09-09 20:20:38 +02:00
|
|
|
this.config = addPartConfig(part, this.config, this.store)
|
|
|
|
} else this.store.log.error(`Part must have a name`)
|
|
|
|
} else this.store.log.error(`Part must have a draft() method`)
|
2022-08-09 20:17:35 +02:00
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2018-07-23 20:14:32 +02:00
|
|
|
/**
|
2018-12-16 18:06:01 +01:00
|
|
|
* The default draft method with pre- and postDraft hooks
|
2018-07-23 20:14:32 +02:00
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.draft = function () {
|
2022-08-09 20:17:35 +02:00
|
|
|
// Late-stage initialization
|
|
|
|
this.init()
|
|
|
|
|
2020-07-18 16:48:29 +02:00
|
|
|
if (this.is !== 'sample') {
|
|
|
|
this.is = 'draft'
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Drafting pattern`)
|
2020-07-18 16:48:29 +02:00
|
|
|
}
|
2021-09-15 20:20:59 +02:00
|
|
|
// Handle snap for pct options
|
2022-09-10 18:23:19 +02:00
|
|
|
for (const i in this.settings.options) {
|
2021-09-15 20:20:59 +02:00
|
|
|
if (
|
|
|
|
typeof this.config.options[i] !== 'undefined' &&
|
|
|
|
typeof this.config.options[i].snap !== 'undefined' &&
|
|
|
|
this.config.options[i].toAbs instanceof Function
|
|
|
|
) {
|
2021-10-08 17:32:12 +02:00
|
|
|
this.settings.absoluteOptions[i] = snappedOption(i, this)
|
2021-09-15 20:20:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
this.runHooks('preDraft')
|
2022-09-10 13:35:26 +02:00
|
|
|
for (const partName of this.config.draftOrder) {
|
2022-08-09 20:17:35 +02:00
|
|
|
// Create parts
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Creating part \`${partName}\``)
|
|
|
|
this.parts[partName] = this.__createPartWithContext(partName)
|
2022-08-09 20:17:35 +02:00
|
|
|
// Handle inject/inheritance
|
2022-09-10 13:35:26 +02:00
|
|
|
if (typeof this.__inject[partName] === 'string') {
|
|
|
|
this.store.log.debug(`Creating part \`${partName}\` from part \`${this.__inject[partName]}\``)
|
2020-07-19 13:01:01 +02:00
|
|
|
try {
|
2022-09-10 13:35:26 +02:00
|
|
|
this.parts[partName].inject(this.parts[this.__inject[partName]])
|
2020-07-19 13:01:01 +02:00
|
|
|
} catch (err) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.error([
|
2022-08-09 20:17:35 +02:00
|
|
|
`Could not inject part \`${this.inject[partName]}\` into part \`${partName}\``,
|
2021-04-24 10:16:31 +02:00
|
|
|
err,
|
2020-07-19 13:01:01 +02:00
|
|
|
])
|
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
|
|
|
if (this.needs(partName)) {
|
2022-08-09 20:17:35 +02:00
|
|
|
// Draft part
|
|
|
|
if (typeof this.__parts?.[partName]?.draft === 'function') {
|
|
|
|
try {
|
2022-09-10 19:13:43 +02:00
|
|
|
const result = this.__parts[partName].draft(this.parts[partName].shorthand())
|
|
|
|
if (typeof result === 'undefined') {
|
2022-09-11 18:00:41 +02:00
|
|
|
this.store.log.error(
|
|
|
|
`Result of drafting part ${partName} was undefined. Did you forget to return the part?`
|
|
|
|
)
|
2022-09-10 19:13:43 +02:00
|
|
|
} else this.parts[partName] = result
|
2022-08-09 20:17:35 +02:00
|
|
|
} catch (err) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.error([`Unable to draft part \`${partName}\``, err])
|
2022-08-09 20:17:35 +02:00
|
|
|
}
|
2022-09-09 20:20:38 +02:00
|
|
|
} else this.store.log.error(`Unable to draft pattern. Part.draft() is not callable`)
|
2020-07-19 13:01:01 +02:00
|
|
|
try {
|
|
|
|
this.parts[partName].render =
|
|
|
|
this.parts[partName].render === false ? false : this.wants(partName)
|
|
|
|
} catch (err) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.error([`Unable to set \`render\` property on part \`${partName}\``, err])
|
2020-07-19 13:01:01 +02:00
|
|
|
}
|
2019-05-05 17:06:22 +02:00
|
|
|
} else {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(
|
2022-01-28 18:18:37 +01:00
|
|
|
`Part \`${partName}\` is not needed. Skipping draft and setting render to \`false\``
|
|
|
|
)
|
2019-08-03 15:03:33 +02:00
|
|
|
this.parts[partName].render = false
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.runHooks('postDraft')
|
2018-09-30 16:25:03 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-09-30 16:25:03 +02:00
|
|
|
|
2018-08-09 15:10:15 +02:00
|
|
|
/**
|
|
|
|
* Handles pattern sampling
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sample = function () {
|
2022-08-15 10:48:35 +02:00
|
|
|
// Late-stage initialization
|
|
|
|
this.init()
|
2019-08-03 15:03:33 +02:00
|
|
|
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)
|
|
|
|
} 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
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-08-09 15:10:15 +02:00
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleParts = function () {
|
2019-08-03 15:03:33 +02:00
|
|
|
let parts = {}
|
|
|
|
this.settings.complete = false
|
|
|
|
this.settings.paperless = false
|
|
|
|
this.draft()
|
2018-08-10 14:25:26 +02:00
|
|
|
for (let i in this.parts) {
|
2019-08-03 15:03:33 +02:00
|
|
|
parts[i] = new this.Part()
|
|
|
|
parts[i].render = this.parts[i].render
|
2018-08-10 14:25:26 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
return parts
|
|
|
|
}
|
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleRun = function (parts, anchors, run, runs, extraClass = false) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.draft()
|
2018-09-07 16:09:14 +02:00
|
|
|
for (let i in this.parts) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let anchor = false
|
|
|
|
let dx = 0
|
|
|
|
let dy = 0
|
2018-09-07 16:09:14 +02:00
|
|
|
if (this.parts[i].points.anchor) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (typeof anchors[i] === 'undefined') anchors[i] = this.parts[i].points.anchor
|
2018-09-07 16:09:14 +02:00
|
|
|
else {
|
|
|
|
if (!anchors[i].sitsOn(this.parts[i].points.anchor)) {
|
2019-08-03 15:03:33 +02:00
|
|
|
dx = this.parts[i].points.anchor.dx(anchors[i])
|
|
|
|
dy = this.parts[i].points.anchor.dy(anchors[i])
|
2018-09-07 16:09:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let j in this.parts[i].paths) {
|
2019-08-03 15:03:33 +02:00
|
|
|
parts[i].paths[j + '_' + run] = this.parts[i].paths[j]
|
2018-09-07 16:09:14 +02:00
|
|
|
.clone()
|
2020-07-11 15:15:02 +02:00
|
|
|
.attr(
|
|
|
|
'style',
|
|
|
|
extraClass === 'sample-focus'
|
|
|
|
? this.settings.sample
|
|
|
|
? this.settings.sample.focusStyle || sampleStyle(run, runs)
|
|
|
|
: sampleStyle(run, runs)
|
|
|
|
: sampleStyle(
|
|
|
|
run,
|
|
|
|
runs,
|
|
|
|
this.settings.sample ? this.settings.sample.styles || false : false
|
|
|
|
)
|
|
|
|
)
|
2020-05-30 14:02:37 +02:00
|
|
|
.attr('data-sample-run', run)
|
|
|
|
.attr('data-sample-runs', runs)
|
2018-09-07 16:09:14 +02:00
|
|
|
if (this.parts[i].points.anchor)
|
2019-08-03 15:03:33 +02:00
|
|
|
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-09-07 16:09:14 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-09-07 16:09:14 +02:00
|
|
|
|
2018-08-09 15:10:15 +02:00
|
|
|
/**
|
|
|
|
* Handles option sampling
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleOption = function (optionName) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.is = 'sample'
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Sampling option \`${optionName}\``)
|
2019-08-03 15:03:33 +02:00
|
|
|
this.runHooks('preSample')
|
|
|
|
let step, val
|
|
|
|
let factor = 1
|
|
|
|
let anchors = {}
|
|
|
|
let parts = this.sampleParts()
|
|
|
|
let option = this.config.options[optionName]
|
|
|
|
if (typeof option.list === 'object') {
|
|
|
|
return this.sampleListOption(optionName)
|
2018-09-11 16:22:08 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +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
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
if (typeof option.pct !== 'undefined') factor = 100
|
|
|
|
val = option.min / factor
|
|
|
|
step = (option.max / factor - val) / 9
|
2018-09-22 10:41:51 +02:00
|
|
|
for (let run = 1; run < 11; run++) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.settings.options[optionName] = val
|
|
|
|
this.sampleRun(parts, anchors, run, 10)
|
|
|
|
val += step
|
2018-08-09 16:45:46 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.parts = parts
|
|
|
|
this.runHooks('postSample')
|
2018-08-09 16:45:46 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-09 16:45:46 +02:00
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleListOption = function (optionName) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let parts = this.sampleParts()
|
|
|
|
let option = this.config.options[optionName]
|
|
|
|
let anchors = {}
|
|
|
|
let run = 1
|
|
|
|
let runs = option.list.length
|
2018-09-11 16:22:08 +02:00
|
|
|
for (let val of option.list) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.settings.options[optionName] = val
|
|
|
|
this.sampleRun(parts, anchors, run, runs)
|
|
|
|
run++
|
2018-09-11 16:22:08 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.parts = parts
|
2018-09-11 16:22:08 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-09-11 16:22:08 +02:00
|
|
|
|
2018-08-09 16:45:46 +02:00
|
|
|
/**
|
|
|
|
* Handles measurement sampling
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleMeasurement = function (measurementName) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.is = 'sample'
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Sampling measurement \`${measurementName}\``)
|
2019-08-03 15:03:33 +02:00
|
|
|
this.runHooks('preSample')
|
|
|
|
let anchors = {}
|
|
|
|
let parts = this.sampleParts()
|
|
|
|
let val = this.settings.measurements[measurementName]
|
2018-12-29 12:56:09 +01:00
|
|
|
if (val === undefined)
|
2022-09-10 15:04:57 +02:00
|
|
|
this.store.log.error(
|
|
|
|
`Cannot sample measurement \`${measurementName}\` because it's \`undefined\``
|
|
|
|
)
|
2019-08-03 15:03:33 +02:00
|
|
|
let step = val / 50
|
|
|
|
val = val * 0.9
|
2018-09-22 10:41:51 +02:00
|
|
|
for (let run = 1; run < 11; run++) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.settings.measurements[measurementName] = val
|
|
|
|
this.sampleRun(parts, anchors, run, 10)
|
|
|
|
val += step
|
2018-08-10 14:25:26 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.parts = parts
|
|
|
|
this.runHooks('postSample')
|
2018-08-10 14:25:26 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-10 14:25:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles models sampling
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.sampleModels = function (models, focus = false) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.is = 'sample'
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.debug(`Sampling models`)
|
2019-08-03 15:03:33 +02:00
|
|
|
this.runHooks('preSample')
|
|
|
|
let anchors = {}
|
|
|
|
let parts = this.sampleParts()
|
2020-07-11 15:15:02 +02:00
|
|
|
// If there's a focus, do it first so it's at the bottom of the SVG
|
|
|
|
if (focus) {
|
|
|
|
this.settings.measurements = models[focus]
|
|
|
|
this.sampleRun(parts, anchors, -1, -1, 'sample-focus')
|
|
|
|
delete models[focus]
|
|
|
|
}
|
|
|
|
let run = -1
|
2019-08-03 15:03:33 +02:00
|
|
|
let runs = Object.keys(models).length
|
2018-08-10 14:25:26 +02:00
|
|
|
for (let l in models) {
|
2019-08-03 15:03:33 +02:00
|
|
|
run++
|
|
|
|
this.settings.measurements = models[l]
|
2020-07-11 15:15:02 +02:00
|
|
|
this.sampleRun(parts, anchors, run, runs)
|
2018-08-09 15:10:15 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.parts = parts
|
|
|
|
this.runHooks('postSample')
|
2018-08-09 15:57:30 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-09 15:10:15 +02:00
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.render = function () {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.svg = new Svg(this)
|
|
|
|
this.svg.hooks = this.hooks
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this.pack().svg.render(this)
|
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.on = function (hook, method, data) {
|
2022-08-31 17:52:39 +02:00
|
|
|
for (const added of this.hooks[hook]) {
|
|
|
|
// Don't add it twice
|
|
|
|
if (added.method === method) return this
|
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.hooks[hook].push({ method, data })
|
2021-09-28 19:16:35 +02:00
|
|
|
|
|
|
|
return this
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadPlugins = function () {
|
2022-09-12 18:01:37 +02:00
|
|
|
for (const plugin of this.config.plugins) this.use(plugin, plugin.data)
|
2022-09-04 18:22:02 +02:00
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadPlugin = function (plugin, data, explicit = false) {
|
|
|
|
this.plugins[plugin.name] = plugin
|
|
|
|
if (plugin.hooks) this.__loadPluginHooks(plugin, data)
|
|
|
|
if (plugin.macros) this.__loadPluginMacros(plugin)
|
|
|
|
if (plugin.store) this.__loadPluginStoreMethods(plugin)
|
|
|
|
this.store.log.info(`Loaded plugin \`${plugin.name}:${plugin.version}\``)
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
2022-09-04 18:22:02 +02:00
|
|
|
|
|
|
|
Pattern.prototype.use = function (plugin, data) {
|
|
|
|
if (this.plugins?.[plugin.name]?.condition && !plugin.condition) {
|
2022-09-09 20:20:38 +02:00
|
|
|
// Plugin was first loaded conditionally, and is now loaded explicitly
|
|
|
|
this.store.log.info(
|
2022-09-04 18:22:02 +02:00
|
|
|
`Plugin \`${plugin.plugin.name} was loaded conditionally earlier, but is now loaded explicitly.`
|
|
|
|
)
|
2022-09-09 20:20:38 +02:00
|
|
|
return this.__loadPlugin(plugin, data)
|
|
|
|
}
|
|
|
|
// New plugin
|
|
|
|
else if (!this.plugins?.[plugin.name])
|
|
|
|
return plugin.plugin && plugin.condition
|
|
|
|
? this.__useIf(plugin, data) // Conditional plugin
|
|
|
|
: this.__loadPlugin(plugin, data) // Regular plugin
|
|
|
|
|
|
|
|
this.store.log.info(
|
|
|
|
`Plugin \`${
|
|
|
|
plugin.plugin ? plugin.plugin.name : plugin.name
|
|
|
|
}\` was requested, but it's already loaded. Skipping.`
|
2022-09-04 18:22:02 +02:00
|
|
|
)
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__useIf = function (plugin, settings) {
|
2020-09-12 19:01:12 +02:00
|
|
|
if (plugin.condition(settings)) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.info(
|
2022-01-28 18:18:37 +01:00
|
|
|
`Condition met: Loaded plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
|
|
|
)
|
2022-09-09 20:20:38 +02:00
|
|
|
this.__loadPlugin(plugin.plugin, plugin.data)
|
2020-09-12 19:01:12 +02:00
|
|
|
} else {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.info(
|
2022-01-28 18:18:37 +01:00
|
|
|
`Condition not met: Skipped loading plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
|
|
|
)
|
2020-09-12 19:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadPluginHooks = function (plugin, data) {
|
2018-12-09 14:17:46 +01:00
|
|
|
for (let hook of Object.keys(this.hooks)) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (typeof plugin.hooks[hook] === 'function') {
|
|
|
|
this.on(hook, plugin.hooks[hook], data)
|
2018-12-17 14:39:50 +01:00
|
|
|
} else if (Array.isArray(plugin.hooks[hook])) {
|
2018-08-11 14:03:06 +02:00
|
|
|
for (let method of plugin.hooks[hook]) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.on(hook, method, data)
|
2018-08-11 14:03:06 +02:00
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
}
|
2018-07-23 20:14:32 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-07-23 11:12:06 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadPluginMacros = function (plugin) {
|
2018-07-23 20:14:32 +02:00
|
|
|
for (let macro in plugin.macros) {
|
2019-08-03 15:03:33 +02:00
|
|
|
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
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-07-24 14:38:03 +00:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__loadPluginStoreMethods = function (plugin) {
|
2022-09-07 16:16:33 +02:00
|
|
|
if (Array.isArray(plugin.store)) this.store = this.store.extend(...plugin.store)
|
2022-09-09 20:20:38 +02:00
|
|
|
else this.store.log.warning(`Plugin store methods should be an Array`)
|
2022-09-07 16:16:33 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.macro = function (key, method) {
|
2019-08-03 15:03:33 +02:00
|
|
|
this.macros[key] = method
|
|
|
|
}
|
2018-08-01 18:18:29 +02:00
|
|
|
|
2022-09-12 20:10:22 +02:00
|
|
|
/** Packs stacks in a 2D space and sets pattern size */
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.pack = function () {
|
2022-09-10 18:23:19 +02:00
|
|
|
if (this.store.logs.error.length > 0) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.warning(`One or more errors occured. Not packing pattern parts`)
|
2020-07-19 13:01:01 +02:00
|
|
|
return this
|
|
|
|
}
|
2022-09-12 20:10:22 +02:00
|
|
|
// First, create all stacks
|
|
|
|
this.stacks = {}
|
|
|
|
for (const [name, part] of Object.entries(this.parts)) {
|
2022-09-14 15:04:24 +02:00
|
|
|
const stackName =
|
|
|
|
typeof part.stack === 'function' ? part.stack(this.settings, name) : part.stack
|
2022-09-14 12:24:09 +02:00
|
|
|
if (typeof this.stacks[stackName] === 'undefined')
|
|
|
|
this.stacks[stackName] = this.__createStackWithContext(stackName)
|
|
|
|
this.stacks[stackName].addPart(part)
|
2022-09-12 20:10:22 +02:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
let bins = []
|
2022-09-12 20:10:22 +02:00
|
|
|
for (const [key, stack] of Object.entries(this.stacks)) {
|
|
|
|
// Avoid multiple render calls to cause addition of transforms
|
|
|
|
stack.attributes.remove('transform')
|
|
|
|
if (!this.isStackHidden(key)) {
|
|
|
|
stack.home()
|
2022-09-13 17:56:01 +02:00
|
|
|
if (this.settings.layout === true)
|
|
|
|
bins.push({ id: key, width: stack.width, height: stack.height })
|
2019-01-13 15:00:01 +01:00
|
|
|
else {
|
2022-09-12 20:10:22 +02:00
|
|
|
if (this.width < stack.width) this.width = stack.width
|
|
|
|
if (this.height < stack.height) this.height = stack.height
|
2019-01-13 15:00:01 +01:00
|
|
|
}
|
2018-08-01 18:18:29 +02:00
|
|
|
}
|
|
|
|
}
|
2019-01-15 13:56:31 +01:00
|
|
|
if (this.settings.layout === true) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let size = pack(bins, { inPlace: true })
|
2019-01-13 15:00:01 +01:00
|
|
|
for (let bin of bins) {
|
2022-09-12 20:10:22 +02:00
|
|
|
this.autoLayout.stacks[bin.id] = { move: {} }
|
|
|
|
let stack = this.stacks[bin.id]
|
2022-02-25 08:29:28 +01:00
|
|
|
if (bin.x !== 0 || bin.y !== 0) {
|
2022-09-12 20:10:22 +02:00
|
|
|
stack.attr('transform', `translate(${bin.x}, ${bin.y})`)
|
2022-08-17 00:34:25 -05:00
|
|
|
}
|
2022-09-12 20:10:22 +02:00
|
|
|
this.autoLayout.stacks[bin.id].move = {
|
|
|
|
x: bin.x + stack.layout.move.x,
|
|
|
|
y: bin.y + stack.layout.move.y,
|
2022-08-17 00:34:25 -05:00
|
|
|
}
|
2019-01-13 15:00:01 +01:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
this.width = size.width
|
|
|
|
this.height = size.height
|
|
|
|
} else if (typeof this.settings.layout === 'object') {
|
|
|
|
this.width = this.settings.layout.width
|
|
|
|
this.height = this.settings.layout.height
|
2022-09-12 20:10:22 +02:00
|
|
|
for (let stackId of Object.keys(this.settings.layout.stacks)) {
|
2022-03-06 18:55:13 +01:00
|
|
|
// Some parts are added by late-stage plugins
|
2022-09-12 20:10:22 +02:00
|
|
|
if (this.stacks[stackId]) {
|
|
|
|
let transforms = this.settings.layout.stacks[stackId]
|
|
|
|
this.stacks[stackId].generateTransform(transforms)
|
2019-01-15 19:23:36 +01:00
|
|
|
}
|
2019-01-15 13:56:31 +01:00
|
|
|
}
|
2018-08-01 18:18:29 +02:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return this
|
|
|
|
}
|
2018-08-15 18:54:47 +02:00
|
|
|
|
2018-12-16 18:06:01 +01:00
|
|
|
/** Determines the order to draft parts in, based on dependencies */
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDependencies) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let sorted = []
|
|
|
|
let visited = {}
|
2018-12-16 18:06:01 +01:00
|
|
|
Object.keys(graph).forEach(function visit(name, ancestors) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (!Array.isArray(ancestors)) ancestors = []
|
|
|
|
ancestors.push(name)
|
|
|
|
visited[name] = true
|
|
|
|
if (typeof graph[name] !== 'undefined') {
|
2020-04-18 11:36:02 +02:00
|
|
|
graph[name].forEach(function (dep) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (visited[dep]) return
|
|
|
|
visit(dep, ancestors.slice(0))
|
|
|
|
})
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
if (sorted.indexOf(name) < 0) sorted.push(name)
|
|
|
|
})
|
2018-12-16 18:06:01 +01:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
// Don't forget about parts without dependencies
|
|
|
|
for (const part in this.__parts) {
|
|
|
|
if (sorted.indexOf(part) === -1) sorted.push(part)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.__draftOrder = sorted
|
|
|
|
this.config.draftOrder = sorted
|
|
|
|
|
|
|
|
return this
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
|
|
|
|
/** Recursively solves part dependencies for a part */
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.resolveDependency = function (seen, part, graph = this.dependencies, deps = []) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (typeof seen[part] === 'undefined') seen[part] = true
|
2021-04-23 20:09:16 +02:00
|
|
|
if (typeof graph[part] === 'string') graph[part] = [graph[part]]
|
|
|
|
if (Array.isArray(graph[part])) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (graph[part].length === 0) return []
|
2018-12-20 14:34:26 +01:00
|
|
|
else {
|
2021-04-23 17:42:03 +02:00
|
|
|
if (deps.indexOf(graph[part]) === -1) deps.push(...graph[part])
|
2019-08-03 15:03:33 +02:00
|
|
|
for (let apart of graph[part]) deps.concat(this.resolveDependency(seen, apart, graph, deps))
|
2018-12-20 14:34:26 +01:00
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return deps
|
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
|
2022-08-14 16:59:51 +02:00
|
|
|
/** Adds a part as a simple dependency **/
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__addDependency = function (name, part, dep) {
|
|
|
|
this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name])
|
2022-08-14 16:59:51 +02:00
|
|
|
if (typeof this.__parts[dep.name] === 'undefined') {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.config = addPartConfig(this.__parts[dep.name], this.config, this.store)
|
2022-08-14 16:59:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-08-27 09:28:36 +02:00
|
|
|
/** Filter optional measurements out if they are also required measurements */
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__filterOptionalMeasurements = function () {
|
2022-08-14 16:59:51 +02:00
|
|
|
this.config.optionalMeasurements = this.config.optionalMeasurements.filter(
|
2022-09-09 20:20:38 +02:00
|
|
|
(m) => this.config.measurements.indexOf(m) === -1
|
2022-08-14 16:59:51 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-08-09 20:17:35 +02:00
|
|
|
/** Pre-Resolves part dependencies that are passed in 2022 style */
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__resolveParts = function (count = 0) {
|
|
|
|
if (count === 0) {
|
|
|
|
for (const part of this.config.parts) {
|
|
|
|
this.__parts[part.name] = part
|
|
|
|
}
|
|
|
|
}
|
2022-08-09 20:17:35 +02:00
|
|
|
for (const [name, part] of Object.entries(this.__parts)) {
|
2022-09-11 18:00:41 +02:00
|
|
|
// Hide when hideAll is set
|
|
|
|
if (part.hideAll) part.hide = true
|
2022-08-14 16:59:51 +02:00
|
|
|
// Inject (from)
|
2022-08-09 20:17:35 +02:00
|
|
|
if (part.from) {
|
2022-09-11 18:00:41 +02:00
|
|
|
if (part.hideDependencies || part.hideAll) {
|
|
|
|
part.from.hide = true
|
|
|
|
part.from.hideAll = true
|
|
|
|
}
|
2022-09-09 20:20:38 +02:00
|
|
|
this.__parts[part.from.name] = part.from
|
|
|
|
this.__inject[name] = part.from.name
|
2022-08-09 20:17:35 +02:00
|
|
|
}
|
2022-08-14 16:59:51 +02:00
|
|
|
// Simple dependency (after)
|
|
|
|
if (part.after) {
|
|
|
|
if (Array.isArray(part.after)) {
|
2022-09-09 20:20:38 +02:00
|
|
|
for (const dep of part.after) {
|
|
|
|
this.__parts[dep.name] = dep
|
|
|
|
this.__addDependency(name, part, dep)
|
|
|
|
}
|
|
|
|
} else {
|
2022-09-11 18:00:41 +02:00
|
|
|
if (part.hideDependencies) part.after.hide = true
|
2022-09-09 20:20:38 +02:00
|
|
|
this.__parts[part.after.name] = part.after
|
|
|
|
this.__addDependency(name, part, part.after)
|
2022-08-14 16:59:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Did we discover any new dependencies?
|
|
|
|
const len = Object.keys(this.__parts).length
|
2022-09-09 20:20:38 +02:00
|
|
|
// If so, resolve recursively
|
|
|
|
if (len > count) return this.__resolveParts(len)
|
2022-08-14 16:59:51 +02:00
|
|
|
|
|
|
|
for (const [name, part] of Object.entries(this.__parts)) {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.config = addPartConfig(part, this.config, this.store)
|
2022-08-09 20:17:35 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
return this
|
2022-08-09 20:17:35 +02:00
|
|
|
}
|
|
|
|
|
2018-12-16 18:06:01 +01:00
|
|
|
/** Resolves part dependencies into a flat array */
|
2022-09-09 20:20:38 +02:00
|
|
|
Pattern.prototype.__resolveDependencies = function (graph = false) {
|
|
|
|
if (!graph) graph = this.__dependencies
|
|
|
|
for (const i in this.__inject) {
|
|
|
|
const dependency = this.__inject[i]
|
|
|
|
if (typeof this.__dependencies[i] === 'undefined') this.__dependencies[i] = dependency
|
|
|
|
else if (this.__dependencies[i] !== dependency) {
|
|
|
|
if (typeof this.__dependencies[i] === 'string') {
|
|
|
|
this.__dependencies[i] = [this.__dependencies[i], dependency]
|
|
|
|
} else if (Array.isArray(this.__dependencies[i])) {
|
|
|
|
if (this.__dependencies[i].indexOf(dependency) === -1)
|
|
|
|
this.__dependencies[i].push(dependency)
|
2020-07-19 13:01:01 +02:00
|
|
|
} else {
|
2022-09-09 20:20:38 +02:00
|
|
|
this.store.log.error('Part dependencies should be a string or an array of strings')
|
2020-07-19 13:01:01 +02:00
|
|
|
throw new Error('Part dependencies should be a string or an array of strings')
|
|
|
|
}
|
2019-05-31 19:18:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
let resolved = {}
|
|
|
|
let seen = {}
|
|
|
|
for (let part in graph) resolved[part] = this.resolveDependency(seen, part, graph)
|
|
|
|
for (let part in seen) if (typeof resolved[part] === 'undefined') resolved[part] = []
|
2018-12-16 18:06:01 +01:00
|
|
|
|
2022-09-09 20:20:38 +02:00
|
|
|
this.__resolvedDependencies = resolved
|
|
|
|
this.config.resolvedDependencies = resolved
|
|
|
|
|
|
|
|
return this
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
|
2018-08-16 12:09:57 +02:00
|
|
|
/** Determines whether a part is needed
|
2018-12-16 18:06:01 +01:00
|
|
|
* This depends on the 'only' setting and the
|
|
|
|
* configured dependencies.
|
2018-08-15 18:54:47 +02:00
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.needs = function (partName) {
|
2022-09-10 15:04:57 +02:00
|
|
|
// If only is unset, all parts are needed
|
|
|
|
if (
|
|
|
|
typeof this.settings.only === 'undefined' ||
|
|
|
|
this.settings.only === false ||
|
2022-09-11 18:00:41 +02:00
|
|
|
(Array.isArray(this.settings.only) && this.settings.only.length === 0)
|
|
|
|
)
|
|
|
|
return true
|
2022-09-10 15:04:57 +02:00
|
|
|
|
|
|
|
// Make only to always be an array
|
2022-09-11 18:00:41 +02:00
|
|
|
const only = typeof this.settings.only === 'string' ? [this.settings.only] : this.settings.only
|
2022-09-10 15:04:57 +02:00
|
|
|
|
|
|
|
// Walk the only parts, checking each one for a match in its dependencies
|
|
|
|
for (const part of only) {
|
|
|
|
if (part === partName) return true
|
|
|
|
if (this.__resolvedDependencies[part]) {
|
|
|
|
for (const dependency of this.__resolvedDependencies[part]) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (dependency === partName) return true
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
2018-08-15 18:54:47 +02:00
|
|
|
}
|
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return false
|
|
|
|
}
|
2018-12-16 18:06:01 +01:00
|
|
|
|
|
|
|
/** Determines whether a part is wanted by the user
|
|
|
|
* This depends on the 'only' setting
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.wants = function (partName) {
|
2022-09-10 15:04:57 +02:00
|
|
|
// Hidden parts are not wanted
|
2022-08-29 17:44:24 +02:00
|
|
|
if (this.isHidden(partName)) return false
|
2022-09-09 20:20:38 +02:00
|
|
|
else if (typeof this.settings.only === 'string') return this.settings.only === partName
|
2022-08-27 09:28:36 +02:00
|
|
|
else if (Array.isArray(this.settings.only)) {
|
|
|
|
for (const part of this.settings.only) {
|
2019-08-03 15:03:33 +02:00
|
|
|
if (part === partName) return true
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
return false
|
2018-12-16 18:06:01 +01:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return true
|
|
|
|
}
|
2019-05-05 17:06:22 +02:00
|
|
|
|
2022-09-10 15:04:57 +02:00
|
|
|
/* Checks whether a part is hidden in the config */
|
|
|
|
Pattern.prototype.isHidden = function (partName) {
|
|
|
|
if (Array.isArray(this.settings.only)) {
|
|
|
|
if (this.settings.only.includes(partName)) return false
|
|
|
|
}
|
|
|
|
if (this.__parts?.[partName]?.hide) return true
|
|
|
|
if (this.__parts?.[partName]?.hideAll) return true
|
|
|
|
|
|
|
|
return false
|
2022-07-31 14:32:24 +02:00
|
|
|
}
|
|
|
|
|
2022-09-12 20:10:22 +02:00
|
|
|
/* Checks whether (all parts in) a stack is hidden in the config */
|
|
|
|
Pattern.prototype.isStackHidden = function (stackName) {
|
|
|
|
if (!this.stacks[stackName]) return true
|
|
|
|
const parts = this.stacks[stackName].getPartNames()
|
|
|
|
if (Array.isArray(this.settings.only)) {
|
|
|
|
for (const partName of parts) {
|
|
|
|
if (this.settings.only.includes(partName)) return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const partName of parts) {
|
|
|
|
if (this.__parts?.[partName]?.hide) return true
|
|
|
|
if (this.__parts?.[partName]?.hideAll) return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-05-05 17:06:22 +02:00
|
|
|
/** Returns props required to render this pattern through
|
|
|
|
* an external renderer (eg. a React component)
|
|
|
|
*/
|
2020-04-18 11:36:02 +02:00
|
|
|
Pattern.prototype.getRenderProps = function () {
|
2020-11-08 18:19:10 +01:00
|
|
|
// Run pre-render hook
|
|
|
|
let svg = new Svg(this)
|
|
|
|
svg.hooks = this.hooks
|
|
|
|
svg.runHooks('preRender')
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
this.pack()
|
2022-02-20 18:45:44 +01:00
|
|
|
// Run post-layout hook
|
|
|
|
this.runHooks('postLayout')
|
2020-11-08 18:19:10 +01:00
|
|
|
let props = { svg }
|
2019-08-03 15:03:33 +02:00
|
|
|
props.width = this.width
|
|
|
|
props.height = this.height
|
2022-02-25 08:29:28 +01:00
|
|
|
props.autoLayout = this.autoLayout
|
2019-08-03 15:03:33 +02:00
|
|
|
props.settings = this.settings
|
2022-09-10 18:23:19 +02:00
|
|
|
props.logs = {
|
|
|
|
debug: this.store.logs.debug,
|
|
|
|
info: this.store.logs.info,
|
|
|
|
error: this.store.logs.error,
|
|
|
|
warning: this.store.logs.warning,
|
2020-07-11 18:04:29 +02:00
|
|
|
}
|
2019-08-03 15:03:33 +02:00
|
|
|
props.parts = {}
|
2019-07-08 14:46:58 +02:00
|
|
|
for (let p in this.parts) {
|
2019-05-05 17:06:22 +02:00
|
|
|
if (this.parts[p].render) {
|
|
|
|
props.parts[p] = {
|
|
|
|
paths: this.parts[p].paths,
|
|
|
|
points: this.parts[p].points,
|
2019-05-06 17:01:44 +02:00
|
|
|
snippets: this.parts[p].snippets,
|
2019-05-05 17:06:22 +02:00
|
|
|
attributes: this.parts[p].attributes,
|
|
|
|
height: this.parts[p].height,
|
|
|
|
width: this.parts[p].width,
|
|
|
|
bottomRight: this.parts[p].bottomRight,
|
2021-04-24 10:16:31 +02:00
|
|
|
topLeft: this.parts[p].topLeft,
|
2019-08-03 15:03:33 +02:00
|
|
|
}
|
2019-05-05 17:06:22 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-13 17:56:01 +02:00
|
|
|
props.stacks = {}
|
|
|
|
for (let s in this.stacks) {
|
|
|
|
if (!this.isStackHidden(s)) {
|
|
|
|
props.stacks[s] = this.stacks[s]
|
|
|
|
}
|
|
|
|
}
|
2019-05-05 17:06:22 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return props
|
|
|
|
}
|
2022-09-09 20:20:38 +02:00
|
|
|
|
|
|
|
// Merges settings object with default settings
|
|
|
|
Pattern.prototype.__applySettings = function (settings) {
|
2022-09-09 20:29:31 +02:00
|
|
|
this.settings = { ...loadPatternDefaults(), ...settings }
|
2022-09-09 20:20:38 +02:00
|
|
|
|
|
|
|
return this
|
|
|
|
}
|