1
0
Fork 0

wip(core/brian/aaron): Support for 2022 style part inheritance

This is very rough around the edges, but it's kinda working, so
I'm committing this now.

What this enabled is the ability to extend a part by importing
only that part and then just saying you want a part `from` the
imported one.

The imported part comes with all options, it does not currently
come with all measurements.

This also *follows* dependencies. For example in Brian, we never
explicitly add the base and sleevecap parts, they are simply
added automatically because other parts are buily *from* them.

Best to look at the source code of designs/brian and designs/aaron
to understand what's going on and how it is different.
This commit is contained in:
joostdecock 2022-08-09 20:17:35 +02:00
parent ac9b616b99
commit 70bd946bdc
18 changed files with 1306 additions and 1109 deletions

View file

@ -23,16 +23,21 @@ const options = {
// Different formats
const formats = {
cjs: "dist/index.js",
esm: "dist/index.mjs",
cjs: "js",
esm: "mjs",
}
// Let esbuild generate different formats
let result
(async () => {
for (const [format, outfile] of Object.entries(formats)) {
for (const [format, ext] of Object.entries(formats)) {
// Regular build
result = await esbuild
.build({ ...options, outfile, format })
.build({ ...options, format, outfile: `dist/index.${ext}` })
.catch(() => process.exit(1))
// Config build
await esbuild
.build({ ...options, format, outfile: `dist/config.${ext}`, entryPoints: ['config/index.js'] })
.catch(() => process.exit(1))
}
@ -41,11 +46,13 @@ let result
console.log(info)
}
// Also build a version that has all dependencies bundled
// This makes it easy to run tests
await esbuild
.build({
...options,
entryPoints: ['src/index.js'],
minify: false,
sourcemap: false,
outfile: 'tests/dist/index.mjs',

View file

@ -1,8 +1,6 @@
import { version } from '../package.json'
import configHelpers from '@freesewing/config-helpers'
const { pctBasedOn } = configHelpers
export default {
export const info = {
version,
name: 'aaron',
design: 'Joost De Cock',
@ -22,7 +20,9 @@ export default {
'lengthBonus',
],
},
measurements: [
}
export const measurements = [
'biceps',
'chest',
'hpsToWaistBack',
@ -31,43 +31,7 @@ export default {
'shoulderSlope',
'shoulderToShoulder',
'hips',
],
optionalMeasurements: ['highBust'],
dependencies: {
front: 'base',
back: 'front',
},
inject: {
front: 'base',
back: 'front',
},
hide: ['base'],
options: {
// Constants
brianFitCollar: false,
collarFactor: 4.8,
acrossBackFactor: 0.97,
backNeckCutout: 0.05,
bicepsEase: 0.05,
shoulderEase: 0,
collarEase: 0,
frontArmholeDeeper: 0,
armholeDepthFactor: 0.6,
shoulderSlopeReduction: 0,
]
// Percentages
armholeDrop: { pct: 10, min: 0, max: 75 },
backlineBend: { pct: 50, min: 25, max: 100 },
chestEase: { pct: 8, min: 0, max: 20, ...pctBasedOn('chest') },
hipsEase: { pct: 8, min: 0, max: 20 },
lengthBonus: { pct: 10, min: -20, max: 60 },
necklineBend: { pct: 100, min: 40, max: 100 },
necklineDrop: { pct: 20, min: 10, max: 35 },
stretchFactor: { pct: 5, min: 0, max: 15 },
shoulderStrapWidth: { pct: 15, min: 10, max: 40 },
shoulderStrapPlacement: { pct: 40, min: 20, max: 80 },
export const optionalMeasurements = ['highBust']
// draft for high bust
draftForHighBust: { bool: false },
},
}

View file

@ -0,0 +1,28 @@
import configHelpers from '@freesewing/config-helpers'
const { pctBasedOn } = configHelpers
export const brianFitCollar = false
export const brianFitSleeve = false
export const acrossBackFactor = 0.97
export const backNeckCutout = 0.05
export const bicepsEase = 0.05
export const shoulderEase = 0
export const collarEase = 0
export const frontArmholeDeeper = 0
export const armholeDepthFactor = 0.6
export const shoulderSlopeReduction = 0
// Percentages
export const armholeDrop = { pct: 10, min: 0, max: 75 }
export const backlineBend = { pct: 50, min: 25, max: 100 }
export const chestEase = { pct: 8, min: 0, max: 20, ...pctBasedOn('chest') }
export const hipsEase = { pct: 8, min: 0, max: 20 }
export const lengthBonus = { pct: 10, min: -20, max: 60 }
export const necklineBend = { pct: 100, min: 40, max: 100 }
export const necklineDrop = { pct: 20, min: 10, max: 35 }
export const stretchFactor = { pct: 5, min: 0, max: 15 }
export const shoulderStrapWidth = { pct: 15, min: 10, max: 40 }
export const shoulderStrapPlacement = { pct: 40, min: 20, max: 80 }
// draft for high bust
export const draftForHighBust = { bool: false }

View file

@ -1,7 +1,11 @@
import { dimensions } from './shared'
import front from "./front.js"
export default function (part) {
let {
export default {
from: front,
name: 'back',
draft: function (part) {
const {
store,
sa,
Point,
@ -101,3 +105,4 @@ export default function (part) {
return part
}
}

View file

@ -1,7 +1,11 @@
import { dimensions } from './shared'
import { base } from '@freesewing/brian'
export default function (part) {
let {
export default {
from: base,
name: 'front',
draft: function (part) {
const {
utils,
store,
sa,
@ -167,3 +171,4 @@ export default function (part) {
return part
}
}

View file

@ -1,38 +1,33 @@
// FreeSewing core library
import freesewing from '@freesewing/core'
import Brian from '@freesewing/brian'
import plugins from '@freesewing/plugin-bundle'
import plugin from '@freesewing/plugin-bust' // Note: conditional plugin
import config from '../config'
// FreeSewing Plugins
import pluginBundle from '@freesewing/plugin-bundle'
import bustPlugin from '@freesewing/plugin-bust' // Note: conditional plugin
// Design config & options
import { info, measurements, optionalMeasurements } from '../config/index'
import * as options from '../config/options'
// Design parts
import back from './back'
import front from './front'
// Parts
import draftBack from './back'
import draftFront from './front'
/* Check to see whether we should load the bust plugin
* Only of the `draftForHighBust` options is set
* AND the highBust measurement is available
*/
const condition = (settings = false) =>
settings &&
settings.options &&
settings.options.draftForHighBust &&
settings.measurements.highBust
? true
: false
// Create design
const Aaron = new freesewing.Design(config, plugins, { plugin, condition })
// Attach draft methods to prototype
Aaron.prototype.draftBase = function (part) {
// Getting the base part from Brian
return new Brian(this.settings).draftBase(part)
// Setup design
const Aaron = new freesewing.Design({
...info,
measurements,
optionalMeasurements,
options: { ...options },
parts: { back, front },
plugins: pluginBundle,
conditionalPlugins: {
plugin: bustPlugin,
condition: (settings=false) =>
settings?.options?.draftForHighBust &&
settings?.measurements?.highBust
? true : false
}
Aaron.prototype.draftFront = (part) => draftFront(part)
Aaron.prototype.draftBack = (part) => draftBack(part)
})
// Named exports
export { config, Aaron }
export { front, back, Aaron }
// Default export
export default Aaron

View file

@ -23,16 +23,21 @@ const options = {
// Different formats
const formats = {
cjs: "dist/index.js",
esm: "dist/index.mjs",
cjs: "js",
esm: "mjs",
}
// Let esbuild generate different formats
let result
(async () => {
for (const [format, outfile] of Object.entries(formats)) {
for (const [format, ext] of Object.entries(formats)) {
// Regular build
result = await esbuild
.build({ ...options, outfile, format })
.build({ ...options, format, outfile: `dist/index.${ext}` })
.catch(() => process.exit(1))
// Config build
await esbuild
.build({ ...options, format, outfile: `dist/config.${ext}`, entryPoints: ['config/index.js'] })
.catch(() => process.exit(1))
}
@ -41,11 +46,13 @@ let result
console.log(info)
}
// Also build a version that has all dependencies bundled
// This makes it easy to run tests
await esbuild
.build({
...options,
entryPoints: ['src/index.js'],
minify: false,
sourcemap: false,
outfile: 'tests/dist/index.mjs',

View file

@ -1,6 +1,6 @@
import { version } from '../package.json'
export default {
export const info = {
version,
name: 'brian',
design: 'Joost De Cock',
@ -52,7 +52,9 @@ export default {
},
],
},
measurements: [
}
export const measurements = [
'biceps',
'chest',
'hpsToWaistBack',
@ -62,64 +64,7 @@ export default {
'shoulderToShoulder',
'shoulderToWrist',
'wrist',
],
optionalMeasurements: ['highBust'],
dependencies: {
back: 'base',
front: 'back',
sleevecap: 'front',
sleeve: 'sleevecap',
},
inject: {
back: 'base',
front: 'back',
sleeve: 'sleevecap',
},
hide: ['base', 'sleevecap'],
options: {
// Constants
brianFitSleeve: true,
brianFitCollar: true,
collarFactor: 4.8,
]
// Percentages
acrossBackFactor: { pct: 98, min: 93, max: 100 },
armholeDepthFactor: { pct: 55, min: 50, max: 70 },
backNeckCutout: { pct: 5, min: 2, max: 8 },
bicepsEase: { pct: 15, min: 0, max: 50 },
chestEase: { pct: 15, min: -4, max: 35 },
collarEase: { pct: 5, min: 0, max: 10 },
cuffEase: { pct: 20, min: 0, max: 200 },
frontArmholeDeeper: { pct: 0.2, min: 0, max: 0.5 },
lengthBonus: { pct: 0, min: -4, max: 60 },
shoulderEase: { pct: 0, min: -2, max: 6 },
shoulderSlopeReduction: { pct: 0, min: 0, max: 80 },
// s3 is short for Shoulder Seam Shift
s3Collar: { pct: 0, min: -100, max: 100 },
s3Armhole: { pct: 0, min: -100, max: 100 },
sleevecapEase: { pct: 0, min: 0, max: 10 },
sleevecapTopFactorX: { pct: 50, min: 25, max: 75 },
sleevecapTopFactorY: { pct: 45, min: 35, max: 125 },
sleevecapBackFactorX: { pct: 60, min: 35, max: 65 },
sleevecapBackFactorY: { pct: 33, min: 30, max: 65 },
sleevecapFrontFactorX: { pct: 55, min: 35, max: 65 },
sleevecapFrontFactorY: { pct: 33, min: 30, max: 65 },
sleevecapQ1Offset: { pct: 1.7, min: 0, max: 7 },
sleevecapQ2Offset: { pct: 3.5, min: 0, max: 7 },
sleevecapQ3Offset: { pct: 2.5, min: 0, max: 7 },
sleevecapQ4Offset: { pct: 1, min: 0, max: 7 },
sleevecapQ1Spread1: { pct: 10, min: 4, max: 20 },
sleevecapQ1Spread2: { pct: 15, min: 4, max: 20 },
sleevecapQ2Spread1: { pct: 15, min: 4, max: 20 },
sleevecapQ2Spread2: { pct: 10, min: 4, max: 20 },
sleevecapQ3Spread1: { pct: 10, min: 4, max: 20 },
sleevecapQ3Spread2: { pct: 8, min: 4, max: 20 },
sleevecapQ4Spread1: { pct: 7, min: 4, max: 20 },
sleevecapQ4Spread2: { pct: 6.3, min: 4, max: 20 },
sleeveWidthGuarantee: { pct: 90, min: 25, max: 100 },
sleeveLengthBonus: { pct: 0, min: -40, max: 10 },
export const optionalMeasurements = ['highBust']
// draft for high bust
draftForHighBust: { bool: false },
},
}

View file

@ -0,0 +1,94 @@
// Constants
export const brianFitSleeve = true
export const brianFitCollar = true
export const collarFactor = 4.8
// Percentages
export const acrossBackFactor = { pct: 98, min: 93, max: 100 }
export const armholeDepthFactor = { pct: 55, min: 50, max: 70 }
export const backNeckCutout = { pct: 5, min: 2, max: 8 }
export const bicepsEase = { pct: 15, min: 0, max: 50 }
export const chestEase = { pct: 15, min: -4, max: 35 }
export const collarEase = { pct: 5, min: 0, max: 10 }
export const cuffEase = { pct: 20, min: 0, max: 200 }
export const frontArmholeDeeper = { pct: 0.2, min: 0, max: 0.5 }
export const lengthBonus = { pct: 0, min: -4, max: 60 }
export const shoulderEase = { pct: 0, min: -2, max: 6 }
export const shoulderSlopeReduction = { pct: 0, min: 0, max: 80 }
// s3 is short for Shoulder Seam Shift
export const s3Collar = { pct: 0, min: -100, max: 100 }
export const s3Armhole = { pct: 0, min: -100, max: 100 }
// Sleevecap
export const sleevecapEase = { pct: 0, min: 0, max: 10 }
export const sleevecapTopFactorX = { pct: 50, min: 25, max: 75 }
export const sleevecapTopFactorY = { pct: 45, min: 35, max: 125 }
export const sleevecapBackFactorX = { pct: 60, min: 35, max: 65 }
export const sleevecapBackFactorY = { pct: 33, min: 30, max: 65 }
export const sleevecapFrontFactorX = { pct: 55, min: 35, max: 65 }
export const sleevecapFrontFactorY = { pct: 33, min: 30, max: 65 }
export const sleevecapQ1Offset = { pct: 1.7, min: 0, max: 7 }
export const sleevecapQ2Offset = { pct: 3.5, min: 0, max: 7 }
export const sleevecapQ3Offset = { pct: 2.5, min: 0, max: 7 }
export const sleevecapQ4Offset = { pct: 1, min: 0, max: 7 }
export const sleevecapQ1Spread1 = { pct: 10, min: 4, max: 20 }
export const sleevecapQ1Spread2 = { pct: 15, min: 4, max: 20 }
export const sleevecapQ2Spread1 = { pct: 15, min: 4, max: 20 }
export const sleevecapQ2Spread2 = { pct: 10, min: 4, max: 20 }
export const sleevecapQ3Spread1 = { pct: 10, min: 4, max: 20 }
export const sleevecapQ3Spread2 = { pct: 8, min: 4, max: 20 }
export const sleevecapQ4Spread1 = { pct: 7, min: 4, max: 20 }
export const sleevecapQ4Spread2 = { pct: 6.3, min: 4, max: 20 }
// Sleeve
export const sleeveWidthGuarantee = { pct: 90, min: 25, max: 100 }
export const sleeveLengthBonus = { pct: 0, min: -40, max: 10 }
// Draft for high bust
export const draftForHighBust = { bool: false }
// Helper objects for per-part options
export const _base = {
brianFitSleeve,
brianFitCollar,
collarFactor,
acrossBackFactor,
armholeDepthFactor,
backNeckCutout,
bicepsEase,
chestEase,
collarEase,
cuffEase,
frontArmholeDeeper,
lengthBonus,
shoulderEase,
shoulderSlopeReduction,
s3Collar,
s3Armhole,
draftForHighBust,
}
export const _sleevecap = {
sleevecapEase,
sleevecapTopFactorX,
sleevecapTopFactorY,
sleevecapBackFactorX,
sleevecapBackFactorY,
sleevecapFrontFactorX,
sleevecapFrontFactorY,
sleevecapQ1Offset,
sleevecapQ2Offset,
sleevecapQ3Offset,
sleevecapQ4Offset,
sleevecapQ1Spread1,
sleevecapQ1Spread2,
sleevecapQ2Spread1,
sleevecapQ2Spread2,
sleevecapQ3Spread1,
sleevecapQ3Spread2,
sleevecapQ4Spread1,
sleevecapQ4Spread2,
sleeveWidthGuarantee,
}
export const _sleeve = { sleeveLengthBonus }

View file

@ -1,7 +1,11 @@
import * as shared from './shared'
import base from './base'
export default (part) => {
let {
export default {
from: base,
name: 'back',
draft: (part) => {
const {
store,
sa,
points,
@ -182,3 +186,4 @@ export default (part) => {
return part
}
}

View file

@ -1,5 +1,12 @@
export default (part) => {
let {
import { _base as options } from '../config/options.js'
export default {
name: 'base',
hide: true,
options,
draft: (part) => {
const {
measurements,
options,
store,
@ -171,3 +178,4 @@ export default (part) => {
return part
}
}

View file

@ -1,7 +1,11 @@
import * as shared from './shared'
import back from './back'
export default (part) => {
let {
export default {
from: back,
name: 'front',
draft: (part) => {
const {
store,
sa,
Point,
@ -191,3 +195,4 @@ export default (part) => {
return part
}
}

View file

@ -1,38 +1,38 @@
// FreeSewing core library
import freesewing from '@freesewing/core'
import plugins from '@freesewing/plugin-bundle'
import plugin from '@freesewing/plugin-bust' // Note: conditional plugin
import config from '../config'
// Parts
import draftBase from './base'
import draftBack from './back'
import draftFront from './front'
import draftSleevecap from './sleevecap'
import draftSleeve from './sleeve'
// FreeSewing Plugins
import pluginBundle from '@freesewing/plugin-bundle'
import bustPlugin from '@freesewing/plugin-bust' // Note: conditional plugin
// Design config & options
import { info, measurements, optionalMeasurements } from '../config/index'
//import * as options from '../config/options'
// Design parts
import back from './back'
import front from './front'
import sleeve from './sleeve'
// These are only here to be exported
import base from './base'
import sleevecap from './sleevecap'
/* Check to see whether we should load the bust plugin
* Only of the `draftForHighBust` options is set
* AND the highBust measurement is available
*/
const condition = (settings = false) =>
settings &&
settings.options &&
settings.options.draftForHighBust &&
settings.measurements.highBust
? true
: false
// Create design
const Brian = new freesewing.Design(config, plugins, { plugin, condition })
// Attach draft methods to prototype
Brian.prototype.draftBase = draftBase
Brian.prototype.draftBack = draftBack
Brian.prototype.draftFront = draftFront
Brian.prototype.draftSleevecap = draftSleevecap
Brian.prototype.draftSleeve = draftSleeve
// Setup design
const Brian = new freesewing.Design({
...info,
measurements,
optionalMeasurements,
// options: { ...options },
parts: { back, front, sleeve },
plugins: pluginBundle,
conditionalPlugins: {
plugin: bustPlugin,
condition: (settings=false) =>
settings?.options?.draftForHighBust &&
settings?.measurements?.highBust
? true : false
}
})
// Named exports
export { config, Brian }
export { back, front, sleeve, base, sleevecap }
// Default export
export default Brian

View file

@ -1,4 +1,11 @@
export default (part) => {
import sleevecap from './sleevecap'
import { _sleeve as options } from '../config/options.js'
export default {
from: sleevecap,
name: 'sleeve',
options,
draft: (part) => {
const {
store,
sa,
@ -15,6 +22,12 @@ export default (part) => {
macro,
} = part.shorthand()
// Remove things inherited
macro('cutonfold', false)
macro('rmad')
delete paths.waist
for (const key in snippets) delete snippets[key]
// Determine the sleeve length
store.set('sleeveLength', measurements.shoulderToWrist * (1 + options.sleeveLengthBonus))
points.sleeveTip = paths.sleevecap.edge('top')
@ -87,3 +100,4 @@ export default (part) => {
}
return part
}
}

View file

@ -1,3 +1,6 @@
import front from './front'
import { _sleevecap as options } from '../config/options.js'
/** Calculates the differece between actual and optimal sleevecap length
* Positive values mean sleevecap is longer than armhole
*/
@ -136,8 +139,13 @@ function draftSleevecap(part, run) {
}
}
export default (part) => {
let { store, units, options, Point, points, paths, raise } = part.shorthand()
export default {
from: front,
name: 'sleevecap',
hide: true,
options,
draft: (part) => {
const { store, units, options, Point, points, paths, raise } = part.shorthand()
store.set('sleeveFactor', 1)
let run = 0
@ -158,3 +166,4 @@ export default (part) => {
return part
}
}

View file

@ -5,7 +5,17 @@ import Pattern from './pattern'
* So it's sort of a super-constructor
*/
export default function Design(config, plugins = false, conditionalPlugins = false) {
// Add part options to config
if (!config.options) config.options = {}
if (config.parts) {
for (const partName in config.parts) {
if (config.parts[partName].options) {
for (const optionName in config.parts[partName].options) {
config.options[optionName] = config.parts[partName].options[optionName]
}
}
}
}
// Ensure all options have a hide() method
config.options = optionsWithHide(config.options)

View file

@ -1,4 +1,4 @@
import { macroName, sampleStyle, capitalize } from './utils'
import { macroName, sampleStyle, capitalize, decoratePartDependency } from './utils'
import Part from './part'
import Point from './point'
import Path from './path'
@ -77,26 +77,12 @@ export default function Pattern(config = { options: {} }) {
if (typeof this.config.dependencies === 'undefined') this.config.dependencies = {}
if (typeof this.config.inject === 'undefined') this.config.inject = {}
if (typeof this.config.hide === 'undefined') this.config.hide = []
this.config.resolvedDependencies = this.resolveDependencies(this.config.dependencies)
this.config.draftOrder = this.draftOrder(this.config.resolvedDependencies)
// Convert options
for (let i in config.options) {
let option = config.options[i]
if (typeof option === 'object') {
if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100
else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm
else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg
else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count
else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool
else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt
else {
let err = 'Unknown option type: ' + JSON.stringify(option)
this.raise.error(err)
throw new Error(err)
}
} else {
this.settings.options[i] = option
this.addOptions(this.config.options)
if (this.config.parts) {
for (const partName in this.config.parts) {
if (this.config.parts[partName].options) this.addOptions(this.config.parts[partName].options)
}
}
@ -127,6 +113,59 @@ export default function Pattern(config = { options: {} }) {
}
}
// Converts/adds options
Pattern.prototype.addOptions = function(options={}) {
for (let i in options) {
const option = options[i]
if (typeof option === 'object') {
if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100
else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm
else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg
else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count
else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool
else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt
else {
let err = 'Unknown option type: ' + JSON.stringify(option)
this.raise.error(err)
throw new Error(err)
}
} else {
this.settings.options[i] = option
}
}
// Make it chainable
return this
}
/*
* Defer some things that used to happen in the constructor to
* facilitate late-stage adding of parts
*/
Pattern.prototype.init = function () {
// Resolve all dependencies
this.dependencies = this.config.dependencies
this.inject = this.config.inject
this.hide = this.config.hide
if (Array.isArray(this.config.parts)) {
this.resolvedDependencies = this.resolveDependencies(this.dependencies)
}
else if (typeof this.config.parts === 'object') {
this.__parts = this.config.parts
this.preresolveDependencies()
this.resolvedDependencies = this.resolveDependencies(this.dependencies)
}
this.config.draftOrder = this.draftOrder(this.resolvedDependencies)
// Make all parts uniform
for (const [key, value] of Object.entries(this.__parts)) {
this.__parts[key] = decoratePartDependency(value)
}
return this
}
function snappedOption(option, pattern) {
const conf = pattern.config.options[option]
const abs = conf.toAbs(pattern.settings.options[option], pattern.settings)
@ -191,10 +230,26 @@ Pattern.prototype.runHooks = function (hookName, data = false) {
}
}
/*
* Allows adding a part at run-time
*/
Pattern.prototype.addPart = function (part, name=false, key) {
if (!part.draft) part = decoratePartDependency(part, givenName)
if (typeof part?.draft === 'function') {
this.__parts[part.name] = part
}
else this.raise.warning(`Cannot attach part ${name} because it is not a part`)
return this
}
/**
* The default draft method with pre- and postDraft hooks
*/
Pattern.prototype.draft = function () {
// Late-stage initialization
this.init()
if (this.is !== 'sample') {
this.is = 'draft'
this.cutList = {}
@ -212,34 +267,50 @@ Pattern.prototype.draft = function () {
}
this.runHooks('preDraft')
for (let partName of this.config.draftOrder) {
for (const partName of this.config.draftOrder) {
// Create parts
this.raise.debug(`Creating part \`${partName}\``)
this.parts[partName] = new this.Part(partName)
if (typeof this.config.inject[partName] === 'string') {
// Handle inject/inheritance
if (typeof this.inject[partName] === 'string') {
this.raise.debug(
`Injecting part \`${this.config.inject[partName]}\` into part \`${partName}\``
`Injecting part \`${this.inject[partName]}\` into part \`${partName}\``
)
try {
this.parts[partName].inject(this.parts[this.config.inject[partName]])
this.parts[partName].inject(this.parts[this.inject[partName]])
} catch (err) {
this.raise.error([
`Could not inject part \`${this.config.inject[partName]}\` into part \`${partName}\``,
`Could not inject part \`${this.inject[partName]}\` into part \`${partName}\``,
err,
])
}
}
if (this.needs(partName)) {
let method = 'draft' + capitalize(partName)
if (typeof this[method] !== 'function') {
this.raise.error(`Method \`pattern.${method}\` is callable`)
throw new Error('Method "' + method + '" on pattern object is not callable')
// Draft part
const method = 'draft' + capitalize(partName)
if (typeof this.__parts?.[partName]?.draft === 'function') {
// 2022 way - Part is contained in config
try {
this.parts[partName] = this.__parts[partName].draft(this.parts[partName])
if (this.parts[partName].render) this.cutList[partName] = this.parts[partName].cut
} catch (err) {
this.raise.error([`Unable to draft part \`${partName}\``, err])
}
}
else if (typeof this[method] === 'function') {
// Legacy way - Part is attached to the prototype
this.raise.warning(`Adding parts to the prototype is deprecated and will be removed in FreeSewing v4 (part: \`${partName}\`)`)
try {
this.parts[partName] = this[method](this.parts[partName])
if (this.parts[partName].render ) this.cutList[partName] = this.parts[partName].cut
} catch (err) {
this.raise.error([`Unable to draft part \`${partName}\``, err])
}
}
else {
this.raise.error(`Unable to draft pattern. Part is not available in iether legacy or 2022`)
throw new Error('Method "' + method + '" on pattern object is not callable')
}
if (typeof this.parts[partName] === 'undefined') {
this.raise.error(
`Result of \`pattern.${method}\` was \`undefined\`. Did you forget to return the \`Part\` object?`
@ -570,7 +641,7 @@ Pattern.prototype.draftOrder = function (graph = this.resolveDependencies()) {
Pattern.prototype.resolveDependency = function (
seen,
part,
graph = this.config.dependencies,
graph = this.dependencies,
deps = []
) {
if (typeof seen[part] === 'undefined') seen[part] = true
@ -586,33 +657,52 @@ Pattern.prototype.resolveDependency = function (
return deps
}
/** Pre-Resolves part dependencies that are passed in 2022 style */
Pattern.prototype.preresolveDependencies = function (count=0) {
const len = Object.keys(this.__parts).length
if (!this.__parts) return
for (const [name, part] of Object.entries(this.__parts)) {
if (part.from) {
this.inject[name] = part.from.name
if (typeof this.__parts[part.from.name] === 'undefined') {
this.__parts[part.from.name] = decoratePartDependency(part.from)
}
}
}
const newlen = Object.keys(this.__parts).length
return (Object.keys(this.__parts).length > len)
? this.preresolveDependencies()
: this
}
/** Resolves part dependencies into a flat array */
Pattern.prototype.resolveDependencies = function (graph = this.config.dependencies) {
for (let i in this.config.inject) {
let dependency = this.config.inject[i]
if (typeof this.config.dependencies[i] === 'undefined') this.config.dependencies[i] = dependency
else if (this.config.dependencies[i] !== dependency) {
if (typeof this.config.dependencies[i] === 'string') {
this.config.dependencies[i] = [this.config.dependencies[i], dependency]
} else if (Array.isArray(this.config.dependencies[i])) {
if (this.config.dependencies[i].indexOf(dependency) === -1)
this.config.dependencies[i].push(dependency)
Pattern.prototype.resolveDependencies = function (graph = this.dependencies) {
for (let i in this.inject) {
let dependency = this.inject[i]
if (typeof this.dependencies[i] === 'undefined') this.dependencies[i] = dependency
else if (this.dependencies[i] !== dependency) {
if (typeof this.dependencies[i] === 'string') {
this.dependencies[i] = [this.dependencies[i], dependency]
} else if (Array.isArray(this.dependencies[i])) {
if (this.dependencies[i].indexOf(dependency) === -1)
this.dependencies[i].push(dependency)
} else {
this.raise.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')
}
}
// Parts both in the parts and dependencies array trip up the dependency resolver
if (Array.isArray(this.config.parts)) {
let pos = this.config.parts.indexOf(this.config.inject[i])
if (pos !== -1) this.config.parts.splice(pos, 1)
if (Array.isArray(this.__parts)) {
let pos = this.__parts.indexOf(this.inject[i])
if (pos !== -1) this.__parts.splice(pos, 1)
}
}
// Include parts outside the dependency graph
if (Array.isArray(this.config.parts)) {
for (let part of this.config.parts) {
if (typeof this.config.dependencies[part] === 'undefined') this.config.dependencies[part] = []
if (typeof this.dependencies[part] === 'undefined') this.dependencies[part] = []
}
}
@ -632,15 +722,15 @@ Pattern.prototype.needs = function (partName) {
if (typeof this.settings.only === 'undefined' || this.settings.only === false) return true
else if (typeof this.settings.only === 'string') {
if (this.settings.only === partName) return true
if (Array.isArray(this.config.resolvedDependencies[this.settings.only])) {
for (let dependency of this.config.resolvedDependencies[this.settings.only]) {
if (Array.isArray(this.resolvedDependencies[this.settings.only])) {
for (let dependency of this.resolvedDependencies[this.settings.only]) {
if (dependency === partName) return true
}
}
} else if (Array.isArray(this.settings.only)) {
for (let part of this.settings.only) {
if (part === partName) return true
for (let dependency of this.config.resolvedDependencies[part]) {
for (let dependency of this.resolvedDependencies[part]) {
if (dependency === partName) return true
}
}
@ -651,9 +741,11 @@ Pattern.prototype.needs = function (partName) {
/* Checks whether a part is hidden in the config */
Pattern.prototype.isHidden = function (partName) {
if (Array.isArray(this.config.hide)) {
if (this.config.hide.indexOf(partName) !== -1) return true
if (Array.isArray(this.hide)) {
if (this.hide.indexOf(partName) !== -1) return true
}
// 2022 style
if (this.__parts?.[partName]?.hide) return true
return false
}

View file

@ -384,3 +384,7 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
}
}
/*
* Makes sure an object passed to be attached as a part it not merely a method
*/
export const decoratePartDependency = (obj, name) => (typeof obj === 'function') ? { draft: obj, name } : obj