1
0
Fork 0

Merge branch 'v3-stacks' into develop

This commit is contained in:
Joost De Cock 2022-09-14 13:24:38 +02:00
commit 8cc4c588ff
13 changed files with 442 additions and 137 deletions

View file

@ -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,

View file

@ -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

View file

@ -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
}

140
packages/core/src/stack.mjs Normal file
View file

@ -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

View file

@ -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
}

View file

@ -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