Merge pull request #3835 from eriese/eriese-core-refactor
refactor (core) separate various Pattern concerns into helper classes
This commit is contained in:
commit
f47047249d
19 changed files with 1491 additions and 1174 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Pattern } from './pattern.mjs'
|
import { Pattern } from './pattern/index.mjs'
|
||||||
import { __loadDesignDefaults } from './config.mjs'
|
import { __loadDesignDefaults } from './config.mjs'
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Bezier } from 'bezier-js'
|
import { Bezier } from 'bezier-js'
|
||||||
import { Attributes } from './attributes.mjs'
|
import { Attributes } from './attributes.mjs'
|
||||||
import { Design } from './design.mjs'
|
import { Design } from './design.mjs'
|
||||||
import { Pattern } from './pattern.mjs'
|
import { Pattern } from './pattern/index.mjs'
|
||||||
import { Part } from './part.mjs'
|
import { Part } from './part.mjs'
|
||||||
import { Point } from './point.mjs'
|
import { Point } from './point.mjs'
|
||||||
import { Path } from './path.mjs'
|
import { Path } from './path.mjs'
|
||||||
import { Snippet } from './snippet.mjs'
|
import { Snippet } from './snippet.mjs'
|
||||||
import { Store } from './store.mjs'
|
import { Store } from './store.mjs'
|
||||||
import { hidePresets } from './pattern-config.mjs'
|
import { hidePresets } from './pattern/pattern-config.mjs'
|
||||||
import {
|
import {
|
||||||
beamIntersectsCircle,
|
beamIntersectsCircle,
|
||||||
beamIntersectsX,
|
beamIntersectsX,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
461
packages/core/src/pattern/index.mjs
Normal file
461
packages/core/src/pattern/index.mjs
Normal file
|
@ -0,0 +1,461 @@
|
||||||
|
import { Attributes } from '../attributes.mjs'
|
||||||
|
import { __addNonEnumProp } from '../utils.mjs'
|
||||||
|
import { Point } from '../point.mjs'
|
||||||
|
import { Path } from '../path.mjs'
|
||||||
|
import { Snippet } from '../snippet.mjs'
|
||||||
|
import { Store } from '../store.mjs'
|
||||||
|
import { version } from '../../data.mjs'
|
||||||
|
import { __loadPatternDefaults } from '../config.mjs'
|
||||||
|
import { PatternConfig } from './pattern-config.mjs'
|
||||||
|
import { PatternDrafter } from './pattern-drafter.mjs'
|
||||||
|
import { PatternSampler } from './pattern-sampler.mjs'
|
||||||
|
import { PatternPlugins } from './pattern-plugins.mjs'
|
||||||
|
import { PatternRenderer } from './pattern-renderer.mjs'
|
||||||
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// CONSTRUCTOR //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for a Pattern
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {object} config - The Design config
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
export function Pattern(designConfig = {}) {
|
||||||
|
// Enumerable properties
|
||||||
|
this.designConfig = cloneDeep(designConfig) // The design configuration (unresolved)
|
||||||
|
this.config = {} // Will hold the resolved pattern after calling __init()
|
||||||
|
this.store = new Store() // Pattern-wide store
|
||||||
|
this.setStores = [] // Per-set stores
|
||||||
|
|
||||||
|
// Non-enumerable properties
|
||||||
|
__addNonEnumProp(this, 'width', 0)
|
||||||
|
__addNonEnumProp(this, 'height', 0)
|
||||||
|
__addNonEnumProp(this, 'autoLayout', { stacks: {} })
|
||||||
|
__addNonEnumProp(this, 'is', '')
|
||||||
|
__addNonEnumProp(this, 'Point', Point)
|
||||||
|
__addNonEnumProp(this, 'Path', Path)
|
||||||
|
__addNonEnumProp(this, 'Snippet', Snippet)
|
||||||
|
__addNonEnumProp(this, 'Attributes', Attributes)
|
||||||
|
__addNonEnumProp(this, '__initialized', false)
|
||||||
|
__addNonEnumProp(this, 'config.parts', {})
|
||||||
|
__addNonEnumProp(this, 'config.resolvedDependencies', {})
|
||||||
|
|
||||||
|
__addNonEnumProp(this, 'plugins', new PatternPlugins(this))
|
||||||
|
__addNonEnumProp(this, '__configResolver', new PatternConfig(this)) // handles config resolution during __init() as well as runtime part adding
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// PUBLIC METHODS //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Setup //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows adding parts to the config at runtime
|
||||||
|
*
|
||||||
|
* @param {object} part - The part to add
|
||||||
|
* @param {boolean} resolveImmediately - Should the part be resolved now, or wait until the next call to {@link __init()}?
|
||||||
|
* It is useful to resolve immediately if one part is being added at runtime
|
||||||
|
* It might be useful to not resolve immediately if a number of parts will be added over multiple calls
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.addPart = function (part, resolveImmediately = true) {
|
||||||
|
if (
|
||||||
|
this.__configResolver.isPartValid(part) &&
|
||||||
|
!this.designConfig.parts.find((p) => p.name == part.name)
|
||||||
|
) {
|
||||||
|
this.store.log.debug(`Adding Part \`${part.name}\` at runtime`)
|
||||||
|
this.designConfig.parts.push(part)
|
||||||
|
if (resolveImmediately) {
|
||||||
|
if (this.__configResolver.addPart(part) && typeof this.draftQueue !== 'undefined')
|
||||||
|
this.draftQueue.addPart(part.name)
|
||||||
|
} else this.__initialized = false
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the initialized configuration
|
||||||
|
*
|
||||||
|
* @return {object} config - The initialized config
|
||||||
|
*/
|
||||||
|
Pattern.prototype.getConfig = function () {
|
||||||
|
return this.__init().config
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Plugin and Hook Handling //
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a lifecycle hook method to the pattern
|
||||||
|
*
|
||||||
|
* @param {string} hook - Name of the lifecycle hook
|
||||||
|
* @param {function} method - The method to run
|
||||||
|
* @param {object} data - Any data to pass to the hook method
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.on = function (hook, method, data) {
|
||||||
|
this.plugins.on(hook, method, data)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin
|
||||||
|
*
|
||||||
|
* @param {object} plugin - The plugin to load
|
||||||
|
* @param {object} data - Any data to pass to the plugin
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.use = function (plugin, data) {
|
||||||
|
this.plugins.use(plugin, data, this.settings)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Drafting //
|
||||||
|
//////////////
|
||||||
|
/**
|
||||||
|
* Drafts this pattern, aka the raison d'etre of FreeSewing
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.draft = function () {
|
||||||
|
this.__init()
|
||||||
|
new PatternDrafter(this).draft()
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.draftPartForSet = function (partName, set) {
|
||||||
|
this.__init()
|
||||||
|
return new PatternDrafter(this).draftPartForSet(partName, set)
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Rendering //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the pattern to SVG
|
||||||
|
*
|
||||||
|
* @return {string} svg - The rendered SVG
|
||||||
|
*/
|
||||||
|
Pattern.prototype.render = function () {
|
||||||
|
return new PatternRenderer(this).render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns props required to render this pattern through
|
||||||
|
* an external renderer (eg. a React component)
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.getRenderProps = function () {
|
||||||
|
return new PatternRenderer(this).getRenderProps()
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Sampling //
|
||||||
|
//////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles pattern sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.sample = function () {
|
||||||
|
this.__init()
|
||||||
|
const sampleSetting = this.settings[0].sample
|
||||||
|
if (sampleSetting.type === 'option') {
|
||||||
|
return this.sampleOption(sampleSetting.option)
|
||||||
|
} else if (sampleSetting.type === 'measurement') {
|
||||||
|
return this.sampleMeasurement(sampleSetting.measurement)
|
||||||
|
} else if (sampleSetting.type === 'models') {
|
||||||
|
return this.sampleModels(sampleSetting.models, sampleSetting.focus || false)
|
||||||
|
}
|
||||||
|
return this.draft()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles measurement sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.sampleMeasurement = function (measurementName) {
|
||||||
|
return new PatternSampler(this).sampleMeasurement(measurementName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles models sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.sampleModels = function (models, focus = false) {
|
||||||
|
return new PatternSampler(this).sampleModels(models, focus)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles option sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.sampleOption = function (optionName) {
|
||||||
|
return new PatternSampler(this).sampleOption(optionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// PRIVATE METHODS //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Setup //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges (sets of) settings with the default settings
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} sets - An array of settings objects
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
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 (let i = 0; i < sets.length; i++) {
|
||||||
|
// Don't mutate the input itself
|
||||||
|
const set = { ...sets[i] }
|
||||||
|
if (!set.options) set.options = {}
|
||||||
|
if (!set.measurements) set.measurements = {}
|
||||||
|
this.settings.push({
|
||||||
|
...__loadPatternDefaults(),
|
||||||
|
...set,
|
||||||
|
// Force creation of a new objects
|
||||||
|
// so we don't reference the original
|
||||||
|
options: { ...set.options },
|
||||||
|
measurements: { ...set.measurements },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a store for a set (of settings)
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Store} store - A new store populated with relevant data/methods
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__createSetStore = function () {
|
||||||
|
const store = new Store()
|
||||||
|
store.set('data', this.store.data)
|
||||||
|
store.extend([...this.plugins.__storeMethods])
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the pattern coniguration and settings
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__init = function () {
|
||||||
|
if (this.__initialized) return this
|
||||||
|
|
||||||
|
this.__runHooks('preInit')
|
||||||
|
// Say hello
|
||||||
|
this.store.log.info(
|
||||||
|
`New \`${this.designConfig.data?.name || 'No Name'}:` +
|
||||||
|
`${this.designConfig.data?.version || 'No version'}\` ` +
|
||||||
|
`pattern using \`@freesewing/core:${version}\``
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
// Resolve parts
|
||||||
|
this.designConfig.parts.forEach((p) => this.__configResolver.addPart(p))
|
||||||
|
|
||||||
|
// Print final part distances.
|
||||||
|
this.__configResolver.logPartDistances()
|
||||||
|
|
||||||
|
// get the config from the resolver
|
||||||
|
this.config = this.__configResolver.asConfig()
|
||||||
|
|
||||||
|
// load resolved plugins
|
||||||
|
this.plugins.loadConfigPlugins(this.config, this.settings)
|
||||||
|
|
||||||
|
// Make config data available in store
|
||||||
|
if (this.designConfig.data) this.store.set('data', this.designConfig.data)
|
||||||
|
|
||||||
|
// Merges default options with user provided ones
|
||||||
|
this.__loadOptionDefaults()
|
||||||
|
|
||||||
|
this.store.log.info(`Pattern initialized. Draft order is: ${this.config.draftOrder.join(', ')}`)
|
||||||
|
|
||||||
|
this.__runHooks('postInit')
|
||||||
|
this.__initialized = true
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges defaults for options with user-provided options
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__loadOptionDefaults = function () {
|
||||||
|
if (!this.config.options) this.config.options = {}
|
||||||
|
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[i].options[name] === 'undefined') {
|
||||||
|
if (typeof option === 'object') {
|
||||||
|
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)
|
||||||
|
throw new Error(err)
|
||||||
|
}
|
||||||
|
} else this.settings[i].options[name] = option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Hooks //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs subscriptions to a given lifecycle hook
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} hookName - Name of the lifecycle hook
|
||||||
|
* @param {obhect} data - Any data to pass to the hook method
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__runHooks = function (hookName, data = false) {
|
||||||
|
if (data === false) data = this
|
||||||
|
let hooks = this.plugins.hooks[hookName]
|
||||||
|
if (hooks.length > 0) {
|
||||||
|
this.store.log.debug(`Running \`${hookName}\` hooks`)
|
||||||
|
for (let hook of hooks) {
|
||||||
|
hook.method(data, hook.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Config Evaluation //
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a part is hidden in the config
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} partName - Name of the part to check
|
||||||
|
* @return {bool} hidden - true if the part is hidden, or false if not
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__isPartHidden = function (partName) {
|
||||||
|
const partHidden = this.parts?.[this.activeSet]?.[partName]?.hidden || false
|
||||||
|
if (Array.isArray(this.settings[this.activeSet || 0].only)) {
|
||||||
|
if (this.settings[this.activeSet || 0].only.includes(partName)) return partHidden
|
||||||
|
}
|
||||||
|
if (this.config.partHide?.[partName]) return true
|
||||||
|
|
||||||
|
return partHidden
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a stack is hidden in the config
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} stackName - Name of the stack to check
|
||||||
|
* @return {bool} hidden - true if the part is hidden, or false if not
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__isStackHidden = function (stackName) {
|
||||||
|
if (!this.stacks[stackName]) return true
|
||||||
|
const parts = this.stacks[stackName].getPartNames()
|
||||||
|
for (const partName of parts) {
|
||||||
|
if (!this.__isPartHidden(partName)) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a part is needed, depending on the 'only' setting and the configured dependencies
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} partName - Name of the part
|
||||||
|
* @param {int} set - The index of the set of settings
|
||||||
|
* @return {bool} needs - true if the part is needed, or false if not
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__needs = function (partName, set = 0) {
|
||||||
|
// If only is unset, all parts are needed
|
||||||
|
if (
|
||||||
|
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[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) {
|
||||||
|
if (part === partName) return true
|
||||||
|
if (this.config.resolvedDependencies[part]?.indexOf(partName) !== -1) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a part is wanted, depending on the 'only' setting and the configured dependencies
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} partName - Name of the part
|
||||||
|
* @param {int} set - The index of the set of settings
|
||||||
|
* @return {bool} wants - true if the part is wanted, or false if not
|
||||||
|
*/
|
||||||
|
Pattern.prototype.__wants = function (partName, set = 0) {
|
||||||
|
// Hidden parts are not wanted
|
||||||
|
if (this.__isPartHidden(partName)) return false
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { __addNonEnumProp } from './utils.mjs'
|
import { __addNonEnumProp } from '../utils.mjs'
|
||||||
|
import { getPluginName } from './pattern-plugins.mjs'
|
||||||
|
|
||||||
export const hidePresets = {
|
export const hidePresets = {
|
||||||
HIDE_ALL: {
|
HIDE_ALL: {
|
||||||
|
@ -13,17 +14,6 @@ export const hidePresets = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the given plugin config
|
|
||||||
*
|
|
||||||
* @param {(Object|Object[])} plugin the plugin to get the name of
|
|
||||||
* @return {(string|false)} the name, or false if there isn't one
|
|
||||||
*/
|
|
||||||
export function getPluginName(plugin) {
|
|
||||||
const toCheck = Array.isArray(plugin) ? plugin[0] : plugin
|
|
||||||
return toCheck.name || toCheck.plugin?.name || false
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// CONSTRUCTOR //
|
// CONSTRUCTOR //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
@ -76,7 +66,7 @@ export function PatternConfig(pattern) {
|
||||||
const DISTANCE_DEBUG = false
|
const DISTANCE_DEBUG = false
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// PUBLIC METHODs //
|
// PUBLIC METHODS //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
241
packages/core/src/pattern/pattern-drafter.mjs
Normal file
241
packages/core/src/pattern/pattern-drafter.mjs
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import { PatternDraftQueue } from './pattern-draft-queue.mjs'
|
||||||
|
import { Part } from '../part.mjs'
|
||||||
|
import { __macroName } from '../utils.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to handle drafting a pattern
|
||||||
|
* @param {Pattern} pattern the pattern to draft
|
||||||
|
*/
|
||||||
|
export function PatternDrafter(pattern) {
|
||||||
|
this.pattern = pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drafts this pattern, aka the raison d'etre of FreeSewing
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.draft = function () {
|
||||||
|
this.pattern.draftQueue = new PatternDraftQueue(this.pattern)
|
||||||
|
this.pattern.__runHooks('preDraft')
|
||||||
|
// Keep container for drafted parts fresh
|
||||||
|
this.pattern.parts = []
|
||||||
|
|
||||||
|
// Iterate over the provided sets of settings (typically just one)
|
||||||
|
for (const set in this.pattern.settings) {
|
||||||
|
this.pattern.setStores[set] = this.pattern.__createSetStore()
|
||||||
|
this.__useSet(set)
|
||||||
|
|
||||||
|
this.activeStore.log.debug(`Initialized store for set ${set}`)
|
||||||
|
this.pattern.__runHooks('preSetDraft')
|
||||||
|
this.activeStore.log.debug(`📐 Drafting pattern for set ${set}`)
|
||||||
|
|
||||||
|
// Create parts container
|
||||||
|
this.pattern.parts[set] = {}
|
||||||
|
|
||||||
|
// Handle snap for pct options
|
||||||
|
this.__loadAbsoluteOptionsSet(set)
|
||||||
|
|
||||||
|
// draft all the parts for this set
|
||||||
|
this.pattern.draftQueue.start()
|
||||||
|
while (this.pattern.draftQueue.hasNext()) {
|
||||||
|
const partName = this.pattern.draftQueue.next()
|
||||||
|
if (this.pattern.__needs(partName, set)) {
|
||||||
|
this.draftPartForSet(partName, set)
|
||||||
|
} else {
|
||||||
|
this.activeStore.log.debug(`Part \`${partName}\` is not needed. Skipping part`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.pattern.__runHooks('postSetDraft')
|
||||||
|
}
|
||||||
|
this.pattern.__runHooks('postDraft')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draft and save a part for the given set of settings
|
||||||
|
* @param {String} partName the name of the part
|
||||||
|
* @param {number} set the index of the settings set
|
||||||
|
* @return {Part} the drafted part, which is also stored in the Pattern
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.draftPartForSet = function (partName, set) {
|
||||||
|
// gotta protect against attacks
|
||||||
|
if (set === '__proto__') {
|
||||||
|
throw new Error('malicious attempt at altering Object.prototype. Stopping action')
|
||||||
|
}
|
||||||
|
this.__useSet(set)
|
||||||
|
this.__createPartForSet(partName, set)
|
||||||
|
|
||||||
|
// don't draft what can't be drafted
|
||||||
|
const configPart = this.pattern.config.parts?.[partName]
|
||||||
|
if (typeof configPart?.draft !== 'function') {
|
||||||
|
this.activeStore.log.error(
|
||||||
|
`Unable to draft pattern part __${partName}__. Part.draft() is not callable`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the active part for use by hooks and such
|
||||||
|
this.pattern.activePart = partName
|
||||||
|
this.activeStore.set('activePart', partName)
|
||||||
|
try {
|
||||||
|
this.pattern.__runHooks('prePartDraft')
|
||||||
|
// draft
|
||||||
|
const result = configPart.draft(this.pattern.parts[set][partName].shorthand())
|
||||||
|
|
||||||
|
if (typeof result === 'undefined') {
|
||||||
|
this.activeStore.log.error(
|
||||||
|
`Result of drafting part ${partName} was undefined. Did you forget to return the part?`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// hide if necessary
|
||||||
|
if (!this.pattern.__wants(partName, set)) result.hide()
|
||||||
|
this.pattern.__runHooks('postPartDraft')
|
||||||
|
// save the result
|
||||||
|
this.pattern.parts[set][partName] = result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
this.activeStore.log.error([`Unable to draft part \`${partName}\` (set ${set})`, err])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a part for the given set of settings.
|
||||||
|
* Handles injection
|
||||||
|
* @param {String} partName the name of the part to create
|
||||||
|
* @param {Number} set the settings index
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.__createPartForSet = function (partName, set = 0) {
|
||||||
|
// gotta protect against attacks
|
||||||
|
if (set === '__proto__') {
|
||||||
|
throw new Error('malicious attempt at altering Object.prototype. Stopping action')
|
||||||
|
}
|
||||||
|
// Create parts
|
||||||
|
this.activeStore.log.debug(`📦 Creating part \`${partName}\` (set ${set})`)
|
||||||
|
this.pattern.parts[set][partName] = this.__createPartWithContext(partName, set)
|
||||||
|
|
||||||
|
// Handle inject/inheritance
|
||||||
|
const parent = this.pattern.config.inject[partName]
|
||||||
|
if (typeof parent === 'string') {
|
||||||
|
this.activeStore.log.debug(`Creating part \`${partName}\` from part \`${parent}\``)
|
||||||
|
try {
|
||||||
|
this.pattern.parts[set][partName].__inject(this.pattern.parts[set][parent])
|
||||||
|
} catch (err) {
|
||||||
|
this.activeStore.log.error([
|
||||||
|
`Could not inject part \`${parent}\` into part \`${partName}\``,
|
||||||
|
err,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Part instance and populates it with the pattern context
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} name - The name of the part
|
||||||
|
* @param {int} set - The index of the settings set in the list of sets
|
||||||
|
* @return {Part} part - The instantiated Part
|
||||||
|
*/
|
||||||
|
PatternDrafter.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.pattern.config.parts[name]?.stack || name
|
||||||
|
part.context = {
|
||||||
|
parts: this.pattern.parts[set],
|
||||||
|
config: this.pattern.config,
|
||||||
|
settings: this.pattern.settings[set],
|
||||||
|
store: this.pattern.setStores[set],
|
||||||
|
macros: this.pattern.plugins.macros,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pattern.settings[set]?.partClasses) {
|
||||||
|
part.attr('class', this.pattern.settings[set].partClasses)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const macro in this.pattern.plugins.macros) {
|
||||||
|
part[__macroName(macro)] = this.pattern.plugins.macros[macro]
|
||||||
|
}
|
||||||
|
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of settings.absoluteOptions objects for sampling a list option
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} optionName - Name of the option to sample
|
||||||
|
* @return {Array} sets - The list of settings objects
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.__loadAbsoluteOptionsSet = function (set) {
|
||||||
|
for (const optionName in this.pattern.settings[set].options) {
|
||||||
|
const option = this.pattern.config.options[optionName]
|
||||||
|
if (
|
||||||
|
typeof option !== 'undefined' &&
|
||||||
|
typeof option.snap !== 'undefined' &&
|
||||||
|
option.toAbs instanceof Function
|
||||||
|
) {
|
||||||
|
this.pattern.settings[set].absoluteOptions[optionName] = this.__snappedPercentageOption(
|
||||||
|
optionName,
|
||||||
|
set
|
||||||
|
)
|
||||||
|
this.pattern.setStores[set].log.debug(
|
||||||
|
`🧲 Snapped __${optionName}__ to \`${this.pattern.settings[set].absoluteOptions[optionName]}\` for set __${set}__`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the absolute value of a snapped percentage option
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} optionName - The name of the option
|
||||||
|
* @param {int} set - The index of the set in the list of settings
|
||||||
|
* @return {float} abs - The absolute value of the snapped option
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.__snappedPercentageOption = function (optionName, set) {
|
||||||
|
const conf = this.pattern.config.options[optionName]
|
||||||
|
const abs = conf.toAbs(this.pattern.settings[set].options[optionName], this.pattern.settings[set])
|
||||||
|
// Handle units-specific config - Side-step immutability for the snap conf
|
||||||
|
let snapConf = conf.snap
|
||||||
|
if (!Array.isArray(snapConf) && snapConf.metric && snapConf.imperial)
|
||||||
|
snapConf = snapConf[this.pattern.settings[set].units]
|
||||||
|
// Simple steps
|
||||||
|
if (typeof snapConf === 'number') return Math.ceil(abs / snapConf) * snapConf
|
||||||
|
// List of snaps
|
||||||
|
if (Array.isArray(snapConf) && snapConf.length > 1) {
|
||||||
|
for (const snap of snapConf
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.map((snap, i) => {
|
||||||
|
const margin =
|
||||||
|
i < snapConf.length - 1
|
||||||
|
? (snapConf[Number(i) + 1] - snap) / 2 // Look forward
|
||||||
|
: (snap - snapConf[i - 1]) / 2 // Final snap, look backward
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: snap - margin,
|
||||||
|
max: snap + Number(margin),
|
||||||
|
snap,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
if (abs <= snap.max && abs >= snap.min) return snap.snap
|
||||||
|
}
|
||||||
|
|
||||||
|
return abs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active set
|
||||||
|
* @param {Number} set the set to use
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
PatternDrafter.prototype.__useSet = function (set = 0) {
|
||||||
|
this.pattern.activeSet = set
|
||||||
|
this.activeStore = this.pattern.setStores[set]
|
||||||
|
}
|
189
packages/core/src/pattern/pattern-plugins.mjs
Normal file
189
packages/core/src/pattern/pattern-plugins.mjs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import { Hooks } from '../hooks.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the given plugin config
|
||||||
|
*
|
||||||
|
* @param {(Object|Object[])} plugin the plugin to get the name of
|
||||||
|
* @return {(string|false)} the name, or false if there isn't one
|
||||||
|
*/
|
||||||
|
export function getPluginName(plugin) {
|
||||||
|
const toCheck = Array.isArray(plugin) ? plugin[0] : plugin
|
||||||
|
return toCheck.name || toCheck.plugin?.name || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for managing the plugins and lifecycle hooks of a pattern
|
||||||
|
* @param {Pattern} pattern the pattern to manage
|
||||||
|
*/
|
||||||
|
export function PatternPlugins(pattern) {
|
||||||
|
this.store = pattern.store
|
||||||
|
|
||||||
|
this.plugins = {}
|
||||||
|
this.hooks = new Hooks()
|
||||||
|
this.macros = {}
|
||||||
|
this.__storeMethods = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the plugins that are part of the config
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.loadConfigPlugins = function (config, settings) {
|
||||||
|
if (!config.plugins) return this
|
||||||
|
for (const plugin in config.plugins)
|
||||||
|
this.use(config.plugins[plugin], config.plugins[plugin]?.data, settings)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a lifecycle hook method to the pattern
|
||||||
|
*
|
||||||
|
* @param {string} hook - Name of the lifecycle hook
|
||||||
|
* @param {function} method - The method to run
|
||||||
|
* @param {object} data - Any data to pass to the hook method
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.on = function (hook, method, data) {
|
||||||
|
for (const added of this.hooks[hook]) {
|
||||||
|
// Don't add it twice
|
||||||
|
if (added.method === method) return this
|
||||||
|
}
|
||||||
|
this.hooks[hook].push({ method, data })
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin
|
||||||
|
*
|
||||||
|
* @param {object} plugin - The plugin to load
|
||||||
|
* @param {object} data - Any data to pass to the plugin
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.use = function (plugin, data, settings = [{}]) {
|
||||||
|
const name = getPluginName(plugin)
|
||||||
|
if (!this.plugins?.[name])
|
||||||
|
return plugin.plugin && plugin.condition
|
||||||
|
? this.__useIf(plugin, data, settings) // Conditional plugin
|
||||||
|
: this.__loadPlugin(plugin, data) // Regular plugin
|
||||||
|
|
||||||
|
this.store.log.info(`Plugin \`${name}\` was requested, but it's already loaded. Skipping.`)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} plugin - The plugin object, or an object with `plugin` and `condition` keys
|
||||||
|
* @param {object} data - Any plugin data to load
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__loadPlugin = function (plugin, data) {
|
||||||
|
const name = getPluginName(plugin)
|
||||||
|
this.plugins[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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin's hooks
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} plugin - The plugin object
|
||||||
|
* @param {object} data - Any plugin data to load
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__loadPluginHooks = function (plugin, data) {
|
||||||
|
// console.log('hooks', plugin)
|
||||||
|
for (let hook of Object.keys(this.hooks)) {
|
||||||
|
if (typeof plugin.hooks[hook] === 'function') {
|
||||||
|
this.on(hook, plugin.hooks[hook], data)
|
||||||
|
} else if (Array.isArray(plugin.hooks[hook])) {
|
||||||
|
for (let method of plugin.hooks[hook]) {
|
||||||
|
this.on(hook, method, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin's macros
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} plugin - The plugin object
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__loadPluginMacros = function (plugin) {
|
||||||
|
// console.log('macros', plugin)
|
||||||
|
for (let macro in plugin.macros) {
|
||||||
|
if (typeof plugin.macros[macro] === 'function') {
|
||||||
|
this.__macro(macro, plugin.macros[macro])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a plugin's store methods
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} plugin - The plugin object
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__loadPluginStoreMethods = function (plugin) {
|
||||||
|
if (Array.isArray(plugin.store)) {
|
||||||
|
for (const method of plugin.store) this.__storeMethods.add(method)
|
||||||
|
} else this.store.log.warning(`Plugin store methods should be an Array`)
|
||||||
|
|
||||||
|
// console.log('store', plugin, this.__storeMethods)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a method for a macro
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} macro - Name of the macro to run
|
||||||
|
* @param {function} method - The macro method
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__macro = function (key, method) {
|
||||||
|
this.macros[key] = method
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a conditional plugin
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} plugin - An object with `plugin` and `condition` keys
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternPlugins.prototype.__useIf = function (plugin, settings = [{}]) {
|
||||||
|
let load = 0
|
||||||
|
for (const set of settings) {
|
||||||
|
if (plugin.condition(set)) load++
|
||||||
|
}
|
||||||
|
if (load > 0) {
|
||||||
|
this.store.log.info(
|
||||||
|
`Condition met: Loaded plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
||||||
|
)
|
||||||
|
this.__loadPlugin(plugin.plugin, plugin.data)
|
||||||
|
} else {
|
||||||
|
this.store.log.info(
|
||||||
|
`Condition not met: Skipped loading plugin \`${plugin.plugin.name}:${plugin.plugin.version}\``
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
187
packages/core/src/pattern/pattern-renderer.mjs
Normal file
187
packages/core/src/pattern/pattern-renderer.mjs
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
import { Svg } from '../svg.mjs'
|
||||||
|
import { Stack } from '../stack.mjs'
|
||||||
|
import pack from 'bin-pack-with-constraints'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for handling layout and rendering for a pattern
|
||||||
|
* @param {Pattern} pattern the pattern to layout or render
|
||||||
|
*/
|
||||||
|
export function PatternRenderer(pattern) {
|
||||||
|
this.pattern = pattern
|
||||||
|
this.autoLayout = pattern.autoLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the pattern to SVG
|
||||||
|
*
|
||||||
|
* @return {string} svg - The rendered SVG
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.render = function () {
|
||||||
|
this.__startRender()
|
||||||
|
this.pattern.svg = this.svg
|
||||||
|
return this.svg.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns props required to render this pattern through
|
||||||
|
* an external renderer (eg. a React component)
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.getRenderProps = function () {
|
||||||
|
this.pattern.store.log.info('Gathering render props')
|
||||||
|
// Run pre-render hook
|
||||||
|
this.__startRender()
|
||||||
|
this.svg.__runHooks('preRender')
|
||||||
|
|
||||||
|
let props = {
|
||||||
|
svg: this.svg,
|
||||||
|
width: this.pattern.width,
|
||||||
|
height: this.pattern.height,
|
||||||
|
autoLayout: this.pattern.autoLayout,
|
||||||
|
settings: this.pattern.settings,
|
||||||
|
parts: [],
|
||||||
|
stacks: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const partSet of this.pattern.parts) {
|
||||||
|
const setPartProps = {}
|
||||||
|
for (let partName in partSet) {
|
||||||
|
const part = partSet[partName]
|
||||||
|
if (!part.hidden) {
|
||||||
|
setPartProps[partName] = {
|
||||||
|
...partSet[partName].asProps(),
|
||||||
|
store: this.pattern.setStores[part.set],
|
||||||
|
}
|
||||||
|
} else if (this.pattern.setStores[part.set]) {
|
||||||
|
this.pattern.setStores[part.set].log.info(
|
||||||
|
`Part ${partName} is hidden in set ${part.set}. Not adding to render props`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.parts.push(setPartProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let s in this.pattern.stacks) {
|
||||||
|
if (!this.pattern.__isStackHidden(s)) {
|
||||||
|
props.stacks[s] = this.pattern.stacks[s].asProps()
|
||||||
|
} else this.pattern.store.log.info(`Stack ${s} is hidden. Skipping in render props.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.logs = {
|
||||||
|
pattern: this.pattern.store.logs,
|
||||||
|
sets: this.pattern.setStores.map((store) => store.logs),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.svg.__runHooks('postRender')
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternRenderer.prototype.__startRender = function () {
|
||||||
|
this.svg = new Svg(this.pattern)
|
||||||
|
this.svg.hooks = this.pattern.plugins.hooks
|
||||||
|
this.__pack()
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternRenderer.prototype.__stack = function () {
|
||||||
|
// First, create all stacks
|
||||||
|
this.stacks = {}
|
||||||
|
const settings = this.pattern.settings
|
||||||
|
for (const set in settings) {
|
||||||
|
for (const [name, part] of Object.entries(this.pattern.parts[set])) {
|
||||||
|
const stackName =
|
||||||
|
settings[set].stackPrefix +
|
||||||
|
(typeof part.stack === 'function' ? part.stack(settings[set], name) : part.stack)
|
||||||
|
if (typeof this.stacks[stackName] === 'undefined')
|
||||||
|
this.stacks[stackName] = this.__createStackWithContext(stackName, set)
|
||||||
|
this.stacks[stackName].addPart(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pattern.stacks = this.stacks
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Packs stacks in a 2D space and sets pattern size
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Pattern} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.__pack = function () {
|
||||||
|
this.pattern.__runHooks('preLayout')
|
||||||
|
const { settings, setStores, activeSet } = this.pattern
|
||||||
|
for (const set in settings) {
|
||||||
|
if (setStores[set].logs.error.length > 0) {
|
||||||
|
setStores[set].log.warning(`One or more errors occured. Not packing pattern parts`)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__stack()
|
||||||
|
|
||||||
|
let bins = []
|
||||||
|
for (const [key, stack] of Object.entries(this.stacks)) {
|
||||||
|
// Avoid multiple render calls to cause addition of transforms
|
||||||
|
stack.attributes.remove('transform')
|
||||||
|
if (!this.pattern.__isStackHidden(key)) {
|
||||||
|
stack.home()
|
||||||
|
if (settings[activeSet].layout === true)
|
||||||
|
bins.push({ id: key, width: stack.width, height: stack.height })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settings[activeSet].layout === true) {
|
||||||
|
// some plugins will add a width constraint to the settings, but we can safely pass undefined if not
|
||||||
|
let size = pack(bins, { inPlace: true, maxWidth: settings[0].maxWidth })
|
||||||
|
this.autoLayout.width = size.width
|
||||||
|
this.autoLayout.height = size.height
|
||||||
|
|
||||||
|
for (let bin of bins) {
|
||||||
|
let stack = this.stacks[bin.id]
|
||||||
|
this.autoLayout.stacks[bin.id] = {
|
||||||
|
move: {
|
||||||
|
x: bin.x + stack.layout.move.x,
|
||||||
|
y: bin.y + stack.layout.move.y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const packedLayout =
|
||||||
|
typeof settings[activeSet].layout === 'object' ? settings[activeSet].layout : this.autoLayout
|
||||||
|
|
||||||
|
this.width = packedLayout.width
|
||||||
|
this.height = packedLayout.height
|
||||||
|
for (let stackId of Object.keys(packedLayout.stacks)) {
|
||||||
|
// Some parts are added by late-stage plugins
|
||||||
|
if (this.stacks[stackId]) {
|
||||||
|
let transforms = packedLayout.stacks[stackId]
|
||||||
|
this.stacks[stackId].generateTransform(transforms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pattern.width = this.width
|
||||||
|
this.pattern.height = this.height
|
||||||
|
this.pattern.autoLayout = this.autoLayout
|
||||||
|
this.pattern.__runHooks('postLayout')
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Stack instance and populates it with the pattern context
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} name - The name of the stack
|
||||||
|
* @return {Stack} stack - The instantiated Stack
|
||||||
|
*/
|
||||||
|
PatternRenderer.prototype.__createStackWithContext = function (name) {
|
||||||
|
// Context object to add to Stack closure
|
||||||
|
const stack = new Stack()
|
||||||
|
stack.name = name
|
||||||
|
stack.context = {
|
||||||
|
config: this.pattern.config,
|
||||||
|
settings: this.pattern.settings,
|
||||||
|
setStores: this.pattern.setStores,
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack
|
||||||
|
}
|
216
packages/core/src/pattern/pattern-sampler.mjs
Normal file
216
packages/core/src/pattern/pattern-sampler.mjs
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
/**
|
||||||
|
* A class for handling pattern sampling
|
||||||
|
* @param {Pattern} pattern the pattern that will be sampled
|
||||||
|
*/
|
||||||
|
export function PatternSampler(pattern) {
|
||||||
|
this.pattern = pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles measurement sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.sampleMeasurement = function (measurementName) {
|
||||||
|
this.pattern.store.log.debug(`Sampling measurement \`${measurementName}\``)
|
||||||
|
this.pattern.__runHooks('preSample')
|
||||||
|
this.pattern.__applySettings(this.__measurementSets(measurementName))
|
||||||
|
this.pattern.__init()
|
||||||
|
this.pattern.__runHooks('postSample')
|
||||||
|
|
||||||
|
return this.pattern.draft()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles models sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.sampleModels = function (models, focus = false) {
|
||||||
|
this.pattern.store.log.debug(`Sampling models \`${Object.keys(models).join(', ')}\``)
|
||||||
|
this.pattern.__runHooks('preSample')
|
||||||
|
this.pattern.__applySettings(this.__modelSets(models, focus))
|
||||||
|
this.pattern.__init()
|
||||||
|
this.pattern.__runHooks('postSample')
|
||||||
|
|
||||||
|
return this.pattern.draft()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles option sampling
|
||||||
|
*
|
||||||
|
* @return {object} this - The Pattern instance
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.sampleOption = function (optionName) {
|
||||||
|
this.pattern.store.log.debug(`Sampling option \`${optionName}\``)
|
||||||
|
this.pattern.__runHooks('preSample')
|
||||||
|
this.pattern.__applySettings(this.__optionSets(optionName))
|
||||||
|
this.pattern.__init()
|
||||||
|
this.pattern.__runHooks('postSample')
|
||||||
|
|
||||||
|
return this.pattern.draft()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of settings.options objects for sampling a list or boolean option
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} optionName - Name of the option to sample
|
||||||
|
* @return {Array} sets - The list of settings objects
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.__listBoolOptionSets = function (optionName) {
|
||||||
|
let option = this.pattern.config.options[optionName]
|
||||||
|
const base = this.__setBase()
|
||||||
|
const sets = []
|
||||||
|
let run = 1
|
||||||
|
if (typeof option.bool !== 'undefined') option = { list: [false, true] }
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of settings objects for sampling a measurement
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} measurementName - The name of the measurement to sample
|
||||||
|
* @return {Array} sets - The list of settings objects
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.__measurementSets = function (measurementName) {
|
||||||
|
let val = this.pattern.settings[0].measurements[measurementName]
|
||||||
|
if (val === undefined)
|
||||||
|
this.pattern.store.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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of settings objects for sampling a list of models
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {object} models - The models to sample
|
||||||
|
* @param {string} focus - The ID of the model that should be highlighted
|
||||||
|
* @return {Array} sets - The list of settings objects
|
||||||
|
*/
|
||||||
|
PatternSampler.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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array of settings objects for sampling an option
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} optionName - The name of the option to sample
|
||||||
|
* @return {Array} sets - The list of settings objects
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.__optionSets = function (optionName) {
|
||||||
|
const sets = []
|
||||||
|
if (!(optionName in this.pattern.config.options)) return sets
|
||||||
|
let option = this.pattern.config.options[optionName]
|
||||||
|
if (typeof option.list === 'object' || typeof option.bool !== 'undefined')
|
||||||
|
return this.__listBoolOptionSets(optionName)
|
||||||
|
let factor = 1
|
||||||
|
let step, val
|
||||||
|
let numberRuns = 10
|
||||||
|
let stepFactor = numberRuns - 1
|
||||||
|
if (typeof option.min === 'undefined' || typeof option.max === 'undefined') {
|
||||||
|
const min = option * 0.9
|
||||||
|
const max = option * 1.1
|
||||||
|
option = { min, max }
|
||||||
|
}
|
||||||
|
if (typeof option.pct !== 'undefined') factor = 100
|
||||||
|
val = option.min / factor
|
||||||
|
if (typeof option.count !== 'undefined' || typeof option.mm !== 'undefined') {
|
||||||
|
const numberOfCounts = option.max - option.min + 1
|
||||||
|
if (numberOfCounts < 10) {
|
||||||
|
numberRuns = numberOfCounts
|
||||||
|
stepFactor = Math.max(numberRuns - 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step = (option.max / factor - val) / stepFactor
|
||||||
|
const base = this.__setBase()
|
||||||
|
const roundVal = typeof option.count !== 'undefined' || typeof option.mm !== 'undefined'
|
||||||
|
for (let run = 1; run <= numberRuns; run++) {
|
||||||
|
const settings = {
|
||||||
|
...base,
|
||||||
|
options: {
|
||||||
|
...base.options,
|
||||||
|
},
|
||||||
|
idPrefix: `sample-${run}`,
|
||||||
|
partClasses: `sample-${run}`,
|
||||||
|
}
|
||||||
|
settings.options[optionName] = roundVal ? Math.ceil(val) : val
|
||||||
|
sets.push(settings)
|
||||||
|
val += step
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the base/defaults to generate a set of settings
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {object} settings - The settings object
|
||||||
|
*/
|
||||||
|
PatternSampler.prototype.__setBase = function () {
|
||||||
|
return {
|
||||||
|
measurements: {},
|
||||||
|
options: {},
|
||||||
|
...this.pattern.settings[0],
|
||||||
|
}
|
||||||
|
}
|
|
@ -320,7 +320,7 @@ export const generateStackTransform = (
|
||||||
|
|
||||||
// add the scaling to the transforms
|
// add the scaling to the transforms
|
||||||
if (scaleX + scaleY < 2) {
|
if (scaleX + scaleY < 2) {
|
||||||
transforms.push(`scale(${scaleX} ${scaleY})`)
|
transforms.push(`scale(${scaleX}, ${scaleY})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rotate) {
|
if (rotate) {
|
||||||
|
@ -331,11 +331,11 @@ export const generateStackTransform = (
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the rotation around the center to the transforms
|
// add the rotation around the center to the transforms
|
||||||
transforms.push(`rotate(${rotate} ${center.x} ${center.y})`)
|
transforms.push(`rotate(${rotate}, ${center.x}, ${center.y})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the translation before any other transforms to avoid having to make complex calculations once the matrix has been rotated or scaled
|
// put the translation before any other transforms to avoid having to make complex calculations once the matrix has been rotated or scaled
|
||||||
if (xTotal !== 0 || yTotal !== 0) transforms.unshift(`translate(${xTotal} ${yTotal})`)
|
if (xTotal !== 0 || yTotal !== 0) transforms.unshift(`translate(${xTotal}, ${yTotal})`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transform: transforms.join(' '),
|
transform: transforms.join(' '),
|
||||||
|
|
|
@ -6,7 +6,7 @@ const expect = chai.expect
|
||||||
describe('Hooks', () => {
|
describe('Hooks', () => {
|
||||||
it('Should contain all hooks', () => {
|
it('Should contain all hooks', () => {
|
||||||
const pattern = new Pattern()
|
const pattern = new Pattern()
|
||||||
const h = pattern.hooks
|
const h = pattern.plugins.hooks
|
||||||
const test = {
|
const test = {
|
||||||
preInit: [],
|
preInit: [],
|
||||||
postInit: [],
|
postInit: [],
|
||||||
|
|
|
@ -33,18 +33,6 @@ describe('Pattern', () => {
|
||||||
expect(count).to.equal(2)
|
expect(count).to.equal(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Pattern.createPartForSet()', () => {
|
|
||||||
it('Should not allow malicious assignment to Object.prototype', () => {
|
|
||||||
const objProto = Object.prototype
|
|
||||||
const Pattern = new Design()
|
|
||||||
const pattern = new Pattern()
|
|
||||||
|
|
||||||
expect(() => pattern.createPartForSet('part', '__proto__')).to.throw(
|
|
||||||
'malicious attempt at altering Object.prototype. Stopping action'
|
|
||||||
)
|
|
||||||
expect(objProto).to.equal(Object.prototype)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('Should check whether a part is needed', () => {
|
it('Should check whether a part is needed', () => {
|
||||||
const partA = {
|
const partA = {
|
||||||
name: 'test.partA',
|
name: 'test.partA',
|
||||||
|
@ -175,7 +163,7 @@ describe('Pattern', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Pattern.__pack()', () => {
|
describe('PatternRenderer.__pack()', () => {
|
||||||
it('should get a part stack name from a function that uses settings', () => {
|
it('should get a part stack name from a function that uses settings', () => {
|
||||||
const expectedName = 'namedStack'
|
const expectedName = 'namedStack'
|
||||||
const front = {
|
const front = {
|
||||||
|
@ -201,7 +189,7 @@ describe('Pattern', () => {
|
||||||
|
|
||||||
const pattern = new Test()
|
const pattern = new Test()
|
||||||
pattern.draft()
|
pattern.draft()
|
||||||
pattern.__pack()
|
pattern.getRenderProps()
|
||||||
|
|
||||||
const stackNames = Object.keys(pattern.stacks)
|
const stackNames = Object.keys(pattern.stacks)
|
||||||
expect(stackNames).to.include(expectedName)
|
expect(stackNames).to.include(expectedName)
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe('Pattern', () => {
|
||||||
expect(Array.isArray(pattern.setStores)).to.equal(true)
|
expect(Array.isArray(pattern.setStores)).to.equal(true)
|
||||||
expect(typeof pattern.store).to.equal('object')
|
expect(typeof pattern.store).to.equal('object')
|
||||||
expect(typeof pattern.config).to.equal('object')
|
expect(typeof pattern.config).to.equal('object')
|
||||||
expect(Object.keys(pattern).length).to.equal(5)
|
expect(Object.keys(pattern)).to.have.lengthOf(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern constructor should add non-enumerable properties', () => {
|
it('Pattern constructor should add non-enumerable properties', () => {
|
||||||
|
@ -26,18 +26,10 @@ describe('Pattern', () => {
|
||||||
const pattern = new Pattern()
|
const pattern = new Pattern()
|
||||||
expect(typeof pattern.plugins).to.equal('object')
|
expect(typeof pattern.plugins).to.equal('object')
|
||||||
expect(typeof pattern.autoLayout).to.equal('object')
|
expect(typeof pattern.autoLayout).to.equal('object')
|
||||||
expect(typeof pattern.hooks).to.equal('object')
|
|
||||||
expect(typeof pattern.Point).to.equal('function')
|
expect(typeof pattern.Point).to.equal('function')
|
||||||
expect(typeof pattern.Path).to.equal('function')
|
expect(typeof pattern.Path).to.equal('function')
|
||||||
expect(typeof pattern.Snippet).to.equal('function')
|
expect(typeof pattern.Snippet).to.equal('function')
|
||||||
expect(typeof pattern.Attributes).to.equal('function')
|
expect(typeof pattern.Attributes).to.equal('function')
|
||||||
expect(typeof pattern.macros).to.equal('object')
|
|
||||||
// expect(typeof pattern.__designParts).to.equal('object')
|
|
||||||
// expect(typeof pattern.config.inject).to.equal('object')
|
|
||||||
// expect(typeof pattern.config.directDependencies).to.equal('object')
|
|
||||||
// expect(typeof pattern.__resolvedDependencies).to.equal('object')
|
|
||||||
// expect(typeof pattern.__hide).to.equal('object')
|
|
||||||
// expect(Array.isArray(pattern.__draftOrder)).to.equal(true)
|
|
||||||
expect(pattern.width).to.equal(0)
|
expect(pattern.width).to.equal(0)
|
||||||
expect(pattern.height).to.equal(0)
|
expect(pattern.height).to.equal(0)
|
||||||
expect(pattern.is).to.equal('')
|
expect(pattern.is).to.equal('')
|
||||||
|
@ -58,7 +50,7 @@ describe('Pattern', () => {
|
||||||
absoluteOptions: {},
|
absoluteOptions: {},
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(dflts)) {
|
for (const [key, value] of Object.entries(dflts)) {
|
||||||
if (typeof value === 'object') expect(Object.keys(value).length).to.equal(0)
|
if (typeof value === 'object') expect(Object.keys(value)).to.have.lengthOf(0)
|
||||||
else expect(pattern.settings[0][key]).to.equal(value)
|
else expect(pattern.settings[0][key]).to.equal(value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -120,19 +112,19 @@ describe('Pattern', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve required measurements', () => {
|
it('Pattern.__init() should resolve required measurements', () => {
|
||||||
expect(pattern.config.measurements.length).to.equal(2)
|
expect(pattern.config.measurements).to.have.lengthOf(2)
|
||||||
expect(pattern.config.measurements[0]).to.equal('head')
|
expect(pattern.config.measurements[0]).to.equal('head')
|
||||||
expect(pattern.config.measurements[1]).to.equal('knee')
|
expect(pattern.config.measurements[1]).to.equal('knee')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve optional measurements', () => {
|
it('Pattern.__init() should resolve optional measurements', () => {
|
||||||
expect(pattern.config.optionalMeasurements.length).to.equal(2)
|
expect(pattern.config.optionalMeasurements).to.have.lengthOf(2)
|
||||||
expect(pattern.config.optionalMeasurements[0]).to.equal('chest')
|
expect(pattern.config.optionalMeasurements[0]).to.equal('chest')
|
||||||
expect(pattern.config.optionalMeasurements[1]).to.equal('waist')
|
expect(pattern.config.optionalMeasurements[1]).to.equal('waist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve options', () => {
|
it('Pattern.__init() should resolve options', () => {
|
||||||
expect(Object.keys(pattern.config.options).length).to.equal(3)
|
expect(Object.keys(pattern.config.options)).to.have.lengthOf(3)
|
||||||
for (const [key, value] of Object.entries(partA.options.optA)) {
|
for (const [key, value] of Object.entries(partA.options.optA)) {
|
||||||
expect(pattern.config.options.optA[key]).to.equal(value)
|
expect(pattern.config.options.optA[key]).to.equal(value)
|
||||||
}
|
}
|
||||||
|
@ -149,7 +141,7 @@ describe('Pattern', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should resolve plugins', () => {
|
it('Pattern.__init() should resolve plugins', () => {
|
||||||
expect(Object.keys(pattern.config.plugins).length).to.equal(1)
|
expect(Object.keys(pattern.config.plugins)).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should set config data in the store', () => {
|
it('Pattern.__init() should set config data in the store', () => {
|
||||||
|
@ -160,12 +152,12 @@ describe('Pattern', () => {
|
||||||
it('Pattern.__init() should resolve dependencies', () => {
|
it('Pattern.__init() should resolve dependencies', () => {
|
||||||
expect(typeof pattern.config.resolvedDependencies).to.equal('object')
|
expect(typeof pattern.config.resolvedDependencies).to.equal('object')
|
||||||
expect(Array.isArray(pattern.config.resolvedDependencies['test.partA'])).to.equal(true)
|
expect(Array.isArray(pattern.config.resolvedDependencies['test.partA'])).to.equal(true)
|
||||||
expect(pattern.config.resolvedDependencies['test.partA'].length).to.equal(0)
|
expect(pattern.config.resolvedDependencies['test.partA']).to.have.lengthOf(0)
|
||||||
expect(Array.isArray(pattern.config.resolvedDependencies['test.partB'])).to.equal(true)
|
expect(Array.isArray(pattern.config.resolvedDependencies['test.partB'])).to.equal(true)
|
||||||
expect(pattern.config.resolvedDependencies['test.partB'].length).to.equal(1)
|
expect(pattern.config.resolvedDependencies['test.partB']).to.have.lengthOf(1)
|
||||||
expect(pattern.config.resolvedDependencies['test.partB'][0]).to.equal('test.partA')
|
expect(pattern.config.resolvedDependencies['test.partB'][0]).to.equal('test.partA')
|
||||||
expect(Array.isArray(pattern.config.resolvedDependencies['test.partC'])).to.equal(true)
|
expect(Array.isArray(pattern.config.resolvedDependencies['test.partC'])).to.equal(true)
|
||||||
expect(pattern.config.resolvedDependencies['test.partC'].length).to.equal(2)
|
expect(pattern.config.resolvedDependencies['test.partC']).to.have.lengthOf(2)
|
||||||
expect(
|
expect(
|
||||||
pattern.config.resolvedDependencies['test.partC'].indexOf('test.partA') !== -1
|
pattern.config.resolvedDependencies['test.partC'].indexOf('test.partA') !== -1
|
||||||
).to.equal(true)
|
).to.equal(true)
|
||||||
|
@ -325,13 +317,13 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [partC] })
|
const design = new Design({ parts: [partC] })
|
||||||
const pattern = new design().addPart(partR).draft()
|
const pattern = new design().addPart(partR).draft()
|
||||||
// Measurements
|
// Measurements
|
||||||
expect(pattern.config.measurements.length).to.equal(4)
|
expect(pattern.config.measurements).to.have.lengthOf(4)
|
||||||
expect(pattern.config.measurements.indexOf('measieA') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieA') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieB') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieB') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieC') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieC') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieR') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieR') === -1).to.equal(false)
|
||||||
// Optional measurements
|
// Optional measurements
|
||||||
expect(pattern.config.optionalMeasurements.length).to.equal(4)
|
expect(pattern.config.optionalMeasurements).to.have.lengthOf(4)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieA') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieA') === -1).to.equal(false)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieB') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieB') === -1).to.equal(false)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieC') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieC') === -1).to.equal(false)
|
||||||
|
@ -473,13 +465,13 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [partD] })
|
const design = new Design({ parts: [partD] })
|
||||||
const pattern = new design().draft()
|
const pattern = new design().draft()
|
||||||
// Measurements
|
// Measurements
|
||||||
expect(pattern.config.measurements.length).to.equal(4)
|
expect(pattern.config.measurements).to.have.lengthOf(4)
|
||||||
expect(pattern.config.measurements.indexOf('measieA') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieA') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieB') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieB') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieC') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieC') === -1).to.equal(false)
|
||||||
expect(pattern.config.measurements.indexOf('measieD') === -1).to.equal(false)
|
expect(pattern.config.measurements.indexOf('measieD') === -1).to.equal(false)
|
||||||
// Optional measurements
|
// Optional measurements
|
||||||
expect(pattern.config.optionalMeasurements.length).to.equal(4)
|
expect(pattern.config.optionalMeasurements).to.have.lengthOf(4)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieA') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieA') === -1).to.equal(false)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieB') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieB') === -1).to.equal(false)
|
||||||
expect(pattern.config.optionalMeasurements.indexOf('optmeasieC') === -1).to.equal(false)
|
expect(pattern.config.optionalMeasurements.indexOf('optmeasieC') === -1).to.equal(false)
|
||||||
|
@ -578,7 +570,7 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.draft()
|
pattern.draft()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(1)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should load array of plugins', () => {
|
it('Pattern.__init() should load array of plugins', () => {
|
||||||
|
@ -608,7 +600,7 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(2)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should load conditional plugin if condition is met', () => {
|
it('Pattern.__init() should load conditional plugin if condition is met', () => {
|
||||||
|
@ -629,11 +621,11 @@ describe('Pattern', () => {
|
||||||
}
|
}
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.draft()
|
pattern.__init()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(1)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should not load conditional plugin if condition is not mett', () => {
|
it('Pattern.__init() should not load conditional plugin if condition is not met', () => {
|
||||||
const plugin = {
|
const plugin = {
|
||||||
name: 'example',
|
name: 'example',
|
||||||
version: 1,
|
version: 1,
|
||||||
|
@ -651,7 +643,7 @@ describe('Pattern', () => {
|
||||||
}
|
}
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(0)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should load multiple conditional plugins', () => {
|
it('Pattern.__init() should load multiple conditional plugins', () => {
|
||||||
|
@ -686,7 +678,7 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [part] })
|
const design = new Design({ parts: [part] })
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.draft()
|
pattern.draft()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(1)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should load a conditional plugin multiple times with different conditions', () => {
|
it('Pattern.__init() should load a conditional plugin multiple times with different conditions', () => {
|
||||||
|
@ -718,7 +710,7 @@ describe('Pattern', () => {
|
||||||
expect(pattern.config.plugins).to.be.an('object').that.has.all.keys('example1', 'example1_')
|
expect(pattern.config.plugins).to.be.an('object').that.has.all.keys('example1', 'example1_')
|
||||||
expect(pattern.config.plugins.example1.plugin).to.deep.equal(plugin1)
|
expect(pattern.config.plugins.example1.plugin).to.deep.equal(plugin1)
|
||||||
expect(pattern.config.plugins.example1_.plugin).to.deep.equal(plugin1)
|
expect(pattern.config.plugins.example1_.plugin).to.deep.equal(plugin1)
|
||||||
expect(pattern.hooks.preRender.length).to.equal(1)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Load conditional plugins that are also passing data', () => {
|
it('Load conditional plugins that are also passing data', () => {
|
||||||
|
@ -757,7 +749,7 @@ describe('Pattern', () => {
|
||||||
})
|
})
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
pattern.__init()
|
pattern.__init()
|
||||||
expect(pattern.hooks.preRender.length).to.equal(2)
|
expect(pattern.plugins.hooks.preRender).to.have.lengthOf(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Pattern.__init() should register a hook via on', () => {
|
it('Pattern.__init() should register a hook via on', () => {
|
||||||
|
@ -814,6 +806,32 @@ describe('Pattern', () => {
|
||||||
expect(count).to.equal(2)
|
expect(count).to.equal(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Pattern.__init() should not register the same method twice on one hook', () => {
|
||||||
|
function hookMethod() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
const plugin = {
|
||||||
|
name: 'test',
|
||||||
|
version: '0.1-test',
|
||||||
|
hooks: {
|
||||||
|
preDraft: [
|
||||||
|
hookMethod,
|
||||||
|
hookMethod,
|
||||||
|
function () {
|
||||||
|
count++
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Pattern = new Design()
|
||||||
|
const pattern = new Pattern()
|
||||||
|
let count = 0
|
||||||
|
pattern._draft = () => {}
|
||||||
|
pattern.use(plugin)
|
||||||
|
pattern.draft()
|
||||||
|
expect(count).to.equal(2)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should check whether created parts get the pattern context', () => {
|
it('Should check whether created parts get the pattern context', () => {
|
||||||
let partContext
|
let partContext
|
||||||
const plugin = {
|
const plugin = {
|
||||||
|
|
|
@ -61,9 +61,7 @@ describe('Pattern', () => {
|
||||||
const design = new Design({ parts: [test, you] })
|
const design = new Design({ parts: [test, you] })
|
||||||
const pattern = new design({ only: ['you'] })
|
const pattern = new design({ only: ['you'] })
|
||||||
pattern.draft()
|
pattern.draft()
|
||||||
expect(pattern.setStores[0].logs.debug).to.include(
|
expect(pattern.setStores[0].logs.debug).to.include('Part `test` is not needed. Skipping part')
|
||||||
'Part `test` is not needed. Skipping draft and setting hidden to `true`'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should return the initialized config', () => {
|
it('Should return the initialized config', () => {
|
||||||
|
@ -90,8 +88,8 @@ describe('Pattern', () => {
|
||||||
pattern.use(plugin)
|
pattern.use(plugin)
|
||||||
pattern.use({ plugin })
|
pattern.use({ plugin })
|
||||||
pattern.use({ plugin })
|
pattern.use({ plugin })
|
||||||
expect(Object.keys(pattern.plugins).length).to.equal(1)
|
expect(Object.keys(pattern.plugins.plugins)).to.have.lengthOf(1)
|
||||||
expect(Object.keys(pattern.plugins)[0]).to.equal('test')
|
expect(Object.keys(pattern.plugins.plugins)[0]).to.equal('test')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should log an error of added parts do not have a draft method', () => {
|
it('Should log an error of added parts do not have a draft method', () => {
|
||||||
|
@ -161,7 +159,6 @@ describe('Pattern', () => {
|
||||||
expect(pattern.setStores[0].logs.error[0][0]).to.equal('Unable to draft part `test` (set 0)')
|
expect(pattern.setStores[0].logs.error[0][0]).to.equal('Unable to draft part `test` (set 0)')
|
||||||
})
|
})
|
||||||
|
|
||||||
// FIXME: Add assertions here
|
|
||||||
it('Handle layout object', () => {
|
it('Handle layout object', () => {
|
||||||
const part = {
|
const part = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
@ -176,7 +173,7 @@ describe('Pattern', () => {
|
||||||
layout: { stacks: { test: { flipX: true } }, width: 300, height: 400 },
|
layout: { stacks: { test: { flipX: true } }, width: 300, height: 400 },
|
||||||
})
|
})
|
||||||
const props = pattern.draft().getRenderProps()
|
const props = pattern.draft().getRenderProps()
|
||||||
expect(props.stacks.test.attributes.get('transform')).to.equal('scale(-1 1)')
|
expect(props.stacks.test.attributes.get('transform')).to.equal('scale(-1, 1)')
|
||||||
expect(props.width).to.equal(300)
|
expect(props.width).to.equal(300)
|
||||||
expect(props.height).to.equal(400)
|
expect(props.height).to.equal(400)
|
||||||
})
|
})
|
||||||
|
|
39
packages/core/tests/pattern-renderer.test.mjs
Normal file
39
packages/core/tests/pattern-renderer.test.mjs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import chai from 'chai'
|
||||||
|
import { Design } from '../src/index.mjs'
|
||||||
|
|
||||||
|
const expect = chai.expect
|
||||||
|
|
||||||
|
describe('Pattern Rendering', () => {
|
||||||
|
describe('Pattern.prototype.getRenderProps()', () => {
|
||||||
|
describe('Hidden parts and stacks', () => {
|
||||||
|
const part = {
|
||||||
|
name: 'test',
|
||||||
|
draft: ({ part }) => {
|
||||||
|
part.hide()
|
||||||
|
return part
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const design = new Design({ parts: [part] })
|
||||||
|
const pattern = new design({})
|
||||||
|
const props = pattern.draft().getRenderProps()
|
||||||
|
|
||||||
|
it('Should not include hidden parts', () => {
|
||||||
|
expect(props.parts[0]).not.to.have.property('test')
|
||||||
|
})
|
||||||
|
it('Should log that it has skipped a hidden part', () => {
|
||||||
|
expect(props.logs.sets[0].info).to.include(
|
||||||
|
'Part test is hidden in set 0. Not adding to render props'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('Should not include hidden stacks', () => {
|
||||||
|
expect(props.stacks).not.to.have.property('test')
|
||||||
|
})
|
||||||
|
it('Should log that it has skipped a hidden stack', () => {
|
||||||
|
expect(props.logs.pattern.info).to.include(
|
||||||
|
'Stack test is hidden. Skipping in render props.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -31,6 +31,7 @@ describe('Pattern', () => {
|
||||||
pattern.sample()
|
pattern.sample()
|
||||||
expect(pattern.setStores.length).to.equal(10)
|
expect(pattern.setStores.length).to.equal(10)
|
||||||
expect(pattern.settings.length).to.equal(10)
|
expect(pattern.settings.length).to.equal(10)
|
||||||
|
expect(pattern.parts[0].test.paths.test.ops[1].to.y).to.equal(80)
|
||||||
expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(320)
|
expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(320)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -60,9 +61,70 @@ describe('Pattern', () => {
|
||||||
pattern.sample()
|
pattern.sample()
|
||||||
expect(pattern.setStores.length).to.equal(10)
|
expect(pattern.setStores.length).to.equal(10)
|
||||||
expect(pattern.settings.length).to.equal(10)
|
expect(pattern.settings.length).to.equal(10)
|
||||||
|
expect(round(pattern.parts[0].test.paths.test.ops[1].to.y)).to.equal(round(0.05 * 0.9 * 400))
|
||||||
expect(round(pattern.parts[9].test.paths.test.ops[1].to.y)).to.equal(22)
|
expect(round(pattern.parts[9].test.paths.test.ops[1].to.y)).to.equal(22)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should sample a count option', () => {
|
||||||
|
const part = {
|
||||||
|
name: 'test',
|
||||||
|
measurements: ['head'],
|
||||||
|
options: {
|
||||||
|
size: { count: 2, min: 0, max: 6 },
|
||||||
|
},
|
||||||
|
draft: ({ Point, paths, Path, measurements, options, part }) => {
|
||||||
|
paths.test = new Path()
|
||||||
|
.move(new Point(0, 0))
|
||||||
|
.line(new Point(0, measurements.head * options.size))
|
||||||
|
|
||||||
|
return part
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Pattern = new Design({ parts: [part] })
|
||||||
|
const pattern = new Pattern({
|
||||||
|
measurements: { head: 400 },
|
||||||
|
sample: {
|
||||||
|
type: 'option',
|
||||||
|
option: 'size',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pattern.sample()
|
||||||
|
expect(pattern.setStores.length).to.equal(7)
|
||||||
|
expect(pattern.settings.length).to.equal(7)
|
||||||
|
expect(round(pattern.parts[0].test.paths.test.ops[1].to.y)).to.equal(0)
|
||||||
|
expect(round(pattern.parts[6].test.paths.test.ops[1].to.y)).to.equal(2400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not sample a count option more than 10 times', () => {
|
||||||
|
const part = {
|
||||||
|
name: 'test',
|
||||||
|
measurements: ['head'],
|
||||||
|
options: {
|
||||||
|
size: { count: 2, min: 0, max: 20 },
|
||||||
|
},
|
||||||
|
draft: ({ Point, paths, Path, measurements, options, part }) => {
|
||||||
|
paths.test = new Path()
|
||||||
|
.move(new Point(0, 0))
|
||||||
|
.line(new Point(0, measurements.head * options.size))
|
||||||
|
|
||||||
|
return part
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Pattern = new Design({ parts: [part] })
|
||||||
|
const pattern = new Pattern({
|
||||||
|
measurements: { head: 400 },
|
||||||
|
sample: {
|
||||||
|
type: 'option',
|
||||||
|
option: 'size',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pattern.sample()
|
||||||
|
expect(pattern.setStores.length).to.equal(10)
|
||||||
|
expect(pattern.settings.length).to.equal(10)
|
||||||
|
expect(round(pattern.parts[0].test.paths.test.ops[1].to.y)).to.equal(0)
|
||||||
|
expect(round(pattern.parts[9].test.paths.test.ops[1].to.y)).to.equal(8000)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should sample a list option', () => {
|
it('Should sample a list option', () => {
|
||||||
const part = {
|
const part = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
@ -89,9 +151,39 @@ describe('Pattern', () => {
|
||||||
pattern.sample()
|
pattern.sample()
|
||||||
expect(pattern.setStores.length).to.equal(10)
|
expect(pattern.setStores.length).to.equal(10)
|
||||||
expect(pattern.settings.length).to.equal(10)
|
expect(pattern.settings.length).to.equal(10)
|
||||||
|
expect(pattern.parts[0].test.paths.test.ops[1].to.y).to.equal(40)
|
||||||
expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(400)
|
expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should sample a boolean option', () => {
|
||||||
|
const part = {
|
||||||
|
name: 'test',
|
||||||
|
measurements: ['head'],
|
||||||
|
options: {
|
||||||
|
reverse: { bool: true },
|
||||||
|
},
|
||||||
|
draft: ({ Point, paths, Path, measurements, options, part }) => {
|
||||||
|
const yFac = options.reverse ? -1 : 1
|
||||||
|
paths.test = new Path().move(new Point(0, 0)).line(new Point(0, measurements.head * yFac))
|
||||||
|
|
||||||
|
return part
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Pattern = new Design({ parts: [part] })
|
||||||
|
const pattern = new Pattern({
|
||||||
|
measurements: { head: 400 },
|
||||||
|
sample: {
|
||||||
|
type: 'option',
|
||||||
|
option: 'reverse',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
pattern.sample()
|
||||||
|
expect(pattern.setStores.length).to.equal(2)
|
||||||
|
expect(pattern.settings.length).to.equal(2)
|
||||||
|
expect(pattern.parts[0].test.paths.test.ops[1].to.y).to.equal(400)
|
||||||
|
expect(pattern.parts[1].test.paths.test.ops[1].to.y).to.equal(-400)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should sample a measurement', () => {
|
it('Should sample a measurement', () => {
|
||||||
const part = {
|
const part = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
|
|
@ -170,7 +170,7 @@ describe('Stacks', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(pattern.stacks.test.attributes.list.transform.length).to.equal(1)
|
expect(pattern.stacks.test.attributes.list.transform.length).to.equal(1)
|
||||||
expect(pattern.stacks.test.attributes.list.transform[0]).to.equal('translate(10 20)')
|
expect(pattern.stacks.test.attributes.list.transform[0]).to.equal('translate(10, 20)')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -492,6 +492,6 @@ describe('Utils', () => {
|
||||||
const pattern = new design()
|
const pattern = new design()
|
||||||
const props = pattern.draft().getRenderProps()
|
const props = pattern.draft().getRenderProps()
|
||||||
const transform = generateStackTransform(30, 60, 90, true, true, props.stacks.test)
|
const transform = generateStackTransform(30, 60, 90, true, true, props.stacks.test)
|
||||||
expect(transform.transform).to.equal('translate(51 138) scale(-1 -1) rotate(90 10.5 39)')
|
expect(transform.transform).to.equal('translate(51, 138) scale(-1, -1) rotate(90, 10.5, 39)')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue