1
0
Fork 0

wip(core): Started working on support for stacks in core

This commit is contained in:
joostdecock 2022-09-12 20:10:22 +02:00
parent dbb0ceb35d
commit 85748db201
7 changed files with 238 additions and 36 deletions

View file

@ -26,6 +26,7 @@ export function Part() {
this.points = {} this.points = {}
this.paths = {} this.paths = {}
this.snippets = {} this.snippets = {}
this.name = null
return this return this
} }
@ -121,8 +122,8 @@ Part.prototype.boundary = function () {
return this return this
} }
/** Stacks part so that its top left corner is in (0,0) */ /** Homes part so that its top left corner is in (0,0) */
Part.prototype.stack = function () { Part.prototype.home = function () {
if (this.topLeft !== false) return this if (this.topLeft !== false) return this
else this.boundary() else this.boundary()
if (this.topLeft.x == 0 && this.topLeft.y == 0) return this if (this.topLeft.x == 0 && this.topLeft.y == 0) return this

View file

@ -9,6 +9,7 @@ import {
mergeDependencies, mergeDependencies,
} from './utils.mjs' } from './utils.mjs'
import { Part } from './part.mjs' import { Part } from './part.mjs'
import { Stack } from './stack.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'
@ -23,7 +24,7 @@ export function Pattern(config) {
addNonEnumProp(this, 'plugins', {}) addNonEnumProp(this, 'plugins', {})
addNonEnumProp(this, 'width', 0) addNonEnumProp(this, 'width', 0)
addNonEnumProp(this, 'height', 0) addNonEnumProp(this, 'height', 0)
addNonEnumProp(this, 'autoLayout', { parts: {} }) addNonEnumProp(this, 'autoLayout', { stacks: {} })
addNonEnumProp(this, 'is', '') addNonEnumProp(this, 'is', '')
addNonEnumProp(this, 'hooks', new Hooks()) addNonEnumProp(this, 'hooks', new Hooks())
addNonEnumProp(this, 'Point', Point) addNonEnumProp(this, 'Point', Point)
@ -41,6 +42,7 @@ export function Pattern(config) {
// Enumerable properties // Enumerable properties
this.config = config // Design config this.config = config // Design config
this.parts = {} // Drafted parts container this.parts = {} // Drafted parts container
this.stacks = {} // Drafted stacks container
this.store = new Store() // Store for sharing data across parts this.store = new Store() // Store for sharing data across parts
return this return this
@ -86,6 +88,7 @@ Pattern.prototype.__createPartWithContext = function (name) {
// Context object to add to Part closure // Context object to add to Part closure
const part = new Part() const part = new Part()
part.name = name part.name = name
part.stack = this.__parts[name]?.stack || name
part.context = { part.context = {
parts: this.parts, parts: this.parts,
config: this.config, config: this.config,
@ -536,40 +539,44 @@ Pattern.prototype.macro = function (key, method) {
this.macros[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 () { Pattern.prototype.pack = function () {
if (this.store.logs.error.length > 0) { if (this.store.logs.error.length > 0) {
this.store.log.warning(`One or more errors occured. Not packing pattern parts`) this.store.log.warning(`One or more errors occured. Not packing pattern parts`)
return this return this
} }
// First, create all stacks
this.stacks = {}
for (const [name, part] of Object.entries(this.parts)) {
if (typeof this.stacks[part.stack] === 'undefined') this.stacks[part.stack] = new Stack(part.stack)
this.stacks[part.stack].addPart(part)
}
let bins = [] let bins = []
for (let key in this.parts) { for (const [key, stack] of Object.entries(this.stacks)) {
let part = this.parts[key] // Avoid multiple render calls to cause addition of transforms
// Avoid multiple render calls to cause stacking of transforms stack.attributes.remove('transform')
part.attributes.remove('transform') if (!this.isStackHidden(key)) {
if (part.render) { stack.home()
part.stack() if (this.settings.layout === true) bins.push({ id: key, width: stack.width, height: stack.height })
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 })
else { else {
if (this.width < width) this.width = width if (this.width < stack.width) this.width = stack.width
if (this.height < height) this.height = height if (this.height < stack.height) this.height = stack.height
} }
} }
} }
if (this.settings.layout === true) { if (this.settings.layout === true) {
let size = pack(bins, { inPlace: true }) let size = pack(bins, { inPlace: true })
for (let bin of bins) { for (let bin of bins) {
this.autoLayout.parts[bin.id] = { move: {} } this.autoLayout.stacks[bin.id] = { move: {} }
let part = this.parts[bin.id] let stack = this.stacks[bin.id]
if (bin.x !== 0 || bin.y !== 0) { 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 = { this.autoLayout.stacks[bin.id].move = {
x: bin.x + part.layout.move.x, x: bin.x + stack.layout.move.x,
y: bin.y + part.layout.move.y, y: bin.y + stack.layout.move.y,
} }
} }
this.width = size.width this.width = size.width
@ -577,11 +584,11 @@ Pattern.prototype.pack = function () {
} else if (typeof this.settings.layout === 'object') { } else if (typeof this.settings.layout === 'object') {
this.width = this.settings.layout.width this.width = this.settings.layout.width
this.height = this.settings.layout.height 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 // Some parts are added by late-stage plugins
if (this.parts[partId]) { if (this.stacks[stackId]) {
let transforms = this.settings.layout.parts[partId] let transforms = this.settings.layout.stacks[stackId]
this.parts[partId].generateTransform(transforms) this.stacks[stackId].generateTransform(transforms)
} }
} }
} }
@ -783,6 +790,23 @@ Pattern.prototype.isHidden = function (partName) {
return false 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 /** Returns props required to render this pattern through
* an external renderer (eg. a React component) * an external renderer (eg. a React component)
*/ */

View file

@ -0,0 +1,82 @@
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, 'topLeft', false)
utils.addNonEnumProp(this, 'bottomRight', false)
utils.addNonEnumProp(this, 'width', false)
utils.addNonEnumProp(this, 'height', false)
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
// Enumerable properties
this.attributes = new Attributes()
this.parts = new Set()
this.name = name
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 () {
this.topLeft = new Point(Infinity, Infinity)
this.bottomRight = new Point(-Infinity, -Infinity)
for (const part of this.getPartList()) {
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
this.width = this.bottomRight.x - this.topLeft.x
this.height = this.bottomRight.y - this.topLeft.y
return this
}
export default Stack

View file

@ -5,12 +5,6 @@ import get from 'lodash.get'
const avoid = ['set', 'setIfUnset', 'push', 'unset', 'get', 'extend'] const avoid = ['set', 'setIfUnset', 'push', 'unset', 'get', 'extend']
export function Store(methods = []) { 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 * Default logging methods
* You can override these with a plugin * You can override these with a plugin
@ -37,6 +31,12 @@ export function Store(methods = []) {
} }
this.logs = logs 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 return this
} }

View file

@ -195,7 +195,7 @@ describe('Part', () => {
const design = new Design({ parts: [ part ]}) const design = new Design({ parts: [ part ]})
const pattern = new design({ paperless: true }) const pattern = new design({ paperless: true })
pattern.draft() pattern.draft()
pattern.parts.test.stack() pattern.parts.test.home()
console.log(pattern.parts.test.attributes) console.log(pattern.parts.test.attributes)
expect(part.attributes.get('transform')).to.equal('translate(-17, -74)') expect(part.attributes.get('transform')).to.equal('translate(-17, -74)')
}) })
@ -208,9 +208,9 @@ describe('Part', () => {
part.points.from = new short.Point(2, 2) part.points.from = new short.Point(2, 2)
part.points.to = new short.Point(19, 76) part.points.to = new short.Point(19, 76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to) 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) expect(part.attributes.get('transform')).to.equal(false)
part.stack() part.home()
expect(part.attributes.get('transform')).to.equal(false) expect(part.attributes.get('transform')).to.equal(false)
}) })
*/ */
@ -252,7 +252,7 @@ describe('Part', () => {
part.points.from = new short.Point(2, 2) part.points.from = new short.Point(2, 2)
part.points.to = new short.Point(19, 76) part.points.to = new short.Point(19, 76)
part.paths.test = new short.Path().move(part.points.from).line(part.points.to) part.paths.test = new short.Path().move(part.points.from).line(part.points.to)
part.stack() part.home()
part.generateTransform({ part.generateTransform({
move: { move: {
x: 10, x: 10,

View file

@ -18,7 +18,7 @@ describe('Pattern', () => {
expect(typeof pattern.config).to.equal('object') expect(typeof pattern.config).to.equal('object')
expect(typeof pattern.parts).to.equal('object') expect(typeof pattern.parts).to.equal('object')
expect(typeof pattern.store).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', () => { it('Pattern constructor should add non-enumerable properties', () => {

View file

@ -0,0 +1,95 @@
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.store.logs)
//console.log(pattern.parts)
//pattern.render()
console.log(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)
})
})
})