1
0
Fork 0
freesewing/sites/shared/components/workbench/layout/cut/plugin-cut-layout.mjs
2023-04-20 10:21:09 -04:00

236 lines
9.3 KiB
JavaScript

import { addToOnly } from '../plugin-layout-part.mjs'
import { pluginFlip } from '@freesewing/plugin-flip'
import { pluginMirror } from '@freesewing/plugin-mirror'
const prefix = 'mirroredOnFold'
// types of path operations
const opTypes = ['to', 'cp1', 'cp2']
const avoidRegx = new RegExp(`^(cutonfold|grainline|__scalebox|__miniscale|${prefix})`)
/**
* The plugin to handle all business related to mirroring, rotating, and duplicating parts for the cutting layout
* @param {string} material the material to generate a cutting layout for
* @param {number} grainAngle the angle of the material's grain
* @return {Object} the plugin
*/
export const cutLayoutPlugin = function (material, grainAngle) {
return {
hooks: {
// after each part
postPartDraft: (pattern) => {
// if it's a duplicated cut part, the fabric part, or it's not wanted by the pattern
if (
pattern.activePart.startsWith('cut.') ||
pattern.activePart === 'fabric' ||
!pattern.__wants(pattern.activePart)
)
return
// get the part that's just been drafted
const part = pattern.parts[pattern.activeSet][pattern.activePart]
// get this part's cutlist configuration
let partCutlist = pattern.setStores[pattern.activeSet].get(['cutlist', pattern.activePart])
// if there isn't one, we're done here
if (!partCutlist) return
// if the cutlist has materials but this isn't one of them
// or it has no materials but this isn't the main fabric
if (partCutlist.materials ? !partCutlist.materials[material] : material !== 'fabric') {
// hide the part because it shouldn't be shown on this fabric
part.hide()
return
}
// get the cutlist configuration for this material
const matCutConfig = partCutlist.materials?.[material]
// if there's specific instructions for this material
if (matCutConfig) {
// get the config of the active part to be inherited by all duplicates
const activePartConfig = pattern.config.parts[pattern.activePart]
// hide the active part so that all others can inherit from it and be manipulated separately
part.hide()
// for each set of cutting instructions for this material
matCutConfig.forEach(({ cut, identical, bias, ignoreOnFold }, i) => {
// get the grain angle for the part for this set of instructions
const cGrain = partCutlist.grain ? partCutlist.grain + (bias ? 45 : 0) : undefined
// for each piece that should be cut
for (let c = 0; c < cut; c++) {
const dupPartName = `cut.${pattern.activePart}.${material}_${c + i + 1}`
// make a new part that will follow these cutting instructions
pattern.addPart({
name: dupPartName,
from: activePartConfig,
draft: ({ part, macro }) => {
// handle fold and grain for these cutting instructions
macro('handleFoldAndGrain', {
partCutlist,
grainSpec: cGrain,
ignoreOnFold,
bias,
})
// if they shouldn't be identical, flip every other piece
if (!identical && c % 2 === 1) macro('flip')
return part
},
})
// add it to the only list if there is one
addToOnly(pattern, dupPartName)
}
})
}
// if there wasn't a specific configuration, still make sure to handle fold and grain
else {
const { macro } = part.shorthand()
macro('handleFoldAndGrain', { partCutlist, grainSpec: partCutlist.grain })
}
},
},
macros: {
...pluginFlip.macros,
...pluginMirror.macros,
// handle mirroring on the fold and rotating to sit along the grain or bias
handleFoldAndGrain: ({ partCutlist, grainSpec, ignoreOnFold, bias }, { points, macro }) => {
// if the part has cutonfold instructions
if (partCutlist.cutOnFold) {
// if we're not meant to igore those instructions, mirror on the fold
if (!ignoreOnFold) macro('mirrorOnFold', { fold: partCutlist.cutOnFold })
// if we are meant to ignore those instructions, but there's a grainline
else if (grainSpec !== undefined) {
// replace the cutonfold with a grainline
macro('grainline', { from: points.cutonfoldVia1, to: points.cutonfoldVia2 })
macro('cutonfold', false)
}
}
// if there's a grain angle, rotate the part to be along it
if (grainSpec !== undefined)
macro('rotateToGrain', { grainAngle, bias, partGrain: grainSpec })
},
// mirror the part across the line indicated by cutonfold
mirrorOnFold: ({ fold }, { paths, snippets, utils, macro, points }) => {
// get all the paths to mirror
const mirrorPaths = []
for (const p in paths) {
// skip ones that are hidden
if (!paths[p].hidden && !p.match(avoidRegx)) mirrorPaths.push(paths[p])
}
// store all the points to mirror
const mirrorPoints = []
// store snippets by type so we can re-sprinkle later
const snippetsByType = {}
// for each snippet
for (var s in snippets) {
const snip = snippets[s]
// don't mirror these ones
if (['logo'].indexOf(snip.def) > -1) continue
// get or make an array for this type of snippet
snippetsByType[snip.def] = snippetsByType[snip.def] || []
// put the anchor on the list to mirror
mirrorPoints.push(snip.anchor)
// then we have to find the name of that point so we can apply the snippet to its mirror
for (var pName in points) {
if (points[pName] === snip.anchor) {
// add the name-to-be of the mirrored anchor to the list for resprinkling
snippetsByType[snip.def].push(prefix + utils.capitalize(pName))
break
}
}
}
// mirror
let unnamed = 0
macro('mirror', {
paths: mirrorPaths,
points: mirrorPoints,
mirror: fold,
prefix,
nameFormat: () => {
unnamed++
return `${prefix}_${unnamed}`
},
})
// sprinkle the snippets
for (var def in snippetsByType) {
macro('sprinkle', {
snippet: def,
on: snippetsByType[def],
})
}
},
/**
* rotate the part so that it is oriented properly with regard to the fabric grain
* if the part should be on the bias, this rotates the part to lie on the bias
* while keeping the grainline annotation along the grain
*/
rotateToGrain: ({ partGrain, grainAngle, bias }, { paths, snippets, Point, points }) => {
// if this part doesn't have a grain recorded, bail
if (partGrain === undefined) return
// the amount to rotate is the difference between this part's grain angle (as drafted) and the fabric's grain angle
let toRotate = grainAngle - partGrain
// don't over rotate
if (toRotate >= 180) toRotate -= 180
else if (toRotate <= -180) toRotate += 180
// if there's no difference, don't rotate
if (toRotate === 0) return
// we'll pivot rotations along the grainline to point, with a fallback
const pivot = points.grainlineTo || new Point(0, 0)
// go through all the paths
for (const pathName in paths) {
const path = paths[pathName]
// don't rotate hidden paths
if (paths[pathName].hidden) continue
// we want the grainline indicator to always go in the fabric grain direction
// so if this part is on the bias and this path is the grainline indicator
// we'll rotate it 45 degrees less than necessary
let thisRotation = toRotate
if (pathName === 'grainline' && bias) thisRotation -= 45
// replace all the points in all the ops of this path with ones that have been rotated
path.ops.forEach((op) => {
opTypes.forEach((t) => {
if (op[t]) op[t] = op[t].rotate(thisRotation, pivot)
})
})
}
// replace all snippet anchors with ones that have been rotated
for (const snippetName in snippets) {
snippets[snippetName].anchor = snippets[snippetName].anchor.rotate(toRotate, pivot)
}
// go through all the points
for (const pointName in points) {
const point = points[pointName]
const pointAttrs = point.attributes
// if it has attributes, we want to rotate it
if (Object.keys(pointAttrs.list).length) {
points[pointName] = point.rotate(toRotate, pivot)
// title points need to be re-rotated around the top title point to avoid text collisions
if (pointName.match(/_(title|exportDate)(?!Nr)/))
points[pointName] = points[pointName].rotate(-toRotate, points.__titleNr)
// put the attributes back onto the new point
points[pointName].attributes = pointAttrs.clone()
}
}
},
},
}
}