diff --git a/plugins/plugin-annotations/src/cutonfold.mjs b/plugins/plugin-annotations/src/cutonfold.mjs index 17f2ca08c77..49181340f5c 100644 --- a/plugins/plugin-annotations/src/cutonfold.mjs +++ b/plugins/plugin-annotations/src/cutonfold.mjs @@ -1,65 +1,111 @@ +import { getIds } from './utils.mjs' + +/* + * Defaults for the cutonfold macro + */ +const macroDefaults = { + classes: { + line: 'note', + text: 'center fill-note', + }, + id: 'cutonfold', + grainline: false, + margin: 0.05, + offset: 15, +} + // Export defs export const cutonfoldDefs = [ { name: 'cutonfoldFrom', def: ` - - + + `, }, { name: 'cutonfoldTo', def: ` - - + + `, }, ] -// Export macros -export const cutonfoldMacros = { - cutonfold: function (so, { points, paths, Path, complete, store, scale }) { - if (so === false) { - delete points.cutonfoldFrom - delete points.cutonfoldTo - delete points.cutonfoldVia1 - delete points.cutonfoldVia2 - delete paths.cutonfoldCutonfold - - store.cutlist.setCutOnFold(false) // Restore default - return true - } - so = { - offset: 15, - margin: 5, - prefix: 'cutonfold', - ...so, - } - - // store in cutlist - store.cutlist.setCutOnFold(so.from, so.to) - if (so.grainline) store.cutlist.setGrain(so.from.angle(so.to)) - - if (complete) { - points[so.prefix + 'From'] = so.from.shiftFractionTowards(so.to, so.margin / 100) - points[so.prefix + 'To'] = so.to.shiftFractionTowards(so.from, so.margin / 100) - points[so.prefix + 'Via1'] = points[so.prefix + 'From'] - .shiftTowards(so.from, so.offset * scale) - .rotate(-90, points[so.prefix + 'From']) - points[so.prefix + 'Via2'] = points[so.prefix + 'To'] - .shiftTowards(so.to, so.offset * scale) - .rotate(90, points[so.prefix + 'To']) - const text = so.grainline ? 'cutOnFoldAndGrainline' : 'cutOnFold' - paths[so.prefix + 'Cutonfold'] = new Path() - .move(points[so.prefix + 'From']) - .line(points[so.prefix + 'Via1']) - .line(points[so.prefix + 'Via2']) - .line(points[so.prefix + 'To']) - .attr('class', 'note') - .attr('marker-start', 'url(#cutonfoldFrom)') - .attr('marker-end', 'url(#cutonfoldTo)') - .attr('data-text', text) - .attr('data-text-class', 'center fill-note') - } - }, +/* + * The rmcutonfold macro + */ +const rmcutonfold = function (id = macroDefaults.id, { paths, store, part }) { + for (const pid of Object.values( + store.get(['parts', part.name, 'macros', 'cutonfold', 'ids', id, 'paths'], {}) + )) + delete paths[pid] } + +/* + * The cutonfold macro + */ +const cutonfold = function (config, { points, paths, Path, complete, store, scale, part }) { + /* + * Don't add a cutonfold indicator when complete is false, unless force is true + */ + if (!complete && !config.force) return + + /* + * Merge macro defaults with user-provided config to create the macro config (mc) + */ + const mc = { + ...macroDefaults, + ...config, + classes: macroDefaults.classes, + } + if (config.classes) mc.classes = { ...mc.classes, ...config.classes } + + /* + * Make sure mc.from and mc.to are Point instances + */ + if (!mc.from || typeof mc.from.attr !== 'function') { + log.warn(`Cutonfold macro called without a valid from point. Using (0,0) for from.`) + mc.from = new Point(0, 0) + } + if (!mc.to || typeof mc.to.attr !== 'function') { + log.warn(`Cutonfold macro called without a valid to point. Using (6660,666) for to.`) + mc.to = new Point(666, 666) + } + + /* + * Store cutonfold and optional grainline angle for use in cutlist + */ + store.cutlist.setCutOnFold(mc.from, mc.to) + if (mc.grainline) store.cutlist.setGrain(mc.from.angle(mc.to)) + + /* + * Get the list of IDs + */ + const ids = getIds(['line'], mc.id, 'cutonfold') + + /* + * Draw the path + */ + const from = mc.from.shiftFractionTowards(mc.to, mc.margin / 100) + const to = mc.to.shiftFractionTowards(mc.from, mc.margin / 100) + const via1 = from.shiftTowards(mc.from, mc.offset * scale).rotate(-90, from) + const via2 = to.shiftTowards(mc.to, mc.offset * scale).rotate(90, to) + paths[ids.line] = new Path() + .move(from) + .line(via1) + .line(via2) + .line(to) + .attr('class', mc.classes.line) + .attr('marker-start', 'url(#cutonfoldFrom)') + .attr('marker-end', 'url(#cutonfoldTo)') + .addText(mc.grainline ? 'cutOnFoldAndGrainline' : 'cutOnFold', mc.classes.text) + + /* + * Store all IDs in the store so we can remove this macro with rmcutonfold + */ + store.set(['parts', part.name, 'macros', 'cutonfold', 'ids', mc.id, 'paths'], ids) +} + +// Export macros +export const cutonfoldMacros = { cutonfold, rmcutonfold }