diff --git a/packages/core/src/index.mjs b/packages/core/src/index.mjs
index 9a3aa19091d..2707e475770 100644
--- a/packages/core/src/index.mjs
+++ b/packages/core/src/index.mjs
@@ -32,7 +32,7 @@ import {
rad2deg,
pctBasedOn,
Bezier,
- generatePartTransform,
+ generateStackTransform,
macroName,
} from './utils.mjs'
import { version } from '../data.mjs'
@@ -71,7 +71,7 @@ export {
deg2rad,
rad2deg,
pctBasedOn,
- generatePartTransform,
+ generateStackTransform,
macroName,
isCoord,
version,
diff --git a/packages/core/src/part.mjs b/packages/core/src/part.mjs
index 99f12603d41..4c1e5bd9c8f 100644
--- a/packages/core/src/part.mjs
+++ b/packages/core/src/part.mjs
@@ -26,6 +26,7 @@ export function Part() {
this.points = {}
this.paths = {}
this.snippets = {}
+ this.name = null
return this
}
@@ -110,31 +111,15 @@ Part.prototype.boundary = function () {
if (topLeft.y === Infinity) topLeft.y = 0
if (bottomRight.x === -Infinity) bottomRight.x = 0
if (bottomRight.y === -Infinity) bottomRight.y = 0
- // Add margin
- let margin = this.context.settings.margin
- if (this.context.settings.paperless && margin < 10) margin = 10
- this.topLeft = new Point(topLeft.x - margin, topLeft.y - margin)
- this.bottomRight = new Point(bottomRight.x + margin, bottomRight.y + margin)
+
+ this.topLeft = topLeft
+ this.bottomRight = bottomRight
this.width = this.bottomRight.x - this.topLeft.x
this.height = this.bottomRight.y - this.topLeft.y
return this
}
-/** Stacks part so that its top left corner is in (0,0) */
-Part.prototype.stack = function () {
- if (this.topLeft !== false) return this
- else this.boundary()
- if (this.topLeft.x == 0 && this.topLeft.y == 0) return this
- else {
- this.attr('transform', `translate(${this.topLeft.x * -1}, ${this.topLeft.y * -1})`)
- this.layout.move.x = this.topLeft.x * -1
- this.layout.move.y = this.topLeft.y * -1
- }
-
- return this
-}
-
/** Adds an attribute. This is here to make this call chainable in assignment */
Part.prototype.attr = function (name, value, overwrite = false) {
if (overwrite) this.attributes.set(name, value)
@@ -351,15 +336,6 @@ Part.prototype.shorthand = function () {
return shorthand
}
-Part.prototype.generateTransform = function (transforms) {
- const { move, rotate, flipX, flipY } = transforms
- const generated = utils.generatePartTransform(move.x, move.y, rotate, flipX, flipY, this)
-
- for (var t in generated) {
- this.attr(t, generated[t], true)
- }
-}
-
Part.prototype.isEmpty = function () {
if (Object.keys(this.snippets).length > 0) return false
diff --git a/packages/core/src/pattern.mjs b/packages/core/src/pattern.mjs
index 9322d644ce3..ca57b70c1a6 100644
--- a/packages/core/src/pattern.mjs
+++ b/packages/core/src/pattern.mjs
@@ -9,6 +9,7 @@ import {
mergeDependencies,
} from './utils.mjs'
import { Part } from './part.mjs'
+import { Stack } from './stack.mjs'
import { Point } from './point.mjs'
import { Path } from './path.mjs'
import { Snippet } from './snippet.mjs'
@@ -23,7 +24,7 @@ export function Pattern(config) {
addNonEnumProp(this, 'plugins', {})
addNonEnumProp(this, 'width', 0)
addNonEnumProp(this, 'height', 0)
- addNonEnumProp(this, 'autoLayout', { parts: {} })
+ addNonEnumProp(this, 'autoLayout', { stacks: {} })
addNonEnumProp(this, 'is', '')
addNonEnumProp(this, 'hooks', new Hooks())
addNonEnumProp(this, 'Point', Point)
@@ -41,6 +42,7 @@ export function Pattern(config) {
// Enumerable properties
this.config = config // Design config
this.parts = {} // Drafted parts container
+ this.stacks = {} // Drafted stacks container
this.store = new Store() // Store for sharing data across parts
return this
@@ -86,6 +88,7 @@ Pattern.prototype.__createPartWithContext = function (name) {
// Context object to add to Part closure
const part = new Part()
part.name = name
+ part.stack = this.__parts[name]?.stack || name
part.context = {
parts: this.parts,
config: this.config,
@@ -101,6 +104,19 @@ Pattern.prototype.__createPartWithContext = function (name) {
return part
}
+Pattern.prototype.__createStackWithContext = function (name) {
+ // Context object to add to Stack closure
+ const stack = new Stack()
+ stack.name = name
+ stack.context = {
+ config: this.config,
+ settings: this.settings,
+ store: this.store,
+ }
+
+ return stack
+}
+
// Merges default for options with user-provided options
Pattern.prototype.__loadOptionDefaults = function () {
if (Object.keys(this.config.options).length < 1) return this
@@ -536,40 +552,48 @@ Pattern.prototype.macro = function (key, method) {
this.macros[key] = method
}
-/** Packs parts in a 2D space and sets pattern size */
+/** Packs stacks in a 2D space and sets pattern size */
Pattern.prototype.pack = function () {
if (this.store.logs.error.length > 0) {
this.store.log.warning(`One or more errors occured. Not packing pattern parts`)
return this
}
+ // First, create all stacks
+ this.stacks = {}
+ for (const [name, part] of Object.entries(this.parts)) {
+ const stackName = (typeof part.stack === 'function')
+ ? part.stack(this.settings, name)
+ : part.stack
+ if (typeof this.stacks[stackName] === 'undefined')
+ this.stacks[stackName] = this.__createStackWithContext(stackName)
+ this.stacks[stackName].addPart(part)
+ }
+
let bins = []
- for (let key in this.parts) {
- let part = this.parts[key]
- // Avoid multiple render calls to cause stacking of transforms
- part.attributes.remove('transform')
- if (part.render) {
- part.stack()
- let width = part.bottomRight.x - part.topLeft.x
- let height = part.bottomRight.y - part.topLeft.y
- if (this.settings.layout === true) bins.push({ id: key, width, height })
+ for (const [key, stack] of Object.entries(this.stacks)) {
+ // Avoid multiple render calls to cause addition of transforms
+ stack.attributes.remove('transform')
+ if (!this.isStackHidden(key)) {
+ stack.home()
+ if (this.settings.layout === true)
+ bins.push({ id: key, width: stack.width, height: stack.height })
else {
- if (this.width < width) this.width = width
- if (this.height < height) this.height = height
+ if (this.width < stack.width) this.width = stack.width
+ if (this.height < stack.height) this.height = stack.height
}
}
}
if (this.settings.layout === true) {
let size = pack(bins, { inPlace: true })
for (let bin of bins) {
- this.autoLayout.parts[bin.id] = { move: {} }
- let part = this.parts[bin.id]
+ this.autoLayout.stacks[bin.id] = { move: {} }
+ let stack = this.stacks[bin.id]
if (bin.x !== 0 || bin.y !== 0) {
- part.attr('transform', `translate(${bin.x}, ${bin.y})`)
+ stack.attr('transform', `translate(${bin.x}, ${bin.y})`)
}
-
- this.autoLayout.parts[bin.id].move = {
- x: bin.x + part.layout.move.x,
- y: bin.y + part.layout.move.y,
+ this.autoLayout.stacks[bin.id].move = {
+ x: bin.x + stack.layout.move.x,
+ y: bin.y + stack.layout.move.y,
}
}
this.width = size.width
@@ -577,11 +601,11 @@ Pattern.prototype.pack = function () {
} else if (typeof this.settings.layout === 'object') {
this.width = this.settings.layout.width
this.height = this.settings.layout.height
- for (let partId of Object.keys(this.settings.layout.parts)) {
+ for (let stackId of Object.keys(this.settings.layout.stacks)) {
// Some parts are added by late-stage plugins
- if (this.parts[partId]) {
- let transforms = this.settings.layout.parts[partId]
- this.parts[partId].generateTransform(transforms)
+ if (this.stacks[stackId]) {
+ let transforms = this.settings.layout.stacks[stackId]
+ this.stacks[stackId].generateTransform(transforms)
}
}
}
@@ -783,6 +807,23 @@ Pattern.prototype.isHidden = function (partName) {
return false
}
+/* Checks whether (all parts in) a stack is hidden in the config */
+Pattern.prototype.isStackHidden = function (stackName) {
+ if (!this.stacks[stackName]) return true
+ const parts = this.stacks[stackName].getPartNames()
+ if (Array.isArray(this.settings.only)) {
+ for (const partName of parts) {
+ if (this.settings.only.includes(partName)) return false
+ }
+ }
+ for (const partName of parts) {
+ if (this.__parts?.[partName]?.hide) return true
+ if (this.__parts?.[partName]?.hideAll) return true
+ }
+
+ return false
+}
+
/** Returns props required to render this pattern through
* an external renderer (eg. a React component)
*/
@@ -821,6 +862,12 @@ Pattern.prototype.getRenderProps = function () {
}
}
}
+ props.stacks = {}
+ for (let s in this.stacks) {
+ if (!this.isStackHidden(s)) {
+ props.stacks[s] = this.stacks[s]
+ }
+ }
return props
}
diff --git a/packages/core/src/stack.mjs b/packages/core/src/stack.mjs
new file mode 100644
index 00000000000..134aa835ef3
--- /dev/null
+++ b/packages/core/src/stack.mjs
@@ -0,0 +1,140 @@
+import { Attributes } from './attributes.mjs'
+import { Point } from './point.mjs'
+import * as utils from './utils.mjs'
+
+export function Stack(name = null) {
+ // Non-enumerable properties
+ utils.addNonEnumProp(this, 'freeId', 0)
+ utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
+
+ // Enumerable properties
+ this.attributes = new Attributes()
+ this.parts = new Set()
+ this.name = name
+ this.topLeft = false
+ this.bottomRight = false
+ this.width = false
+ this.height = false
+
+ return this
+}
+
+/* Adds a part to the stack */
+Stack.prototype.addPart = function (part) {
+ if (part) this.parts.add(part)
+
+ return this
+}
+
+/* Returns a list of parts in this stack */
+Stack.prototype.getPartList = function (part) {
+ return [...this.parts]
+}
+
+/* Returns a list of names of parts in this stack */
+Stack.prototype.getPartNames = function (part) {
+ return [...this.parts].map((p) => p.name)
+}
+
+/** Homes the stack so that its top left corner is in (0,0) */
+//Stack.prototype.home = function () {
+// const parts = this.getPartList()
+// if (parts.length < 1) return this
+// for (const part of this.getPartList()) {
+// part.home()
+// }
+//
+// if (parts.length === 1) {
+// this.topLeft = part.topLeft
+// this.bottomRigth = part.bottomRight
+// this.width = part.width
+// this.height = part.height
+//
+// return this
+// }
+//
+// return this.boundary()
+//}
+
+/** Calculates the stack's bounding box and sets it */
+Stack.prototype.home = function () {
+ if (this.topLeft) return this // Cached
+ this.topLeft = new Point(Infinity, Infinity)
+ this.bottomRight = new Point(-Infinity, -Infinity)
+ for (const part of this.getPartList()) {
+ part.boundary()
+ if (part.topLeft.x < this.topLeft.x) this.topLeft.x = part.topLeft.x
+ if (part.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y
+ if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x
+ if (part.bottomRight.y > this.bottomRight.y) this.bottomRight.y = part.bottomRight.y
+ }
+
+ // Fix infinity if it's not overwritten
+ if (this.topLeft.x === Infinity) this.topLeft.x = 0
+ if (this.topLeft.y === Infinity) this.topLeft.y = 0
+ if (this.bottomRight.x === -Infinity) this.bottomRight.x = 0
+ if (this.bottomRight.y === -Infinity) this.bottomRight.y = 0
+
+ // Add margin
+ let margin = this.context.settings.margin
+ if (this.context.settings.paperless && margin < 10) margin = 10
+ this.topLeft.x -= margin
+ this.topLeft.y -= margin
+ this.bottomRight.x += margin
+ this.bottomRight.y += margin
+
+ // Set dimensions
+ this.width = this.bottomRight.x - this.topLeft.x
+ this.height = this.bottomRight.y - this.topLeft.y
+ this.width = this.bottomRight.x - this.topLeft.x
+ this.height = this.bottomRight.y - this.topLeft.y
+
+ // Add transform
+ this.anchor = this.getAnchor()
+
+ if (this.topLeft.x === this.anchor.x && this.topLeft.y === this.anchor.y) return this
+ else {
+ this.attr('transform', `translate(${this.anchor.x - this.topLeft.x}, ${this.anchor.y - this.topLeft.y})`)
+ this.layout.move.x = this.anchor.x - this.topLeft.x
+ this.layout.move.y = this.anchor.y - this.topLeft.y
+ }
+
+ return this
+}
+
+/** Finds the anchor to align parts in this stack */
+Stack.prototype.getAnchor = function() {
+ let anchorPoint = true
+ let gridAnchorPoint = true
+ const parts = this.getPartList()
+ for (const part of parts) {
+ if (typeof part.points.anchor === 'undefined') anchorPoint = false
+ if (typeof part.points.gridAnchor === 'undefined') gridAnchorPoint = false
+ }
+
+ if (anchorPoint) return parts[0].points.anchor
+ if (gridAnchorPoint) return parts[0].points.gridAnchor
+
+ return new Point(0,0)
+}
+
+/** Adds an attribute. This is here to make this call chainable in assignment */
+Stack.prototype.attr = function (name, value, overwrite = false) {
+ if (overwrite) this.attributes.set(name, value)
+ else this.attributes.add(name, value)
+
+ return this
+}
+
+/** Generates the transform for a stack */
+Stack.prototype.generateTransform = function (transforms) {
+ const { move, rotate, flipX, flipY } = transforms
+ const generated = utils.generateStackTransform(move.x, move.y, rotate, flipX, flipY, this)
+
+ for (var t in generated) {
+ this.attr(t, generated[t], true)
+ }
+}
+
+
+export default Stack
diff --git a/packages/core/src/store.mjs b/packages/core/src/store.mjs
index b1ff749afe3..bf162122916 100644
--- a/packages/core/src/store.mjs
+++ b/packages/core/src/store.mjs
@@ -5,12 +5,6 @@ import get from 'lodash.get'
const avoid = ['set', 'setIfUnset', 'push', 'unset', 'get', 'extend']
export function Store(methods = []) {
- for (const method of methods) {
- if (avoid.indexOf(method[0]) !== -1) {
- console.log(`WARNING: You can't squat ${method[0]}in the store`)
- } else set(this, ...method)
- }
-
/*
* Default logging methods
* You can override these with a plugin
@@ -37,6 +31,12 @@ export function Store(methods = []) {
}
this.logs = logs
+ for (const method of methods) {
+ if (avoid.indexOf(method[0]) !== -1) {
+ this.logs.warning(`You cannot squat ${method[0]} in the store`)
+ } else set(this, ...method)
+ }
+
return this
}
diff --git a/packages/core/src/utils.mjs b/packages/core/src/utils.mjs
index 7a9aed989e6..824dcc52842 100644
--- a/packages/core/src/utils.mjs
+++ b/packages/core/src/utils.mjs
@@ -361,8 +361,8 @@ export function pctBasedOn(measurement) {
}
}
-/** Generates the transform attributes needed for a given part */
-export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => {
+/** Generates the transform attributes needed for a given stack */
+export const generateStackTransform = (x, y, rotate, flipX, flipY, part) => {
const transforms = []
let xTotal = x || 0
let yTotal = y || 0
diff --git a/packages/core/tests/part.test.mjs b/packages/core/tests/part.test.mjs
index 659bd95c9ac..3dcf6b99d4e 100644
--- a/packages/core/tests/part.test.mjs
+++ b/packages/core/tests/part.test.mjs
@@ -124,7 +124,7 @@ describe('Part', () => {
)
})
- it('Should calculate the part boundary with default margin', () => {
+ it('Should calculate the part boundary', () => {
const design = new Design()
const pattern = new design()
const part = pattern.__createPartWithContext()
@@ -134,52 +134,15 @@ describe('Part', () => {
part.points.to = new short.Point(19, 76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
let boundary = part.boundary()
- expect(boundary.topLeft.x).to.equal(17)
- expect(boundary.topLeft.y).to.equal(74)
- expect(boundary.bottomRight.x).to.equal(125)
- expect(boundary.bottomRight.y).to.equal(458)
+ expect(boundary.topLeft.x).to.equal(19)
+ expect(boundary.topLeft.y).to.equal(76)
+ expect(boundary.bottomRight.x).to.equal(123)
+ expect(boundary.bottomRight.y).to.equal(456)
boundary = part.boundary()
- expect(boundary.width).to.equal(108)
- expect(boundary.height).to.equal(384)
+ expect(boundary.width).to.equal(104)
+ expect(boundary.height).to.equal(380)
})
- it('Should calculate the part boundary with custom margin', () => {
- const design = new Design()
- const pattern = new design({ margin: 5 })
- const part = pattern.__createPartWithContext()
- pattern.init()
- const short = part.shorthand()
- part.points.from = new short.Point(123, 456)
- part.points.to = new short.Point(19, 76)
- part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
- let boundary = part.boundary()
- expect(boundary.topLeft.x).to.equal(14)
- expect(boundary.topLeft.y).to.equal(71)
- expect(boundary.bottomRight.x).to.equal(128)
- expect(boundary.bottomRight.y).to.equal(461)
- boundary = part.boundary()
- expect(boundary.width).to.equal(114)
- expect(boundary.height).to.equal(390)
- })
-
- it('Should calculate the part boundary for paperless', () => {
- const design = new Design()
- const pattern = new design({ paperless: true })
- const part = pattern.__createPartWithContext()
- pattern.init()
- const short = part.shorthand()
- part.points.from = new short.Point(123, 456)
- part.points.to = new short.Point(19, 76)
- part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
- let boundary = part.boundary()
- expect(boundary.topLeft.x).to.equal(9)
- expect(boundary.topLeft.y).to.equal(66)
- expect(boundary.bottomRight.x).to.equal(133)
- expect(boundary.bottomRight.y).to.equal(466)
- boundary = part.boundary()
- expect(boundary.width).to.equal(124)
- expect(boundary.height).to.equal(400)
- })
/*
it('Should stack a part', () => {
const part = {
@@ -195,7 +158,7 @@ describe('Part', () => {
const design = new Design({ parts: [ part ]})
const pattern = new design({ paperless: true })
pattern.draft()
- pattern.parts.test.stack()
+ pattern.parts.test.home()
console.log(pattern.parts.test.attributes)
expect(part.attributes.get('transform')).to.equal('translate(-17, -74)')
})
@@ -208,9 +171,9 @@ describe('Part', () => {
part.points.from = new short.Point(2, 2)
part.points.to = new short.Point(19, 76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
- part.stack()
+ part.home()
expect(part.attributes.get('transform')).to.equal(false)
- part.stack()
+ part.home()
expect(part.attributes.get('transform')).to.equal(false)
})
*/
@@ -243,25 +206,6 @@ describe('Part', () => {
)
})
- it('Should generate the part transforms', () => {
- const design = new Design()
- const pattern = new design({ margin: 5 })
- const part = pattern.__createPartWithContext()
- pattern.init()
- let short = part.shorthand()
- part.points.from = new short.Point(2, 2)
- part.points.to = new short.Point(19, 76)
- part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
- part.stack()
- part.generateTransform({
- move: {
- x: 10,
- y: 20,
- },
- })
- expect(part.attributes.list.transform.length).to.equal(1)
- expect(part.attributes.list.transform[0]).to.equal('translate(10 20)')
- })
describe('isEmpty', () => {
it('Should return true if the part has no paths or snippets', () => {
const design = new Design()
diff --git a/packages/core/tests/pattern-init.test.mjs b/packages/core/tests/pattern-init.test.mjs
index 258ac0cb1d7..f1a094e9a2d 100644
--- a/packages/core/tests/pattern-init.test.mjs
+++ b/packages/core/tests/pattern-init.test.mjs
@@ -18,7 +18,7 @@ describe('Pattern', () => {
expect(typeof pattern.config).to.equal('object')
expect(typeof pattern.parts).to.equal('object')
expect(typeof pattern.store).to.equal('object')
- expect(Object.keys(pattern).length).to.equal(4)
+ expect(Object.keys(pattern).length).to.equal(5)
})
it('Pattern constructor should add non-enumerable properties', () => {
diff --git a/packages/core/tests/stacks.test.mjs b/packages/core/tests/stacks.test.mjs
new file mode 100644
index 00000000000..6191ca93e0e
--- /dev/null
+++ b/packages/core/tests/stacks.test.mjs
@@ -0,0 +1,178 @@
+import chai from 'chai'
+import { round, Design, Point, pctBasedOn } from '../src/index.mjs'
+
+const expect = chai.expect
+
+describe('Stacks', () => {
+ describe('Pattern.init()', () => {
+ const partA = {
+ name: 'test.partA',
+ measurements: ['head'],
+ options: {
+ size: { pct: 40, min: 20, max: 80 },
+ },
+ draft: ({ points, Point, paths, Path, part, store, measurements, options }) => {
+ store.set('size', measurements.head * options.size)
+ points.from = new Point(0, 0)
+ points.to = new Point(0, store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to)
+ return part
+ },
+ stack: 'box',
+ }
+ const partB = {
+ name: 'test.partB',
+ measurements: ['head'],
+ after: partA,
+ draft: ({ points, Point, paths, Path, part, store }) => {
+ points.from = new Point(0, store.get('size'))
+ points.to = new Point(store.get('size'), store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to)
+ return part
+ },
+ stack: 'box',
+ }
+ const partC = {
+ name: 'test.partC',
+ after: partB,
+ draft: ({ points, Point, paths, Path, part, store }) => {
+ points.from = new Point(store.get('size'), store.get('size'))
+ points.to = new Point(store.get('size'), 0)
+ paths.line = new Path().move(points.from).line(points.to)
+ return part
+ },
+ stack: 'box',
+ }
+ const partD = {
+ name: 'test.partD',
+ after: partC,
+ draft: ({ points, Point, paths, Path, part, store }) => {
+ points.from = new Point(store.get('size'), 0)
+ points.to = new Point(0, 0)
+ paths.line = new Path().move(points.from).line(points.to)
+ return part
+ },
+ // stack: 'box',
+ }
+
+ const Pattern = new Design({
+ data: {
+ name: 'test',
+ version: '1.2.3',
+ },
+ parts: [partD],
+ })
+ const pattern = new Pattern({
+ measurements: {
+ head: 400,
+ },
+ })
+ pattern.draft()
+ //console.log(pattern.parts)
+ //pattern.render()
+
+ it('Pattern.init() should resolve dependencies', () => {
+ expect(typeof pattern.config.resolvedDependencies).to.equal('object')
+ expect(Array.isArray(pattern.config.resolvedDependencies['test.partA'])).to.equal(true)
+ expect(pattern.config.resolvedDependencies['test.partA'].length).to.equal(0)
+ 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'][0]).to.equal('test.partA')
+ 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'].indexOf('test.partA') !== -1
+ ).to.equal(true)
+ expect(
+ pattern.config.resolvedDependencies['test.partC'].indexOf('test.partB') !== -1
+ ).to.equal(true)
+ })
+
+
+ it('Should calculate the part boundary', () => {
+ const part = {
+ name: 'test',
+ draft: ({points, Point, paths, Path, part }) => {
+ points.from = new Point(123, 456)
+ points.to = new Point(19, 76)
+ paths.test = new Path().move(points.from).line(points.to)
+
+ return part
+ }
+ }
+ const design = new Design({ parts: [ part ]})
+ const pattern = new design()
+ pattern.draft().render()
+ expect(pattern.stacks.test.topLeft.x).to.equal(17)
+ expect(pattern.stacks.test.topLeft.y).to.equal(74)
+ expect(pattern.stacks.test.bottomRight.x).to.equal(125)
+ expect(pattern.stacks.test.bottomRight.y).to.equal(458)
+ expect(pattern.stacks.test.width).to.equal(108)
+ expect(pattern.stacks.test.height).to.equal(384)
+ })
+
+ it('Should calculate the part boundary with custom margin', () => {
+ const part = {
+ name: 'test',
+ draft: ({points, Point, paths, Path, part }) => {
+ points.from = new Point(123, 456)
+ points.to = new Point(19, 76)
+ paths.test = new Path().move(points.from).line(points.to)
+
+ return part
+ }
+ }
+ const design = new Design({ parts: [ part ]})
+ const pattern = new design({ margin: 5 })
+ pattern.draft().render()
+ expect(pattern.stacks.test.topLeft.x).to.equal(14)
+ expect(pattern.stacks.test.topLeft.y).to.equal(71)
+ expect(pattern.stacks.test.bottomRight.x).to.equal(128)
+ expect(pattern.stacks.test.bottomRight.y).to.equal(461)
+ expect(pattern.stacks.test.width).to.equal(114)
+ expect(pattern.stacks.test.height).to.equal(390)
+ })
+
+ it('Should calculate the part boundary for paperless', () => {
+ const part = {
+ name: 'test',
+ draft: ({points, Point, paths, Path, part }) => {
+ points.from = new Point(123, 456)
+ points.to = new Point(19, 76)
+ paths.test = new Path().move(points.from).line(points.to)
+
+ return part
+ }
+ }
+ const design = new Design({ parts: [ part ]})
+ const pattern = new design({ paperless: true })
+ pattern.draft().render()
+ expect(pattern.stacks.test.topLeft.x).to.equal(9)
+ expect(pattern.stacks.test.topLeft.y).to.equal(66)
+ })
+ it('Should generate the part transforms', () => {
+ const part = {
+ name: 'test',
+ draft: ({points, Point, paths, Path, part }) => {
+ points.from = new Point(2, 2)
+ points.to = new Point(19, 76)
+ paths.test = new Path().move(points.from).line(points.to)
+
+ return part
+ }
+ }
+ const design = new Design({ parts: [ part ] })
+ const pattern = new design()
+ pattern.draft().render()
+ pattern.stacks.test.generateTransform({
+ move: {
+ x: 10,
+ y: 20,
+ },
+ })
+ expect(pattern.stacks.test.attributes.list.transform.length).to.equal(1)
+ expect(pattern.stacks.test.attributes.list.transform[0]).to.equal('translate(10 20)')
+ })
+
+ })
+})
diff --git a/packages/core/tests/utils.test.mjs b/packages/core/tests/utils.test.mjs
index 22880596544..87ef4e349ba 100644
--- a/packages/core/tests/utils.test.mjs
+++ b/packages/core/tests/utils.test.mjs
@@ -28,7 +28,7 @@ import {
rad2deg,
pctBasedOn,
Bezier,
- generatePartTransform,
+ generateStackTransform,
macroName,
Design,
} from '../src/index.mjs'
diff --git a/sites/shared/components/workbench/draft/index.js b/sites/shared/components/workbench/draft/index.js
index 4e6a3959d2d..b0625c095e8 100644
--- a/sites/shared/components/workbench/draft/index.js
+++ b/sites/shared/components/workbench/draft/index.js
@@ -27,7 +27,7 @@ const LabDraft = props => {
return (
<>
- {(!patternProps || patternProps.events?.error?.length > 0)
+ {(!patternProps || patternProps.logs?.error?.length > 0)
?
: null
}
diff --git a/sites/shared/components/workbench/draft/stack.js b/sites/shared/components/workbench/draft/stack.js
new file mode 100644
index 00000000000..e733e205759
--- /dev/null
+++ b/sites/shared/components/workbench/draft/stack.js
@@ -0,0 +1,20 @@
+import Part from './part'
+import { getProps } from './utils'
+
+const Stack = props => {
+ const { stackName, stack, patternProps, gist, app, updateGist, unsetGist, showInfo } = props
+
+ return (
+
+ {[...stack.parts].map((part) => (
+
+ ))}
+
+ )
+}
+
+export default Stack
diff --git a/sites/shared/components/workbench/draft/svg-wrapper.js b/sites/shared/components/workbench/draft/svg-wrapper.js
index 8f737841084..6aeaf14a799 100644
--- a/sites/shared/components/workbench/draft/svg-wrapper.js
+++ b/sites/shared/components/workbench/draft/svg-wrapper.js
@@ -2,7 +2,7 @@ import { SizeMe } from 'react-sizeme'
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"
import Svg from './svg'
import Defs from './defs'
-import Part from './part'
+import Stack from './stack'
/* What's with all the wrapping?
*
@@ -40,11 +40,11 @@ const SvgWrapper = props => {
- {Object.keys(patternProps.parts).map((name) => (
- (
+
))}