2023-09-05 20:38:03 +02:00
|
|
|
/*
|
|
|
|
* Defaults for the title macro
|
|
|
|
*/
|
|
|
|
const macroDefaults = {
|
|
|
|
classes: {
|
|
|
|
lead: 'text-xs bold',
|
|
|
|
title: 'text bold',
|
2023-09-06 08:14:49 +02:00
|
|
|
text: 'text-xs',
|
|
|
|
link: 'text-sm fill-note bold',
|
2023-09-05 20:38:03 +02:00
|
|
|
metric: 'text-xs center',
|
|
|
|
imperial: 'text-xs center',
|
2023-09-08 10:15:41 +02:00
|
|
|
imperialBox: 'scalebox imperial fill-current',
|
|
|
|
metricBox: 'scalebox metric fill-bg',
|
2023-09-05 20:38:03 +02:00
|
|
|
},
|
2023-09-06 08:14:49 +02:00
|
|
|
lead: 'FreeSewing',
|
|
|
|
link: 'FreeSewing.org/patrons/join',
|
2023-09-06 10:30:52 +02:00
|
|
|
text: 'plugin-annotations:supportFreeSewingBecomeAPatron',
|
2023-09-06 08:14:49 +02:00
|
|
|
title: false,
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
2023-03-08 05:15:30 +00:00
|
|
|
|
2023-09-05 20:38:03 +02:00
|
|
|
/*
|
|
|
|
* Various sizes for scaleboxes per units
|
|
|
|
*/
|
|
|
|
const sizes = {
|
|
|
|
scalebox: {
|
|
|
|
metric: [
|
2023-03-11 19:01:40 +01:00
|
|
|
[10, 5, '1cm', '0.5cm'],
|
|
|
|
[20, 10, '2cm', '1cm'],
|
|
|
|
[30, 15, '3cm', '1.5cm'],
|
|
|
|
[40, 20, '4cm', '2cm'],
|
|
|
|
[50, 25, '5cm', '2.5cm'],
|
|
|
|
[60, 30, '6cm', '3cm'],
|
|
|
|
[70, 35, '7cm', '3.5cm'],
|
|
|
|
[80, 40, '8cm', '4cm'],
|
|
|
|
[90, 45, '9cm', '4.5cm'],
|
|
|
|
[100, 50, '10cm', '5cm'],
|
2023-09-05 20:38:03 +02:00
|
|
|
],
|
|
|
|
imperial: [
|
2023-03-11 19:01:40 +01:00
|
|
|
[25.4 * 0.5, 25.4 * 0.25, '½″', '¼″'],
|
|
|
|
[25.4 * 0.875, 25.4 * 0.5, '⅞″', '½″'],
|
|
|
|
[25.4 * 1.25, 25.4 * 0.625, '1 ¼″', '⅝″'],
|
|
|
|
[25.4 * 1.625, 25.4 * 0.875, '1 ⅝″', '⅞″'],
|
|
|
|
[25.4 * 2, 25.4 * 1, '2″', '1″'],
|
|
|
|
[25.4 * 2.375, 25.4 * 1.25, '2 ⅜″', '1 ¼″'],
|
|
|
|
[25.4 * 2.875, 25.4 * 1.5, '2 ⅞″', '1 ½″'],
|
|
|
|
[25.4 * 3.25, 25.4 * 1.625, '3 ¼″', '1 ⅝″'],
|
|
|
|
[25.4 * 3.625, 25.4 * 1.875, '3 ⅝″', '1 ⅞″'],
|
|
|
|
[25.4 * 4, 25.4 * 2, '4″', '2″'],
|
2023-09-05 20:38:03 +02:00
|
|
|
],
|
|
|
|
},
|
|
|
|
miniscale: [
|
|
|
|
[10, '1cm', 25.4 * 0.375, '⅜″'],
|
|
|
|
[13, '1.3cm', 25.4 * 0.5, '½″'],
|
|
|
|
[16, '1.6cm', 25.4 * 0.625, '⅝″'],
|
|
|
|
[19, '1.9cm', 25.4 * 0.75, '¾″'],
|
|
|
|
[22, '2.2cm', 25.4 * 0.875, '⅞″'],
|
|
|
|
[25, '2.5cm', 25.4 * 1, '1″'],
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This removes a given macro type
|
|
|
|
*/
|
|
|
|
const removeScaleAnnotation = function (id = false, { paths, points, store, part }, type) {
|
|
|
|
if (!id) id = type
|
2023-10-18 16:00:15 +02:00
|
|
|
return store.removeMacroNodes(id, type, part)
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The scalebox macro
|
|
|
|
*/
|
2023-09-07 10:29:19 +02:00
|
|
|
const scalebox = function (
|
|
|
|
config,
|
|
|
|
{ store, points, paths, scale, Point, Path, complete, log, part }
|
|
|
|
) {
|
2023-09-05 20:38:03 +02:00
|
|
|
/*
|
|
|
|
* Don't add a title 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,
|
|
|
|
id: 'scalebox',
|
|
|
|
...config,
|
|
|
|
classes: macroDefaults.classes,
|
|
|
|
}
|
|
|
|
if (config.classes) mc.classes = { ...mc.classes, ...config.classes }
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out what size to use
|
|
|
|
* We convert scale to a value between 0 and 9, inclusive.
|
|
|
|
* Then pick the right size from the sizes[units] array.
|
|
|
|
* Array holds width, height, displayWidth, displayHeight
|
|
|
|
*/
|
|
|
|
const scaleIndex = Math.round(10 * Math.max(0.1, Math.min(1, scale))) - 1
|
|
|
|
const [mw, mh, mdw, mdh] = sizes.scalebox.metric[scaleIndex]
|
|
|
|
const [iw, ih, idw, idh] = sizes.scalebox.imperial[scaleIndex]
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure mc.at is a Point instance
|
|
|
|
*/
|
|
|
|
if (!mc.at || typeof mc.at.attr !== 'function') {
|
|
|
|
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
2023-09-07 10:29:19 +02:00
|
|
|
mc.at = new Point(0, 0)
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the list of IDs
|
|
|
|
*/
|
2023-10-18 16:00:15 +02:00
|
|
|
const ids = store.generateMacroIds(
|
2023-09-05 20:38:03 +02:00
|
|
|
[
|
|
|
|
'metric',
|
|
|
|
'imperial',
|
|
|
|
'textLead',
|
|
|
|
'textMetric',
|
|
|
|
'textImperial',
|
|
|
|
'textTitle',
|
|
|
|
'textText',
|
|
|
|
'textLink',
|
|
|
|
],
|
2023-10-18 16:00:15 +02:00
|
|
|
mc.id
|
2023-09-05 20:38:03 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Box points (no need to add these to the part)
|
|
|
|
*/
|
|
|
|
const box = {
|
|
|
|
mtl: new Point(mc.at.x - mw / 2, mc.at.y - mh / 2),
|
|
|
|
mtr: new Point(mc.at.x + mw / 2, mc.at.y - mh / 2),
|
|
|
|
mbl: new Point(mc.at.x - mw / 2, mc.at.y + mh / 2),
|
|
|
|
mbr: new Point(mc.at.x + mw / 2, mc.at.y + mh / 2),
|
|
|
|
itl: new Point(mc.at.x - iw / 2, mc.at.y - ih / 2),
|
|
|
|
itr: new Point(mc.at.x + iw / 2, mc.at.y - ih / 2),
|
|
|
|
ibl: new Point(mc.at.x - iw / 2, mc.at.y + ih / 2),
|
|
|
|
ibr: new Point(mc.at.x + iw / 2, mc.at.y + ih / 2),
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Text points
|
|
|
|
*/
|
|
|
|
const text = {
|
|
|
|
lead: new Point(mc.at.x - 45 * scale, mc.at.y - 15 * scale),
|
|
|
|
metric: new Point(mc.at.x, mc.at.y + 20 * scale),
|
|
|
|
imperial: new Point(mc.at.x, mc.at.y + 24 * scale),
|
|
|
|
}
|
|
|
|
text.title = text.lead.shift(-90, 10 * scale)
|
|
|
|
text.text = text.title.shift(-90, 12 * scale)
|
|
|
|
text.link = text.text.shift(-90, 5 * scale)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle rotation if needed
|
|
|
|
*/
|
|
|
|
if (mc.rotate) {
|
|
|
|
mc.rotate = Number(mc.rotate)
|
|
|
|
for (const pid in box) box[pid] = box[pid].rotate(mc.rotate, mc.at)
|
|
|
|
for (const pid in text) {
|
2023-09-16 13:04:22 +02:00
|
|
|
text[pid] = text[pid].rotate(mc.rotate, mc.at)
|
|
|
|
text[pid].attr(
|
|
|
|
'data-text-transform',
|
|
|
|
`rotate(${mc.rotate * -1}, ${text[pid].x}, ${text[pid].y})`,
|
|
|
|
true
|
|
|
|
)
|
2023-03-11 19:01:40 +01:00
|
|
|
}
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Draw the imperial box
|
|
|
|
*/
|
|
|
|
paths[ids.imperial] = new Path()
|
2023-09-08 10:15:41 +02:00
|
|
|
.addClass(mc.classes.imperialBox)
|
2023-09-05 20:38:03 +02:00
|
|
|
.move(box.itl)
|
|
|
|
.line(box.ibl)
|
|
|
|
.line(box.ibr)
|
|
|
|
.line(box.itr)
|
|
|
|
.line(box.itl)
|
|
|
|
.close()
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Draw the metric box
|
|
|
|
*/
|
|
|
|
paths[ids.metric] = new Path()
|
2023-09-08 10:15:41 +02:00
|
|
|
.addClass(mc.classes.metricBox)
|
2023-09-05 20:38:03 +02:00
|
|
|
.move(box.mtl)
|
|
|
|
.line(box.mbl)
|
|
|
|
.line(box.mbr)
|
|
|
|
.line(box.mtr)
|
|
|
|
.line(box.mtl)
|
|
|
|
.close()
|
2023-03-04 01:36:34 +00:00
|
|
|
|
2023-09-05 20:38:03 +02:00
|
|
|
/*
|
|
|
|
* Add lead text to the part points
|
|
|
|
*/
|
2023-09-08 10:15:41 +02:00
|
|
|
points[ids.textLead] = text.lead.addText(mc.lead, mc.classes.lead)
|
2023-09-05 20:38:03 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out what title to use, and add the title text to the part points
|
|
|
|
*/
|
|
|
|
let title = mc.title
|
|
|
|
if (!title) {
|
2023-09-06 08:14:49 +02:00
|
|
|
title = store.data?.name || 'plugin-annotations:noName'
|
2023-09-05 20:38:03 +02:00
|
|
|
if (title.indexOf('@freesewing/') !== -1) title = title.replace('@freesewing/', '')
|
|
|
|
}
|
|
|
|
points[ids.textTitle] = text.title
|
|
|
|
.addText(title, mc.classes.title)
|
|
|
|
.attr('data-text', 'v' + (store.data?.version || 'No Version'))
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add text text to the part points
|
|
|
|
*/
|
2023-09-06 08:14:49 +02:00
|
|
|
points[ids.textText] = text.text.addText(mc.text, mc.classes.text)
|
2023-09-05 20:38:03 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Add link text to the part points
|
|
|
|
*/
|
2023-09-06 08:14:49 +02:00
|
|
|
points[ids.textLink] = text.link.addText(mc.link, mc.classes.link).attr('data-text-lineheight', 4)
|
2023-09-05 20:38:03 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Add metric instructions text to the part points
|
|
|
|
*/
|
|
|
|
points[ids.textMetric] = text.metric
|
2023-09-06 08:14:49 +02:00
|
|
|
.attr('data-text', 'plugin-annotations:theWhiteInsideOfThisBoxShouldMeasure')
|
2023-09-05 20:38:03 +02:00
|
|
|
.attr('data-text', mdw)
|
|
|
|
.attr('data-text', 'x')
|
|
|
|
.attr('data-text', mdh)
|
|
|
|
.attr('data-text-class', mc.classes.metric)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add imperial instructions text to the part points
|
|
|
|
*/
|
|
|
|
points[ids.textImperial] = text.imperial
|
2023-09-06 08:14:49 +02:00
|
|
|
.attr('data-text', 'plugin-annotations:theBlackOutsideOfThisBoxShouldMeasure')
|
2023-09-05 20:38:03 +02:00
|
|
|
.attr('data-text', idw)
|
|
|
|
.attr('data-text', 'x')
|
|
|
|
.attr('data-text', idh)
|
|
|
|
.attr('data-text-class', mc.classes.imperial)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store all IDs in the store so we can remove this macro with rmscaleboc
|
|
|
|
*/
|
2023-10-18 16:00:15 +02:00
|
|
|
store.storeMacroIds(mc.id, {
|
2023-09-05 20:38:03 +02:00
|
|
|
points: {
|
|
|
|
textLead: ids.textLead,
|
|
|
|
textMetric: ids.textMetric,
|
|
|
|
textImperial: ids.textImperial,
|
|
|
|
textTitle: ids.textTitle,
|
|
|
|
textText: ids.textText,
|
|
|
|
textLink: ids.textLink,
|
|
|
|
},
|
|
|
|
paths: {
|
|
|
|
metric: ids.metric,
|
|
|
|
imperial: ids.imperial,
|
|
|
|
},
|
|
|
|
})
|
2023-09-28 13:26:32 +02:00
|
|
|
|
2023-10-18 16:00:15 +02:00
|
|
|
/*
|
|
|
|
* Returning ids is a best practice for FreeSewing macros
|
|
|
|
*/
|
|
|
|
return store.getMacroIds(mc.id)
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The miniscale macro
|
|
|
|
*/
|
2023-09-07 10:29:19 +02:00
|
|
|
const miniscale = function (
|
|
|
|
config,
|
|
|
|
{ points, paths, scale, Point, Path, part, complete, log, store }
|
|
|
|
) {
|
2023-09-05 20:38:03 +02:00
|
|
|
/*
|
|
|
|
* Don't add a title 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,
|
|
|
|
id: 'miniscale',
|
|
|
|
...config,
|
|
|
|
classes: macroDefaults.classes,
|
|
|
|
}
|
|
|
|
if (config.classes) mc.classes = { ...mc.classes, ...config.classes }
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out what size to use
|
|
|
|
* We convert scale to a value between 0 and 5, inclusive.
|
|
|
|
* Then pick the right size from the sizes.miniscale array.
|
|
|
|
* Array holds metricSize, metricDisplaySize, imperialSize, imperialDisplaySize
|
|
|
|
*/
|
|
|
|
const scaleIndex = Math.ceil(6 * Math.max(0.1, Math.min(1, scale))) - 1
|
|
|
|
const [ms, mds, is, imds] = sizes.miniscale[scaleIndex]
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure mc.at is a Point instance
|
|
|
|
*/
|
|
|
|
if (!mc.at || typeof mc.at.attr !== 'function') {
|
|
|
|
log.warn(`Scalebox macro called without a valid at point. Using (0,0) for at.`)
|
2023-09-07 10:29:19 +02:00
|
|
|
mc.at = new Point(0, 0)
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the list of IDs
|
|
|
|
*/
|
2023-10-18 16:00:15 +02:00
|
|
|
const ids = store.generateMacroIds(['metric', 'imperial', 'textMetric', 'textImperial'], mc.id)
|
2023-09-05 20:38:03 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Box points (no need to add these to the part)
|
|
|
|
*/
|
|
|
|
const box = {
|
|
|
|
mtl: new Point(mc.at.x - ms / 2, mc.at.y - ms / 2),
|
|
|
|
mtr: new Point(mc.at.x + ms / 2, mc.at.y - ms / 2),
|
|
|
|
mbl: new Point(mc.at.x - ms / 2, mc.at.y + ms / 2),
|
|
|
|
mbr: new Point(mc.at.x + ms / 2, mc.at.y + ms / 2),
|
|
|
|
itl: new Point(mc.at.x - is / 2, mc.at.y - is / 2),
|
|
|
|
itr: new Point(mc.at.x + is / 2, mc.at.y - is / 2),
|
|
|
|
ibl: new Point(mc.at.x - is / 2, mc.at.y + is / 2),
|
|
|
|
ibr: new Point(mc.at.x + is / 2, mc.at.y + is / 2),
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Text points
|
|
|
|
*/
|
|
|
|
const text = {
|
|
|
|
metric: new Point(mc.at.x, mc.at.y - 2 * scale),
|
|
|
|
imperial: new Point(mc.at.x, mc.at.y + 8 * scale),
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle rotation if needed
|
|
|
|
*/
|
|
|
|
if (mc.rotate) {
|
|
|
|
mc.rotate = Number(mc.rotate)
|
|
|
|
for (const pid in box) box[pid] = box[pid].rotate(mc.rotate, mc.at)
|
|
|
|
for (const pid in text) {
|
|
|
|
text[pid] = text[pid]
|
|
|
|
.rotate(mc.rotate, mc.at)
|
|
|
|
.attr(
|
2023-03-11 19:01:40 +01:00
|
|
|
'data-text-transform',
|
2023-09-05 20:38:03 +02:00
|
|
|
`rotate(${mc.rotate * -1}, ${text[pid].x}, ${text[pid].y})`,
|
|
|
|
true
|
2023-03-11 19:01:40 +01:00
|
|
|
)
|
|
|
|
}
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Draw the imperial box
|
|
|
|
*/
|
|
|
|
paths[ids.imperial] = new Path()
|
|
|
|
.attr('class', 'scalebox imperial fill-current')
|
|
|
|
.move(box.itl)
|
|
|
|
.line(box.ibl)
|
|
|
|
.line(box.ibr)
|
|
|
|
.line(box.itr)
|
|
|
|
.line(box.itl)
|
|
|
|
.close()
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Draw the metric box
|
|
|
|
*/
|
|
|
|
paths[ids.metric] = new Path()
|
|
|
|
.attr('class', 'scalebox metric fill-bg')
|
|
|
|
.move(box.mtl)
|
|
|
|
.line(box.mbl)
|
|
|
|
.line(box.mbr)
|
|
|
|
.line(box.mtr)
|
|
|
|
.line(box.mtl)
|
|
|
|
.close()
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add metric text to the part points
|
|
|
|
*/
|
|
|
|
points[ids.textMetric] = text.metric.addText(`${mds} x ${mds}`, mc.classes.metric)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add imperial text to the part points
|
|
|
|
*/
|
|
|
|
points[ids.textImperial] = text.imperial.addText(`${imds} x ${imds}`, mc.classes.imperial)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store all IDs in the store so we can remove this macro with rmscaleboc
|
|
|
|
*/
|
2023-10-18 16:00:15 +02:00
|
|
|
store.storeMacroIds(mc.id, {
|
2023-09-05 20:38:03 +02:00
|
|
|
points: {
|
|
|
|
textMetric: ids.textMetric,
|
|
|
|
textImperial: ids.textImperial,
|
|
|
|
},
|
|
|
|
paths: {
|
|
|
|
metric: ids.metric,
|
|
|
|
imperial: ids.imperial,
|
|
|
|
},
|
|
|
|
})
|
2023-10-18 16:00:15 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Returning ids is a best practice for FreeSewing macros
|
|
|
|
*/
|
|
|
|
return store.getMacroIds(mc.id)
|
2023-09-05 20:38:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Export macros
|
|
|
|
export const scaleboxMacros = {
|
|
|
|
scalebox,
|
|
|
|
miniscale,
|
|
|
|
rmscalebox: (id, props) => removeScaleAnnotation(id, props, 'scalebox'),
|
|
|
|
rmminiscale: (id, props) => removeScaleAnnotation(id, props, 'miniscale'),
|
2023-03-03 22:02:57 +00:00
|
|
|
}
|