feat(core): Added support for multiple sets of settings
This changes they was settings (what the user provides) are handled. Before this, settings were passed as an object and that was it. Now, settings are treated as an array of settings objects and this adds full support for managing multiple sets of settings in a single pattern instance. This is the mechanism that's used for FreeSewing's sampling which used to be a rather hackish implementation, but now merely sets up the relevant list of settings, and then calls `pattern.draft()` as usual. Things to be mindful of is that parts, the store and settings themselves are tied to each set of settings. So where they used to be an object, they are now an array of object.
This commit is contained in:
parent
11a2a1dd1c
commit
0b18d81e14
7 changed files with 397 additions and 252 deletions
|
@ -2,6 +2,7 @@ export const loadDesignDefaults = () => ({
|
|||
measurements: [],
|
||||
optionalMeasurements: [],
|
||||
options: {},
|
||||
optionDistance: {},
|
||||
parts: [],
|
||||
data: {},
|
||||
plugins: [],
|
||||
|
@ -10,6 +11,7 @@ export const loadDesignDefaults = () => ({
|
|||
export const loadPatternDefaults = () => ({
|
||||
complete: true,
|
||||
idPrefix: 'fs-',
|
||||
stackPrefix: '',
|
||||
locale: 'en',
|
||||
units: 'metric',
|
||||
margin: 2,
|
||||
|
|
|
@ -10,12 +10,12 @@ export function Design(config) {
|
|||
config = { ...loadDesignDefaults(), ...config }
|
||||
|
||||
// Create the pattern constructor
|
||||
const pattern = function (settings) {
|
||||
const pattern = function (...sets) {
|
||||
// Pass the design config
|
||||
Pattern.call(this, config)
|
||||
|
||||
// Pass the pattern settings
|
||||
return this.__applySettings(settings)
|
||||
return this.__applySettings(sets)
|
||||
}
|
||||
|
||||
// Set up inheritance
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import { Design } from './design.mjs'
|
||||
import { Pattern } from './pattern.mjs'
|
||||
import { Part } from './part.mjs'
|
||||
import { Point } from './point.mjs'
|
||||
import { Path } from './path.mjs'
|
||||
import { Snippet } from './snippet.mjs'
|
||||
|
@ -44,6 +45,7 @@ export {
|
|||
Pattern,
|
||||
Point,
|
||||
Path,
|
||||
Part,
|
||||
Snippet,
|
||||
Store,
|
||||
Bezier,
|
||||
|
|
|
@ -336,21 +336,21 @@ Part.prototype.shorthand = function () {
|
|||
return shorthand
|
||||
}
|
||||
|
||||
Part.prototype.isEmpty = function () {
|
||||
if (Object.keys(this.snippets).length > 0) return false
|
||||
|
||||
if (Object.keys(this.paths).length > 0) {
|
||||
for (const p in this.paths) {
|
||||
if (this.paths[p].render && this.paths[p].length()) return false
|
||||
}
|
||||
}
|
||||
|
||||
for (const p in this.points) {
|
||||
if (this.points[p].attributes.get('data-text')) return false
|
||||
if (this.points[p].attributes.get('data-circle')) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
//Part.prototype.isEmpty = function () {
|
||||
// if (Object.keys(this.snippets).length > 0) return false
|
||||
//
|
||||
// if (Object.keys(this.paths).length > 0) {
|
||||
// for (const p in this.paths) {
|
||||
// if (this.paths[p].render && this.paths[p].length()) return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (const p in this.points) {
|
||||
// if (this.points[p].attributes.get('data-text')) return false
|
||||
// if (this.points[p].attributes.get('data-circle')) return false
|
||||
// }
|
||||
//
|
||||
// return true
|
||||
//}
|
||||
|
||||
export default Part
|
||||
|
|
|
@ -21,6 +21,7 @@ import { loadPatternDefaults } from './config.mjs'
|
|||
export function Pattern(config) {
|
||||
// Non-enumerable properties
|
||||
addNonEnumProp(this, 'plugins', {})
|
||||
addNonEnumProp(this, 'parts', [{}])
|
||||
addNonEnumProp(this, 'width', 0)
|
||||
addNonEnumProp(this, 'height', 0)
|
||||
addNonEnumProp(this, 'autoLayout', { stacks: {} })
|
||||
|
@ -40,9 +41,8 @@ export function Pattern(config) {
|
|||
|
||||
// Enumerable properties
|
||||
this.config = config // Design config
|
||||
this.parts = {} // Drafted parts container
|
||||
this.stacks = {} // Drafted stacks container
|
||||
this.store = new Store() // Store for sharing data across parts
|
||||
this.stores = [new Store()]
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -65,36 +65,42 @@ Pattern.prototype.init = function () {
|
|||
.__loadOptionDefaults() // Merges default options with user provided ones
|
||||
|
||||
// Say hello
|
||||
this.store.log.info(
|
||||
`New \`${this.store.get('data.name', 'No Name')}:` +
|
||||
`${this.store.get(
|
||||
this.stores[0].log.info(
|
||||
`New \`${this.stores[0].get('data.name', 'No Name')}:` +
|
||||
`${this.stores[0].get(
|
||||
'data.version',
|
||||
'No version'
|
||||
)}\` pattern using \`@freesewing/core:${version}\``
|
||||
)
|
||||
this.store.log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`)
|
||||
this.stores[0].log.info(`Pattern initialized. Draft order is: ${this.__draftOrder.join(', ')}`)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Pattern.prototype.__loadConfigData = function () {
|
||||
if (this.config.data) this.store.set('data', this.config.data)
|
||||
if (this.config.data) {
|
||||
for (const i in this.settings) this.stores[i].set('data', this.config.data)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Pattern.prototype.__createPartWithContext = function (name) {
|
||||
Pattern.prototype.__createPartWithContext = function (name, set) {
|
||||
// Context object to add to Part closure
|
||||
const part = new Part()
|
||||
part.name = name
|
||||
part.set = set
|
||||
part.stack = this.__parts[name]?.stack || name
|
||||
part.context = {
|
||||
parts: this.parts,
|
||||
parts: this.parts[set],
|
||||
config: this.config,
|
||||
settings: this.settings,
|
||||
store: this.store,
|
||||
settings: this.settings[set],
|
||||
store: this.stores[set],
|
||||
macros: this.macros,
|
||||
}
|
||||
if (this.settings[set]?.partClasses) {
|
||||
part.attr('class', this.settings[set].partClasses)
|
||||
}
|
||||
|
||||
for (const macro in this.macros) {
|
||||
part[macroName(macro)] = this.macros[macro]
|
||||
|
@ -110,7 +116,7 @@ Pattern.prototype.__createStackWithContext = function (name) {
|
|||
stack.context = {
|
||||
config: this.config,
|
||||
settings: this.settings,
|
||||
store: this.store,
|
||||
stores: this.stores,
|
||||
}
|
||||
|
||||
return stack
|
||||
|
@ -119,22 +125,25 @@ Pattern.prototype.__createStackWithContext = function (name) {
|
|||
// Merges default for options with user-provided options
|
||||
Pattern.prototype.__loadOptionDefaults = function () {
|
||||
if (Object.keys(this.config.options).length < 1) return this
|
||||
for (const i in this.settings) {
|
||||
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 this.settings[i].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
|
||||
if (typeof option.pct !== 'undefined') this.settings[i].options[name] = option.pct / 100
|
||||
else if (typeof option.mm !== 'undefined') this.settings[i].options[name] = option.mm
|
||||
else if (typeof option.deg !== 'undefined') this.settings[i].options[name] = option.deg
|
||||
else if (typeof option.count !== 'undefined')
|
||||
this.settings[i].options[name] = option.count
|
||||
else if (typeof option.bool !== 'undefined') this.settings[i].options[name] = option.bool
|
||||
else if (typeof option.dflt !== 'undefined') this.settings[i].options[name] = option.dflt
|
||||
else {
|
||||
let err = 'Unknown option type: ' + JSON.stringify(option)
|
||||
this.store.log.error(err)
|
||||
this.stores[i].log.error(err)
|
||||
throw new Error(err)
|
||||
}
|
||||
} else this.settings.options[name] = option
|
||||
} else this.settings[i].options[name] = option
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,12 +162,12 @@ Pattern.prototype.getPartList = function () {
|
|||
return Object.keys(this.config.parts)
|
||||
}
|
||||
|
||||
function snappedOption(option, pattern) {
|
||||
const conf = pattern.config.options[option]
|
||||
const abs = conf.toAbs(pattern.settings.options[option], pattern.settings)
|
||||
Pattern.prototype.__snappedPercentageOption = function (optionName, set) {
|
||||
const conf = this.config.options[optionName]
|
||||
const abs = conf.toAbs(this.settings[set].options[optionName], this.settings[set])
|
||||
// Handle units-specific config
|
||||
if (!Array.isArray(conf.snap) && conf.snap.metric && conf.snap.imperial)
|
||||
conf.snap = conf.snap[pattern.settings.units]
|
||||
conf.snap = conf.snap[this.settings[set].units]
|
||||
// Simple steps
|
||||
if (typeof conf.snap === 'number') return Math.ceil(abs / conf.snap) * conf.snap
|
||||
// List of snaps
|
||||
|
@ -187,7 +196,7 @@ Pattern.prototype.runHooks = function (hookName, data = false) {
|
|||
if (data === false) data = this
|
||||
let hooks = this.hooks[hookName]
|
||||
if (hooks.length > 0) {
|
||||
this.store.log.debug(`Running \`${hookName}\` hooks`)
|
||||
this.stores[0].log.debug(`Running \`${hookName}\` hooks`)
|
||||
for (let hook of hooks) {
|
||||
hook.method(data, hook.data)
|
||||
}
|
||||
|
@ -202,9 +211,30 @@ Pattern.prototype.addPart = function (part) {
|
|||
if (part.name) {
|
||||
this.config.parts[part.name] = part
|
||||
// Add part-level config to config
|
||||
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`)
|
||||
this.config = addPartConfig(part, this.config, this.stores[0])
|
||||
} else this.stores[0].log.error(`Part must have a name`)
|
||||
} else this.stores[0].log.error(`Part must have a draft() method`)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Pattern.prototype.__loadAbsoluteOptionsSet = function (set) {
|
||||
for (const optionName in this.settings[set].options) {
|
||||
const option = this.config.options[optionName]
|
||||
if (
|
||||
typeof option !== 'undefined' &&
|
||||
typeof option.snap !== 'undefined' &&
|
||||
option.toAbs instanceof Function
|
||||
) {
|
||||
this.settings[set].absoluteOptions[optionName] = this.__snappedPercentageOption(
|
||||
optionName,
|
||||
set
|
||||
)
|
||||
this.stores[set].log.debug(
|
||||
`🧲 Snapped __${optionName}__ to \`${this.settings[set].absoluteOptions[optionName]}\` for set __${set}__`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -216,66 +246,68 @@ Pattern.prototype.draft = function () {
|
|||
// Late-stage initialization
|
||||
this.init()
|
||||
|
||||
if (this.is !== 'sample') {
|
||||
this.is = 'draft'
|
||||
this.store.log.debug(`Drafting pattern`)
|
||||
}
|
||||
// Iterate over the provided sets of settings (typically just one)
|
||||
for (const set in this.settings) {
|
||||
// Set store
|
||||
this.stores[set].log.debug(`📐 Drafting pattern (set ${set})`)
|
||||
|
||||
// Create parts container
|
||||
this.parts[set] = {}
|
||||
|
||||
// Handle snap for pct options
|
||||
for (const i in this.settings.options) {
|
||||
if (
|
||||
typeof this.config.options[i] !== 'undefined' &&
|
||||
typeof this.config.options[i].snap !== 'undefined' &&
|
||||
this.config.options[i].toAbs instanceof Function
|
||||
) {
|
||||
this.settings.absoluteOptions[i] = snappedOption(i, this)
|
||||
}
|
||||
}
|
||||
this.__loadAbsoluteOptionsSet(set)
|
||||
|
||||
this.runHooks('preDraft')
|
||||
for (const partName of this.config.draftOrder) {
|
||||
// Create parts
|
||||
this.store.log.debug(`Creating part \`${partName}\``)
|
||||
this.parts[partName] = this.__createPartWithContext(partName)
|
||||
this.stores[set].log.debug(`📦 Creating part \`${partName}\` (set ${set})`)
|
||||
this.parts[set][partName] = this.__createPartWithContext(partName, set)
|
||||
// Handle inject/inheritance
|
||||
if (typeof this.__inject[partName] === 'string') {
|
||||
this.store.log.debug(`Creating part \`${partName}\` from part \`${this.__inject[partName]}\``)
|
||||
this.stores[set].log.debug(
|
||||
`Creating part \`${partName}\` from part \`${this.__inject[partName]}\``
|
||||
)
|
||||
try {
|
||||
this.parts[partName].inject(this.parts[this.__inject[partName]])
|
||||
this.parts[set][partName].inject(this.parts[set][this.__inject[partName]])
|
||||
} catch (err) {
|
||||
this.store.log.error([
|
||||
`Could not inject part \`${this.inject[partName]}\` into part \`${partName}\``,
|
||||
this.stores[set].log.error([
|
||||
`Could not inject part \`${this.__inject[partName]}\` into part \`${partName}\``,
|
||||
err,
|
||||
])
|
||||
}
|
||||
}
|
||||
if (this.needs(partName)) {
|
||||
if (this.needs(partName, set)) {
|
||||
// Draft part
|
||||
if (typeof this.__parts?.[partName]?.draft === 'function') {
|
||||
try {
|
||||
const result = this.__parts[partName].draft(this.parts[partName].shorthand())
|
||||
const result = this.__parts[partName].draft(this.parts[set][partName].shorthand())
|
||||
if (typeof result === 'undefined') {
|
||||
this.store.log.error(
|
||||
this.stores[set].log.error(
|
||||
`Result of drafting part ${partName} was undefined. Did you forget to return the part?`
|
||||
)
|
||||
} else this.parts[partName] = result
|
||||
} else this.parts[set][partName] = result
|
||||
} catch (err) {
|
||||
this.store.log.error([`Unable to draft part \`${partName}\``, err])
|
||||
this.stores[set].log.error([`Unable to draft part \`${partName}\` (set ${set})`, err])
|
||||
}
|
||||
} else this.store.log.error(`Unable to draft pattern. Part.draft() is not callable`)
|
||||
} else this.stores[set].log.error(`Unable to draft pattern. Part.draft() is not callable`)
|
||||
try {
|
||||
this.parts[partName].render =
|
||||
this.parts[partName].render === false ? false : this.wants(partName)
|
||||
this.parts[set][partName].render =
|
||||
this.parts[set][partName].render === false ? false : this.wants(partName, set)
|
||||
} catch (err) {
|
||||
this.store.log.error([`Unable to set \`render\` property on part \`${partName}\``, err])
|
||||
this.stores[set].log.error([
|
||||
`Unable to set \`render\` property on part \`${partName}\``,
|
||||
err,
|
||||
])
|
||||
}
|
||||
} else {
|
||||
this.store.log.debug(
|
||||
this.stores[set].log.debug(
|
||||
`Part \`${partName}\` is not needed. Skipping draft and setting render to \`false\``
|
||||
)
|
||||
this.parts[partName].render = false
|
||||
this.parts[set][partName].render = false
|
||||
}
|
||||
}
|
||||
this.runHooks('postDraft')
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -284,28 +316,26 @@ Pattern.prototype.draft = function () {
|
|||
* Handles pattern sampling
|
||||
*/
|
||||
Pattern.prototype.sample = function () {
|
||||
// Late-stage initialization
|
||||
this.init()
|
||||
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)
|
||||
if (this.settings[0].sample.type === 'option') {
|
||||
return this.sampleOption(this.settings[0].sample.option)
|
||||
} else if (this.settings[0].sample.type === 'measurement') {
|
||||
return this.sampleMeasurement(this.settings[0].sample.measurement)
|
||||
} else if (this.settings.sample.type === 'models') {
|
||||
return this.sampleModels(this.settings.sample.models, this.settings.sample.focus || false)
|
||||
return this.sampleModels(this.settings[0].sample.models, this.settings[0].sample.focus || false)
|
||||
}
|
||||
}
|
||||
|
||||
Pattern.prototype.sampleParts = function () {
|
||||
let parts = {}
|
||||
this.settings.complete = false
|
||||
this.settings.paperless = false
|
||||
this.draft()
|
||||
for (let i in this.parts) {
|
||||
parts[i] = new this.Part()
|
||||
parts[i].render = this.parts[i].render
|
||||
}
|
||||
return parts
|
||||
}
|
||||
//Pattern.prototype.sampleParts = function () {
|
||||
// let parts = {}
|
||||
// this.settings.complete = false
|
||||
// this.settings.paperless = false
|
||||
// this.draft()
|
||||
// for (let i in this.parts) {
|
||||
// parts[i] = new this.Part()
|
||||
// parts[i].render = this.parts[i].render
|
||||
// }
|
||||
// return parts
|
||||
//}
|
||||
|
||||
Pattern.prototype.sampleRun = function (parts, anchors, run, runs, extraClass = false) {
|
||||
this.draft()
|
||||
|
@ -345,109 +375,175 @@ Pattern.prototype.sampleRun = function (parts, anchors, run, runs, extraClass =
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles option sampling
|
||||
*/
|
||||
Pattern.prototype.sampleOption = function (optionName) {
|
||||
this.is = 'sample'
|
||||
this.store.log.debug(`Sampling option \`${optionName}\``)
|
||||
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)
|
||||
Pattern.prototype.__setBase = function () {
|
||||
return {
|
||||
...this.settings[0],
|
||||
measurements: { ...(this.settings[0].measurements || {}) },
|
||||
options: { ...(this.settings[0].options || {}) },
|
||||
}
|
||||
}
|
||||
|
||||
Pattern.prototype.__listOptionSets = function (optionName) {
|
||||
let option = this.config.options[optionName]
|
||||
const base = this.__setBase()
|
||||
const sets = []
|
||||
let run = 1
|
||||
for (const choice of option.list) {
|
||||
const settings = {
|
||||
...base,
|
||||
options: {
|
||||
...base.options,
|
||||
},
|
||||
idPrefix: `sample-${run}`,
|
||||
partClasses: `sample-${run}`,
|
||||
}
|
||||
settings.options[optionName] = choice
|
||||
sets.push(settings)
|
||||
run++
|
||||
}
|
||||
|
||||
return sets
|
||||
}
|
||||
|
||||
Pattern.prototype.__optionSets = function (optionName) {
|
||||
let option = this.config.options[optionName]
|
||||
if (typeof option.list === 'object') return this.__listOptionSets(optionName)
|
||||
const sets = []
|
||||
let factor = 1
|
||||
let step, val
|
||||
if (typeof option.min === 'undefined' || typeof option.max === 'undefined') {
|
||||
let min = option * 0.9
|
||||
let max = option * 1.1
|
||||
const min = option * 0.9
|
||||
const max = option * 1.1
|
||||
option = { min, max }
|
||||
}
|
||||
if (typeof option.pct !== 'undefined') factor = 100
|
||||
val = option.min / factor
|
||||
step = (option.max / factor - val) / 9
|
||||
const base = this.__setBase()
|
||||
for (let run = 1; run < 11; run++) {
|
||||
this.settings.options[optionName] = val
|
||||
this.sampleRun(parts, anchors, run, 10)
|
||||
const settings = {
|
||||
...base,
|
||||
options: {
|
||||
...base.options,
|
||||
},
|
||||
idPrefix: `sample-${run}`,
|
||||
partClasses: `sample-${run}`,
|
||||
}
|
||||
settings.options[optionName] = val
|
||||
sets.push(settings)
|
||||
val += step
|
||||
}
|
||||
this.parts = parts
|
||||
this.runHooks('postSample')
|
||||
|
||||
return this
|
||||
return sets
|
||||
}
|
||||
|
||||
Pattern.prototype.sampleListOption = function (optionName) {
|
||||
let parts = this.sampleParts()
|
||||
let option = this.config.options[optionName]
|
||||
let anchors = {}
|
||||
let run = 1
|
||||
let runs = option.list.length
|
||||
for (let val of option.list) {
|
||||
this.settings.options[optionName] = val
|
||||
this.sampleRun(parts, anchors, run, runs)
|
||||
run++
|
||||
}
|
||||
this.parts = parts
|
||||
/**
|
||||
* Handles option sampling
|
||||
*/
|
||||
Pattern.prototype.sampleOption = function (optionName) {
|
||||
this.stores[0].log.debug(`Sampling option \`${optionName}\``)
|
||||
this.runHooks('preSample')
|
||||
this.__applySettings(this.__optionSets(optionName))
|
||||
this.init()
|
||||
this.runHooks('postSample')
|
||||
|
||||
return this
|
||||
return this.draft()
|
||||
}
|
||||
|
||||
//Pattern.prototype.sampleListOption = function (optionName) {
|
||||
// let parts = this.sampleParts()
|
||||
// let option = this.config.options[optionName]
|
||||
// let anchors = {}
|
||||
// let run = 1
|
||||
// let runs = option.list.length
|
||||
// for (let val of option.list) {
|
||||
// this.settings.options[optionName] = val
|
||||
// this.sampleRun(parts, anchors, run, runs)
|
||||
// run++
|
||||
// }
|
||||
// this.parts = parts
|
||||
//
|
||||
// return this
|
||||
//}
|
||||
|
||||
Pattern.prototype.__measurementSets = function (measurementName) {
|
||||
let val = this.settings[0].measurements[measurementName]
|
||||
if (val === undefined)
|
||||
this.stores.log.error(
|
||||
`Cannot sample measurement \`${measurementName}\` because it's \`undefined\``
|
||||
)
|
||||
let step = val / 50
|
||||
val = val * 0.9
|
||||
const sets = []
|
||||
const base = this.__setBase()
|
||||
for (let run = 1; run < 11; run++) {
|
||||
const settings = {
|
||||
...base,
|
||||
measurements: {
|
||||
...base.measurements,
|
||||
},
|
||||
idPrefix: `sample-${run}`,
|
||||
partClasses: `sample-${run}`,
|
||||
}
|
||||
settings.measurements[measurementName] = val
|
||||
sets.push(settings)
|
||||
val += step
|
||||
}
|
||||
|
||||
return sets
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles measurement sampling
|
||||
*/
|
||||
Pattern.prototype.sampleMeasurement = function (measurementName) {
|
||||
this.is = 'sample'
|
||||
this.store.log.debug(`Sampling measurement \`${measurementName}\``)
|
||||
this.runHooks('preSample')
|
||||
let anchors = {}
|
||||
let parts = this.sampleParts()
|
||||
let val = this.settings.measurements[measurementName]
|
||||
if (val === undefined)
|
||||
this.store.log.error(
|
||||
`Cannot sample measurement \`${measurementName}\` because it's \`undefined\``
|
||||
)
|
||||
let step = val / 50
|
||||
val = val * 0.9
|
||||
for (let run = 1; run < 11; run++) {
|
||||
this.settings.measurements[measurementName] = val
|
||||
this.sampleRun(parts, anchors, run, 10)
|
||||
val += step
|
||||
}
|
||||
this.parts = parts
|
||||
this.__applySettings(this.__measurementSets(measurementName))
|
||||
this.init()
|
||||
this.runHooks('postSample')
|
||||
|
||||
return this
|
||||
return this.draft()
|
||||
}
|
||||
|
||||
Pattern.prototype.__modelSets = function (models, focus = false) {
|
||||
const sets = []
|
||||
const base = this.__setBase()
|
||||
let run = 1
|
||||
// If there's a focus, do it first so it's at the bottom of the SVG
|
||||
if (focus) {
|
||||
sets.push({
|
||||
...base,
|
||||
measurements: models[focus],
|
||||
idPrefix: `sample-${run}`,
|
||||
partClasses: `sample-${run} sample-focus`,
|
||||
})
|
||||
run++
|
||||
delete models[focus]
|
||||
}
|
||||
for (const measurements of Object.values(models)) {
|
||||
sets.push({
|
||||
...base,
|
||||
measurements,
|
||||
idPrefix: `sample-${run}`,
|
||||
partClasses: `sample-${run}`,
|
||||
})
|
||||
}
|
||||
|
||||
return sets
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles models sampling
|
||||
*/
|
||||
Pattern.prototype.sampleModels = function (models, focus = false) {
|
||||
this.is = 'sample'
|
||||
this.store.log.debug(`Sampling models`)
|
||||
this.store.log.debug(`Sampling models \`${Object.keys(models).join(', ')}\``)
|
||||
this.runHooks('preSample')
|
||||
let anchors = {}
|
||||
let parts = this.sampleParts()
|
||||
// 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
|
||||
let runs = Object.keys(models).length
|
||||
for (let l in models) {
|
||||
run++
|
||||
this.settings.measurements = models[l]
|
||||
this.sampleRun(parts, anchors, run, runs)
|
||||
}
|
||||
this.parts = parts
|
||||
this.__applySettings(this.__modelSets(models, focus))
|
||||
this.init()
|
||||
this.runHooks('postSample')
|
||||
|
||||
return this
|
||||
return this.draft()
|
||||
}
|
||||
|
||||
Pattern.prototype.render = function () {
|
||||
|
@ -478,7 +574,7 @@ Pattern.prototype.__loadPlugin = function (plugin, data) {
|
|||
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}\``)
|
||||
this.stores[0].log.info(`Loaded plugin \`${plugin.name}:${plugin.version}\``)
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -486,7 +582,7 @@ Pattern.prototype.__loadPlugin = function (plugin, data) {
|
|||
Pattern.prototype.use = function (plugin, data) {
|
||||
if (this.plugins?.[plugin.name]?.condition && !plugin.condition) {
|
||||
// Plugin was first loaded conditionally, and is now loaded explicitly
|
||||
this.store.log.info(
|
||||
this.stores[0].log.info(
|
||||
`Plugin \`${plugin.plugin.name} was loaded conditionally earlier, but is now loaded explicitly.`
|
||||
)
|
||||
return this.__loadPlugin(plugin, data)
|
||||
|
@ -497,7 +593,7 @@ Pattern.prototype.use = function (plugin, data) {
|
|||
? this.__useIf(plugin, data) // Conditional plugin
|
||||
: this.__loadPlugin(plugin, data) // Regular plugin
|
||||
|
||||
this.store.log.info(
|
||||
this.stores[0].log.info(
|
||||
`Plugin \`${
|
||||
plugin.plugin ? plugin.plugin.name : plugin.name
|
||||
}\` was requested, but it's already loaded. Skipping.`
|
||||
|
@ -506,14 +602,18 @@ Pattern.prototype.use = function (plugin, data) {
|
|||
return this
|
||||
}
|
||||
|
||||
Pattern.prototype.__useIf = function (plugin, settings) {
|
||||
if (plugin.condition(settings)) {
|
||||
this.store.log.info(
|
||||
Pattern.prototype.__useIf = function (plugin) {
|
||||
let load = 0
|
||||
for (const set of this.settings) {
|
||||
if (plugin.condition(set)) load++
|
||||
}
|
||||
if (load > 0) {
|
||||
this.stores[0].log.info(
|
||||
`Condition met: Loaded plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
||||
)
|
||||
this.__loadPlugin(plugin.plugin, plugin.data)
|
||||
} else {
|
||||
this.store.log.info(
|
||||
this.stores[0].log.info(
|
||||
`Condition not met: Skipped loading plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
||||
)
|
||||
}
|
||||
|
@ -542,8 +642,9 @@ Pattern.prototype.__loadPluginMacros = function (plugin) {
|
|||
}
|
||||
|
||||
Pattern.prototype.__loadPluginStoreMethods = function (plugin) {
|
||||
if (Array.isArray(plugin.store)) this.store = this.store.extend(...plugin.store)
|
||||
else this.store.log.warning(`Plugin store methods should be an Array`)
|
||||
if (Array.isArray(plugin.store)) {
|
||||
for (const store of this.stores) store.extend(...plugin.store)
|
||||
} else this.stores[0].log.warning(`Plugin store methods should be an Array`)
|
||||
}
|
||||
|
||||
Pattern.prototype.macro = function (key, method) {
|
||||
|
@ -552,19 +653,24 @@ Pattern.prototype.macro = function (key, method) {
|
|||
|
||||
/** Packs stacks in a 2D space and sets pattern size */
|
||||
Pattern.prototype.pack = function () {
|
||||
if (this.store.logs.error.length > 0) {
|
||||
this.store.log.warning(`One or more errors occured. Not packing pattern parts`)
|
||||
for (const set in this.settings) {
|
||||
if (this.stores[set].logs.error.length > 0) {
|
||||
this.stores[set].log.warning(`One or more errors occured. Not packing pattern parts`)
|
||||
return this
|
||||
}
|
||||
}
|
||||
// First, create all stacks
|
||||
this.stacks = {}
|
||||
for (const [name, part] of Object.entries(this.parts)) {
|
||||
for (const set in this.settings) {
|
||||
for (const [name, part] of Object.entries(this.parts[set])) {
|
||||
const stackName =
|
||||
typeof part.stack === 'function' ? part.stack(this.settings, name) : part.stack
|
||||
this.settings[set].stackPrefix +
|
||||
(typeof part.stack === 'function' ? part.stack(this.settings, name) : part.stack)
|
||||
if (typeof this.stacks[stackName] === 'undefined')
|
||||
this.stacks[stackName] = this.__createStackWithContext(stackName)
|
||||
this.stacks[stackName] = this.__createStackWithContext(stackName, set)
|
||||
this.stacks[stackName].addPart(part)
|
||||
}
|
||||
}
|
||||
|
||||
let bins = []
|
||||
for (const [key, stack] of Object.entries(this.stacks)) {
|
||||
|
@ -572,7 +678,7 @@ Pattern.prototype.pack = function () {
|
|||
stack.attributes.remove('transform')
|
||||
if (!this.isStackHidden(key)) {
|
||||
stack.home()
|
||||
if (this.settings.layout === true)
|
||||
if (this.settings[0].layout === true)
|
||||
bins.push({ id: key, width: stack.width, height: stack.height })
|
||||
else {
|
||||
if (this.width < stack.width) this.width = stack.width
|
||||
|
@ -580,7 +686,7 @@ Pattern.prototype.pack = function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this.settings.layout === true) {
|
||||
if (this.settings[0].layout === true) {
|
||||
let size = pack(bins, { inPlace: true })
|
||||
for (let bin of bins) {
|
||||
this.autoLayout.stacks[bin.id] = { move: {} }
|
||||
|
@ -595,10 +701,10 @@ Pattern.prototype.pack = function () {
|
|||
}
|
||||
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
|
||||
for (let stackId of Object.keys(this.settings.layout.stacks)) {
|
||||
} else if (typeof this.settings[0].layout === 'object') {
|
||||
this.width = this.settings[0].layout.width
|
||||
this.height = this.settings[0].layout.height
|
||||
for (let stackId of Object.keys(this.settings[0].layout.stacks)) {
|
||||
// Some parts are added by late-stage plugins
|
||||
if (this.stacks[stackId]) {
|
||||
let transforms = this.settings.layout.stacks[stackId]
|
||||
|
@ -657,7 +763,7 @@ Pattern.prototype.resolveDependency = function (seen, part, graph = this.depende
|
|||
Pattern.prototype.__addDependency = function (name, part, dep) {
|
||||
this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name])
|
||||
if (typeof this.__parts[dep.name] === 'undefined') {
|
||||
this.config = addPartConfig(this.__parts[dep.name], this.config, this.store)
|
||||
this.config = addPartConfig(this.__parts[dep.name], this.config, this.stores[0])
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -673,12 +779,17 @@ Pattern.prototype.__filterOptionalMeasurements = function () {
|
|||
}
|
||||
|
||||
/** Pre-Resolves part dependencies that are passed in 2022 style */
|
||||
Pattern.prototype.__resolveParts = function (count = 0) {
|
||||
Pattern.prototype.__resolveParts = function (count = 0, distance = 0) {
|
||||
if (count === 0) {
|
||||
for (const part of this.config.parts) {
|
||||
part.distance = distance
|
||||
this.__parts[part.name] = part
|
||||
}
|
||||
}
|
||||
distance++
|
||||
for (const part of this.config.parts) {
|
||||
if (typeof part.distance === 'undefined') part.distance = distance
|
||||
}
|
||||
for (const [name, part] of Object.entries(this.__parts)) {
|
||||
// Hide when hideAll is set
|
||||
if (part.hideAll) part.hide = true
|
||||
|
@ -687,6 +798,7 @@ Pattern.prototype.__resolveParts = function (count = 0) {
|
|||
if (part.hideDependencies || part.hideAll) {
|
||||
part.from.hide = true
|
||||
part.from.hideAll = true
|
||||
part.from.distance = distance
|
||||
}
|
||||
this.__parts[part.from.name] = part.from
|
||||
this.__inject[name] = part.from.name
|
||||
|
@ -695,11 +807,13 @@ Pattern.prototype.__resolveParts = function (count = 0) {
|
|||
if (part.after) {
|
||||
if (Array.isArray(part.after)) {
|
||||
for (const dep of part.after) {
|
||||
dep.distance = distance
|
||||
this.__parts[dep.name] = dep
|
||||
this.__addDependency(name, part, dep)
|
||||
}
|
||||
} else {
|
||||
if (part.hideDependencies) part.after.hide = true
|
||||
part.after.distance = distance
|
||||
this.__parts[part.after.name] = part.after
|
||||
this.__addDependency(name, part, part.after)
|
||||
}
|
||||
|
@ -708,10 +822,10 @@ Pattern.prototype.__resolveParts = function (count = 0) {
|
|||
// Did we discover any new dependencies?
|
||||
const len = Object.keys(this.__parts).length
|
||||
// If so, resolve recursively
|
||||
if (len > count) return this.__resolveParts(len)
|
||||
if (len > count) return this.__resolveParts(len, distance)
|
||||
|
||||
for (const part of Object.values(this.__parts)) {
|
||||
this.config = addPartConfig(part, this.config, this.store)
|
||||
this.config = addPartConfig(part, this.config, this.stores[0])
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -730,7 +844,7 @@ Pattern.prototype.__resolveDependencies = function (graph = false) {
|
|||
if (this.__dependencies[i].indexOf(dependency) === -1)
|
||||
this.__dependencies[i].push(dependency)
|
||||
} else {
|
||||
this.store.log.error('Part dependencies should be a string or an array of strings')
|
||||
this.stores[0].log.error('Part dependencies should be a string or an array of strings')
|
||||
throw new Error('Part dependencies should be a string or an array of strings')
|
||||
}
|
||||
}
|
||||
|
@ -751,17 +865,20 @@ Pattern.prototype.__resolveDependencies = function (graph = false) {
|
|||
* This depends on the 'only' setting and the
|
||||
* configured dependencies.
|
||||
*/
|
||||
Pattern.prototype.needs = function (partName) {
|
||||
Pattern.prototype.needs = function (partName, set = 0) {
|
||||
// If only is unset, all parts are needed
|
||||
if (
|
||||
typeof this.settings.only === 'undefined' ||
|
||||
this.settings.only === false ||
|
||||
(Array.isArray(this.settings.only) && this.settings.only.length === 0)
|
||||
typeof this.settings[set].only === 'undefined' ||
|
||||
this.settings[set].only === false ||
|
||||
(Array.isArray(this.settings[set].only) && this.settings[set].only.length === 0)
|
||||
)
|
||||
return true
|
||||
|
||||
// Make only to always be an array
|
||||
const only = typeof this.settings.only === 'string' ? [this.settings.only] : this.settings.only
|
||||
const only =
|
||||
typeof this.settings[set].only === 'string'
|
||||
? [this.settings[set].only]
|
||||
: this.settings[set].only
|
||||
|
||||
// Walk the only parts, checking each one for a match in its dependencies
|
||||
for (const part of only) {
|
||||
|
@ -779,12 +896,12 @@ Pattern.prototype.needs = function (partName) {
|
|||
/** Determines whether a part is wanted by the user
|
||||
* This depends on the 'only' setting
|
||||
*/
|
||||
Pattern.prototype.wants = function (partName) {
|
||||
Pattern.prototype.wants = function (partName, set = 0) {
|
||||
// Hidden parts are not wanted
|
||||
if (this.isHidden(partName)) return false
|
||||
else if (typeof this.settings.only === 'string') return this.settings.only === partName
|
||||
else if (Array.isArray(this.settings.only)) {
|
||||
for (const part of this.settings.only) {
|
||||
else if (typeof this.settings[set].only === 'string') return this.settings[set].only === partName
|
||||
else if (Array.isArray(this.settings[set].only)) {
|
||||
for (const part of this.settings[set].only) {
|
||||
if (part === partName) return true
|
||||
}
|
||||
return false
|
||||
|
@ -838,12 +955,12 @@ Pattern.prototype.getRenderProps = function () {
|
|||
props.height = this.height
|
||||
props.autoLayout = this.autoLayout
|
||||
props.settings = this.settings
|
||||
props.logs = {
|
||||
debug: this.store.logs.debug,
|
||||
info: this.store.logs.info,
|
||||
error: this.store.logs.error,
|
||||
warning: this.store.logs.warning,
|
||||
}
|
||||
props.logs = this.stores.map((store) => ({
|
||||
debug: store.logs.debug,
|
||||
info: store.logs.info,
|
||||
error: store.logs.error,
|
||||
warning: store.logs.warning,
|
||||
}))
|
||||
props.parts = {}
|
||||
for (let p in this.parts) {
|
||||
if (this.parts[p].render) {
|
||||
|
@ -856,6 +973,7 @@ Pattern.prototype.getRenderProps = function () {
|
|||
width: this.parts[p].width,
|
||||
bottomRight: this.parts[p].bottomRight,
|
||||
topLeft: this.parts[p].topLeft,
|
||||
store: this.stores[this.parts[p].set],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -870,8 +988,14 @@ Pattern.prototype.getRenderProps = function () {
|
|||
}
|
||||
|
||||
// Merges settings object with default settings
|
||||
Pattern.prototype.__applySettings = function (settings) {
|
||||
this.settings = { ...loadPatternDefaults(), ...settings }
|
||||
Pattern.prototype.__applySettings = function (sets) {
|
||||
if (!Array.isArray(sets)) throw 'Sets should be an array of settings objects'
|
||||
if (sets.length === 0) sets.push({}) // Required to load default settings
|
||||
this.settings = []
|
||||
for (const set in sets) {
|
||||
this.settings.push({ ...loadPatternDefaults(), ...sets[set] })
|
||||
if (set > 0) this.stores.push(new Store())
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -76,8 +76,11 @@ Stack.prototype.home = function () {
|
|||
if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0
|
||||
|
||||
// Add margin
|
||||
let margin = this.context.settings.margin
|
||||
if (this.context.settings.paperless && margin < 10) margin = 10
|
||||
let margin = 0
|
||||
for (const set in this.context.settings) {
|
||||
if (this.context.settings[set].margin > margin) margin = this.context.settings[set].margin
|
||||
if (this.context.settings[set].paperless && margin < 10) margin = 10
|
||||
}
|
||||
this.topLeft.x -= margin
|
||||
this.topLeft.y -= margin
|
||||
this.bottomRight.x += margin
|
||||
|
@ -90,7 +93,9 @@ Stack.prototype.home = function () {
|
|||
this.height = this.bottomRight.y - this.topLeft.y
|
||||
|
||||
// Add transform
|
||||
this.anchor = this.getAnchor()
|
||||
//this.anchor = this.getAnchor()
|
||||
// FIXME: Can we be certain this is always (0,0) /
|
||||
this.anchor = new Point(0, 0)
|
||||
|
||||
if (this.topLeft.x === this.anchor.x && this.topLeft.y === this.anchor.y) return this
|
||||
else {
|
||||
|
|
|
@ -15,7 +15,7 @@ export function isCoord(value) {
|
|||
|
||||
/** Returns internal hook name for a macro */
|
||||
export function macroName(name) {
|
||||
return `_macro_${name}`
|
||||
return `__macro_${name}`
|
||||
}
|
||||
|
||||
/** Find intersection of two (endless) lines */
|
||||
|
@ -441,8 +441,14 @@ export function addNonEnumProp(obj, name, value) {
|
|||
const addPartOptions = (part, config, store) => {
|
||||
if (part.options) {
|
||||
for (const optionName in part.options) {
|
||||
store.log.debug(`Config resolver: Option __${optionName}__ in ${part.name}`)
|
||||
if (!config.optionDistance[optionName]) {
|
||||
config.optionDistance[optionName] = part.distance
|
||||
config.options[optionName] = part.options[optionName]
|
||||
store.log.debug(`🔵 __${optionName}__ option loaded from \`${part.name}\``)
|
||||
} else if (config.optionDistance[optionName] > part.distance) {
|
||||
config.options[optionName] = part.options[optionName]
|
||||
store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (part.from) addPartOptions(part.from, config, store)
|
||||
|
@ -460,8 +466,10 @@ const addPartMeasurements = (part, config, store, list = false) => {
|
|||
if (!list) list = config.measurements ? [...config.measurements] : []
|
||||
if (part.measurements) {
|
||||
for (const m of part.measurements) {
|
||||
if (list.indexOf(m) === -1) {
|
||||
list.push(m)
|
||||
store.log.debug(`Config resolver: Measurement __${m}__ is required in ${part.name}`)
|
||||
store.log.debug(`🟠 __${m}__ measurement is required in \`${part.name}\``)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (part.from) addPartMeasurements(part.from, config, store, list)
|
||||
|
@ -484,8 +492,10 @@ const addPartOptionalMeasurements = (part, config, store, list = false) => {
|
|||
for (const m of part.optionalMeasurements) {
|
||||
// Don't add it's a required measurement for another part
|
||||
if (config.measurements.indexOf(m) === -1) {
|
||||
store.log.debug(`Config resolver: Measurement __${m}__ is optional in ${part.name}`)
|
||||
if (list.indexOf(m) === -1) {
|
||||
list.push(m)
|
||||
store.log.debug(`🟡 __${m}__ measurement is optional in \`${part.name}\``)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -514,7 +524,7 @@ export const addPartPlugins = (part, config, store) => {
|
|||
const pluginObj = { ...plugin[0], data: plugin[1] }
|
||||
plugin = pluginObj
|
||||
}
|
||||
store.log.debug(`Config resolver: Plugin __${plugin.name}__ in ${part.name}`)
|
||||
store.log.debug(`🔌 __${plugin.name}__ plugin in \`${part.name}\``)
|
||||
// Do not overwrite an existing plugin with a conditional plugin unless it is also conditional
|
||||
if (plugin.plugin && plugin.condition) {
|
||||
if (plugins[plugin.plugin.name]?.condition) {
|
||||
|
@ -538,7 +548,9 @@ export const addPartPlugins = (part, config, store) => {
|
|||
}
|
||||
|
||||
export const addPartConfig = (part, config, store) => {
|
||||
if (part.resolved) return config
|
||||
// Add parts, using set to keep them unique in the array
|
||||
part.resolved = true
|
||||
config.parts = [...new Set(config.parts).add(part)]
|
||||
config = addPartOptions(part, config, store)
|
||||
config = addPartMeasurements(part, config, store)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue