Merge branch 'develop' into eriese-hiding
This commit is contained in:
commit
dcb9833a9a
64 changed files with 1950 additions and 1497 deletions
|
@ -99,9 +99,9 @@ jaeger:
|
|||
'@freesewing/plugin-mirror': *freesewing
|
||||
new-design:
|
||||
_:
|
||||
'axios': '1.3.3'
|
||||
'axios': '1.3.4'
|
||||
'chalk': '5.0.1'
|
||||
'execa': '6.1.0'
|
||||
'execa': '7.0.0'
|
||||
'mustache': '4.2.0'
|
||||
'ora': '6.1.0'
|
||||
'prompts': '2.4.2'
|
||||
|
@ -222,14 +222,14 @@ backend:
|
|||
'pino': '8.11.0'
|
||||
'qrcode': '1.5.1'
|
||||
'swagger-ui-dist': '4.17.1'
|
||||
'swagger-ui-express': '4.6.0'
|
||||
'swagger-ui-express': '4.6.2'
|
||||
dev:
|
||||
'chai': *chai
|
||||
'chai-http': '4.3.0'
|
||||
'esbuild': '0.17.10'
|
||||
'esbuild': '0.17.11'
|
||||
'mocha': *mocha
|
||||
'mocha-steps': '1.3.0'
|
||||
'nodemon': '2.0.20'
|
||||
'nodemon': '2.0.21'
|
||||
'prisma': *prisma
|
||||
|
||||
dev:
|
||||
|
@ -238,7 +238,7 @@ dev:
|
|||
'@mdx-js/mdx': *mdx
|
||||
'@mdx-js/react': *mdx
|
||||
'@mdx-js/runtime': &mdxRuntime '2.0.0-next.9'
|
||||
'@next/bundle-analyzer': &next '13.2.3'
|
||||
'@next/bundle-analyzer': &next '13.2.4'
|
||||
'@tailwindcss/typography': &tailwindTypography '0.5.9'
|
||||
'algoliasearch': '4.15.0'
|
||||
'daisyui': &daisyui '2.51.3'
|
||||
|
@ -361,7 +361,7 @@ sanity:
|
|||
'eslint': *eslint
|
||||
'prettier': '2.8.4'
|
||||
'typescript': '4.9.5'
|
||||
'@sanity/cli': '3.2.6'
|
||||
'@sanity/cli': '3.6.0'
|
||||
|
||||
shared:
|
||||
_:
|
||||
|
@ -380,6 +380,7 @@ shared:
|
|||
'lodash.clonedeep': '4.5.0'
|
||||
'lodash.orderby': *_orderby
|
||||
'lodash.unset': *_unset
|
||||
'lodash.get': *_get
|
||||
'mdast-util-toc': '6.1.1'
|
||||
'pdfkit': '0.13.0'
|
||||
'postcss-for': '2.1.1'
|
||||
|
@ -388,7 +389,7 @@ shared:
|
|||
'react-markdown': *reactMarkdown
|
||||
'react-sizeme': '3.0.2'
|
||||
'react-timeago': *reactTimeago
|
||||
'react-zoom-pan-pinch': '2.6.1'
|
||||
'react-zoom-pan-pinch': '3.0.2'
|
||||
'rehype-autolink-headings': *rehypeAutolinkHeadings
|
||||
'rehype-highlight': *rehypeHighlight
|
||||
'remark-smartypants': '2.0.0'
|
||||
|
|
|
@ -65,6 +65,7 @@ export const back = {
|
|||
measurements,
|
||||
log,
|
||||
part,
|
||||
addCut,
|
||||
}) => {
|
||||
// Get to work
|
||||
points.cbNeck = new Point(0, measurements.neck * options.backNeckCutout)
|
||||
|
@ -269,6 +270,8 @@ export const back = {
|
|||
on: ['armholePitch', 'bustCenter'],
|
||||
})
|
||||
|
||||
addCut()
|
||||
|
||||
if (sa) paths.sa = paths.saBase.offset(sa).attr('class', 'fabric sa')
|
||||
|
||||
if (paperless) {
|
||||
|
|
|
@ -18,6 +18,7 @@ function draftCarlitaFront({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
/**
|
||||
* we're adding half of the proportionate amount of chest east for the bust span
|
||||
|
@ -348,6 +349,10 @@ function draftCarlitaFront({
|
|||
.attr('class', 'fabric help')
|
||||
|
||||
if (complete) {
|
||||
if (typeof addCut === 'function') {
|
||||
addCut()
|
||||
addCut({ material: 'lining' })
|
||||
}
|
||||
snippets.button1Left = new Snippet('button', points.button1Left).attr('data-scale', 2)
|
||||
snippets.button1Right = new Snippet('button', points.button1Right).attr('data-scale', 2)
|
||||
snippets.button2Left = new Snippet('button', points.button2Left).attr('data-scale', 2)
|
||||
|
|
|
@ -13,6 +13,7 @@ function draftCarlitaSide({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
// Give points their original names
|
||||
for (let i of store.get('side')) points[i] = points[i + 'Rot2'].clone()
|
||||
|
@ -38,6 +39,10 @@ function draftCarlitaSide({
|
|||
paths.seam = paths.saBase.clone().line(points.hem).close().attr('class', 'fabric')
|
||||
|
||||
if (complete) {
|
||||
if (typeof addCut === 'function') {
|
||||
addCut()
|
||||
addCut({ material: 'lining' })
|
||||
}
|
||||
points.title = points.bustPoint.shiftFractionTowards(points.waist, 0.5)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { back as bentBack } from '@freesewing/bent'
|
||||
import { calculateRatios } from './shared.mjs'
|
||||
import { hidePresets } from '@freesewing/core'
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
|
||||
function draftCarltonBack({
|
||||
paperless,
|
||||
|
@ -17,6 +18,7 @@ function draftCarltonBack({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
calculateRatios(part)
|
||||
// Belt width
|
||||
|
@ -96,6 +98,9 @@ function draftCarltonBack({
|
|||
.line(points.bpStart)
|
||||
.attr('class', 'dashed')
|
||||
|
||||
addCut()
|
||||
addCut({ cut: 2, material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
macro('sprinkle', {
|
||||
snippet: 'bnotch',
|
||||
|
@ -239,5 +244,6 @@ export const back = {
|
|||
waistEase: { pct: 14, min: 8, max: 25, menu: 'fit' },
|
||||
seatEase: { pct: 14, min: 8, max: 25, menu: 'fit' },
|
||||
},
|
||||
plugins: [pluginCutlist],
|
||||
draft: draftCarltonBack,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ function draftCarltonBelt({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
let length = 1.6 * (store.get('cbToDart') + store.get('dartToSide'))
|
||||
let width = store.get('beltWidth')
|
||||
|
@ -49,6 +50,7 @@ function draftCarltonBelt({
|
|||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
addCut({ cut: 4 })
|
||||
if (complete) {
|
||||
snippets.button = new Snippet('button', points.button).attr('data-scale', 2)
|
||||
points.title = new Point(points.bottomRight.x / 2, points.bottomRight.y / 2)
|
||||
|
|
|
@ -12,6 +12,7 @@ function draftCarltonChestPocketBag({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(
|
||||
|
@ -43,6 +44,8 @@ function draftCarltonChestPocketBag({
|
|||
.line(points.startRight)
|
||||
.attr('class', 'lining dashed')
|
||||
|
||||
addCut({ material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -11,6 +11,7 @@ function draftCarltonChestPocketWelt({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(store.get('chestPocketWidth') * 2, store.get('chestPocketHeight'))
|
||||
|
@ -30,6 +31,9 @@ function draftCarltonChestPocketWelt({
|
|||
|
||||
paths.fold = new Path().move(points.topMid).line(points.bottomMid).attr('class', 'dashed')
|
||||
|
||||
addCut()
|
||||
addCut({ material: 'lmhCanvas' })
|
||||
|
||||
if (complete) {
|
||||
points.title = new Point(points.bottomRight.x / 4, points.bottomRight.y / 2)
|
||||
macro('title', {
|
||||
|
|
|
@ -16,6 +16,7 @@ function draftCarltonCollar({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
// We're going to slash and spread this collar. Slashing first:
|
||||
// Divide top in 5 parts
|
||||
|
@ -178,13 +179,6 @@ function draftCarltonCollar({
|
|||
paths.seam = paths.saBase.clone().line(points.standTop).close().attr('class', 'fabric')
|
||||
|
||||
if (complete) {
|
||||
points.title = points.standTopCp.clone()
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 8,
|
||||
title: 'collar',
|
||||
})
|
||||
|
||||
// Remove grainline from collarstand part
|
||||
delete paths.grainline
|
||||
macro('cutonfold', {
|
||||
|
@ -193,6 +187,16 @@ function draftCarltonCollar({
|
|||
grainline: true,
|
||||
})
|
||||
|
||||
addCut({ cut: 1 })
|
||||
addCut({ cut: 1, bias: true })
|
||||
addCut({ cut: 2, material: 'lining', bias: true, ignoreOnFold: true })
|
||||
|
||||
points.title = points.standTopCp.clone()
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 8,
|
||||
title: 'collar',
|
||||
})
|
||||
if (sa) {
|
||||
paths.sa = paths.saBase.offset(sa)
|
||||
paths.sa = paths.sa
|
||||
|
|
|
@ -14,6 +14,7 @@ function draftCarltonCollarStand({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
let height = measurements.chest * options.collarHeight
|
||||
let length = store.get('frontCollarLength') + store.get('backCollarLength')
|
||||
|
@ -45,6 +46,9 @@ function draftCarltonCollarStand({
|
|||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
addCut()
|
||||
addCut({ cut: 1, material: 'lmhCanvas' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.bottomLeftCp.clone()
|
||||
macro('title', {
|
||||
|
|
|
@ -12,6 +12,7 @@ function draftCarltonCuffFacing({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(
|
||||
|
@ -46,6 +47,9 @@ function draftCarltonCuffFacing({
|
|||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
addCut()
|
||||
addCut({ cut: 2, material: 'lmhCanvas' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { front as bentFront } from '@freesewing/bent'
|
||||
import { calculateRatios } from './shared.mjs'
|
||||
import { hidePresets } from '@freesewing/core'
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
|
||||
function draftCarltonFront({
|
||||
paperless,
|
||||
|
@ -18,6 +19,7 @@ function draftCarltonFront({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
calculateRatios(part)
|
||||
|
||||
|
@ -299,6 +301,8 @@ function draftCarltonFront({
|
|||
.close()
|
||||
.attr('class', 'fabric help')
|
||||
|
||||
addCut()
|
||||
|
||||
if (complete) {
|
||||
snippets.button1Left = new Snippet('button', points.button1Left).attr('data-scale', 2)
|
||||
snippets.button1Right = new Snippet('button', points.button1Right).attr('data-scale', 2)
|
||||
|
@ -502,5 +506,6 @@ export const front = {
|
|||
seatEase: { pct: 14, min: 8, max: 25, menu: 'fit' },
|
||||
innerPocketWeltHeight: { pct: 3.5, min: 2.5, max: 5, menu: 'pockets' },
|
||||
},
|
||||
plugins: [pluginCutlist],
|
||||
draft: draftCarltonFront,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ function draftCarltonInnerPocketBag({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(
|
||||
|
@ -44,6 +45,8 @@ function draftCarltonInnerPocketBag({
|
|||
.line(points.startRight)
|
||||
.attr('class', 'lining dashed')
|
||||
|
||||
addCut({ material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -11,6 +11,7 @@ function draftCarltonInnerPocketTab({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.topRight = new Point(store.get('innerPocketWidth') * 1.2, 0)
|
||||
|
@ -30,6 +31,8 @@ function draftCarltonInnerPocketTab({
|
|||
|
||||
paths.hint = new Path().move(points.top).line(points.bottom).attr('class', 'lining dashed')
|
||||
|
||||
addCut({ cut: 1, material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.top.shiftFractionTowards(points.bottom, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -11,6 +11,7 @@ function draftCarltonInnerPocketWelt({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(
|
||||
|
@ -49,6 +50,9 @@ function draftCarltonInnerPocketWelt({
|
|||
.close()
|
||||
.attr('class', 'lashed')
|
||||
|
||||
addCut()
|
||||
addCut({ material: 'lmhCanvas' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -12,6 +12,7 @@ function draftCarltonPocket({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(store.get('pocketWidth'), store.get('pocketHeight'))
|
||||
|
@ -53,6 +54,8 @@ function draftCarltonPocket({
|
|||
|
||||
paths.fold = new Path().move(points.topLeft).line(points.topRight).attr('class', 'fabric dashed')
|
||||
|
||||
addCut()
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -12,6 +12,7 @@ function draftCarltonPocketFlap({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.bottomRight = new Point(store.get('pocketWidth'), store.get('pocketFlapHeight'))
|
||||
|
@ -45,6 +46,9 @@ function draftCarltonPocketFlap({
|
|||
|
||||
paths.seam = paths.seam.line(points.topRight).line(points.topLeft).close().attr('class', 'fabric')
|
||||
|
||||
addCut({ cut: 4 })
|
||||
addCut({ material: 'lmhCanvas' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -12,6 +12,7 @@ function draftCarltonPocketLining({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
points.topLeft = points.bottomLeft.shiftFractionTowards(points.topLeft, 0.75)
|
||||
points.topRight = new Point(points.bottomRight.x, points.topLeft.y)
|
||||
|
@ -45,6 +46,8 @@ function draftCarltonPocketLining({
|
|||
|
||||
delete paths.fold
|
||||
|
||||
addCut({ material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -13,6 +13,7 @@ function draftCarltonTail({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
let length = store.get('waistToHem') - store.get('beltWidth') / 2
|
||||
|
||||
|
@ -68,6 +69,9 @@ function draftCarltonTail({
|
|||
.line(points.fold5Bottom)
|
||||
.attr('class', 'fabric dashed')
|
||||
|
||||
addCut()
|
||||
addCut({ material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
points.title = points.fold4Top.shiftFractionTowards(points.waistBottom, 0.5)
|
||||
macro('title', {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { topSleeve as bentTopSleeve } from '@freesewing/bent'
|
||||
import { front as bentFront } from '@freesewing/bent'
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
|
||||
function draftCarltonTopSleeve({
|
||||
paperless,
|
||||
|
@ -15,6 +16,7 @@ function draftCarltonTopSleeve({
|
|||
Snippet,
|
||||
snippets,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
// Add cuff
|
||||
let length = measurements.shoulderToWrist * options.cuffLength
|
||||
|
@ -53,6 +55,9 @@ function draftCarltonTopSleeve({
|
|||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
addCut()
|
||||
addCut({ material: 'lining' })
|
||||
|
||||
if (complete) {
|
||||
macro('grainline', {
|
||||
from: points.boxBottom,
|
||||
|
@ -175,5 +180,6 @@ export const topSleeve = {
|
|||
sleevecapHeight: { pct: 45, min: 40, max: 60, menu: 'advanced' },
|
||||
sleevecapEase: { pct: 1, min: 0, max: 10, menu: 'advanced' },
|
||||
},
|
||||
plugins: [pluginCutlist],
|
||||
draft: draftCarltonTopSleeve,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { underSleeve as bentUnderSleeve } from '@freesewing/bent'
|
||||
import { front as bentFront } from '@freesewing/bent'
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
|
||||
function draftCarltonUnderSleeve({
|
||||
paperless,
|
||||
|
@ -14,6 +15,7 @@ function draftCarltonUnderSleeve({
|
|||
paths,
|
||||
Path,
|
||||
part,
|
||||
addCut,
|
||||
}) {
|
||||
// Add cuff
|
||||
let length = measurements.shoulderToWrist * options.cuffLength
|
||||
|
@ -49,6 +51,8 @@ function draftCarltonUnderSleeve({
|
|||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
addCut()
|
||||
addCut({ material: 'lining' })
|
||||
if (complete) {
|
||||
macro('grainline', {
|
||||
from: points.boxBottom,
|
||||
|
@ -153,5 +157,6 @@ export const underSleeve = {
|
|||
sleevecapHeight: { pct: 45, min: 40, max: 60, menu: 'advanced' },
|
||||
sleevecapEase: { pct: 1, min: 0, max: 10, menu: 'advanced' },
|
||||
},
|
||||
plugins: [pluginCutlist],
|
||||
draft: draftCarltonUnderSleeve,
|
||||
}
|
||||
|
|
150
markdown/dev/howtos/design/cutlist/en.md
Normal file
150
markdown/dev/howtos/design/cutlist/en.md
Normal file
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
title: "Include Cutting Instructions"
|
||||
---
|
||||
|
||||
To include cutting instructions with your part, use the [cutlist plugin](/reference/plugins/cutlist) to add the [`addCut` method](/reference/plugins/cutlist#addcut) to your part's [`draft` method](/reference/api/part/draft)
|
||||
|
||||
<Tip>When you use the cutlist plugin, the [grainline plugin](/reference/plugins/grainline) and the [cut on fold plugin](/reference/plugins/cutonfold) will automatically add grain and fold information to the cutting instructions </Tip>
|
||||
|
||||
<Tip>These cutting instructions get used by the [title macro](/reference/macros/title), so be sure to add them before adding your part's title. </Tip>
|
||||
|
||||
<Note>
|
||||
<details>
|
||||
<summary>addCut() Parameters</summary>
|
||||
|
||||
Pass an object to the `addCut` method with any of the following keys; any you don't provide will be filled with the defaults:
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| :-- | :--- | :------ | :---------- |
|
||||
| cut | Number\|false | 2 | the number of pieces to cut from the specified material. Pass `false` to clear all cutting instructions for the material |
|
||||
| material | String | 'fabric' | the translation key of the material to cut from |
|
||||
| identical | Boolean | false | should even numbers of pieces be cut in the same direction? false for mirrored |
|
||||
| bias | Boolean | false | should the pieces in these cutting instructions be cut on the bias |
|
||||
| ignoreOnFold | Boolean | false | should these cutting instructions ignore any cutOnFold information set by the part |
|
||||
|
||||
|
||||
You can use any `string` you want for your material, but here are some standard ones we have translation for
|
||||
|
||||
| Key | Translation |
|
||||
|:--|:--|
|
||||
| fabric | Main Fabric |
|
||||
| lining | Lining |
|
||||
| canvas | Canvas |
|
||||
| lmhCanavas | Light to Medium Hair Canvas |
|
||||
| heavyCanvas | Heavyweight Hair Canvas |
|
||||
| interfacing | Interfacing |
|
||||
| plastic | Plastic |
|
||||
| ribbing | Ribbing |
|
||||
|
||||
</details>
|
||||
</Note>
|
||||
|
||||
|
||||
## Basic Usage
|
||||
For simple cutting instructions, you can rely on the default method parameters
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, addCut}) => {
|
||||
// add instructions to cut two mirrored from main fabric
|
||||
addCut()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Intermediate Usage
|
||||
For many designs, you'll want more than just "Cut 2 mirrored from Main Fabric"
|
||||
|
||||
### Specifying materials, number of pieces, orientation
|
||||
|
||||
You can override the default values to specify different materials, number of pieces to cut, and whether they should be mirrored or identical
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, addCut}) => {
|
||||
// add instructions to cut three identical from lining
|
||||
addCut({cut: 3, material: 'lining', identical: true})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Instructions for multiple materials
|
||||
You can add as many sets of instructions as you need
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, addCut}) => {
|
||||
// add instructions to cut four mirrored from main fabric
|
||||
addCut({cut: 4})
|
||||
// add instructions to cut three identical from lining
|
||||
addCut({cut: 3, material: 'lining', identical: true})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced usage
|
||||
|
||||
### Cut some on the fold, some not
|
||||
Sometimes you want some pieces cut on the fold and others cut as halves to seam together.
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
import {pluginCutonfold} from '@freesewing/plugin-cutonfold'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist, pluginCutonfold],
|
||||
draft: ({part, points, Point, macro, addCut}) => {
|
||||
// set the cut on fold line
|
||||
points.p1 = new Point(0, 0)
|
||||
points.p2 = new Point(0, 10)
|
||||
|
||||
// pieces should be cut on the fold
|
||||
macro('cutonfold', {from: points.p1, to: points.p2})
|
||||
|
||||
// cut two on the fold
|
||||
addCut()
|
||||
// cut two, not on the fold
|
||||
addCut({cut: 2, ignoreOnFold: true})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Cut some on the grain, some on the bias
|
||||
You set the grainline on a piece, but you also need some to be cut on the bias
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
import {pluginGrainline} from '@freesewing/plugin-grainline'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist, pluginGrainline],
|
||||
draft: ({part, points, Point, macro, addCut}) => {
|
||||
// set the cut on fold line
|
||||
points.p1 = new Point(0, 0)
|
||||
points.p2 = new Point(0, 10)
|
||||
|
||||
// the grain runs from p1 to p2
|
||||
macro('grainline', {from: points.p1, to: points.p2})
|
||||
|
||||
// cut two mirrored on the grain
|
||||
addCut()
|
||||
// cut two mirrored on the bias
|
||||
addCut({cut: 2, bias: true})
|
||||
}
|
||||
}
|
||||
```
|
|
@ -46,4 +46,4 @@ access the following properties:
|
|||
|| **_Return value_** |
|
||||
| `part` | Your draft method **must** return this |
|
||||
|
||||
|
||||
<Note> Some plugins, such as the [cutlist plugin](/reference/plugins/cutlist) add additional methods to this object that can be accessed through the same destructuring </Note>
|
||||
|
|
|
@ -11,6 +11,7 @@ It is provided by the [title plugin](/reference/plugins/title).
|
|||
macro('title', {
|
||||
Boolean append,
|
||||
Point at,
|
||||
Boolean cutlist
|
||||
String nr,
|
||||
String prefix,
|
||||
Number rotation,
|
||||
|
@ -50,11 +51,12 @@ macro('title', {
|
|||
|
||||
| Property | Default | Type | Description |
|
||||
| ----------:| :-----: | ------------------- | ----------- |
|
||||
| `append` | `false` | Boolean | Set this to `true` to append the `nr` to any text already set in Point `at`'s attributes, rather than overwrite it |
|
||||
| `at` | | [Point](/reference/api/point) | The point at which to insert the title |
|
||||
| `cutlist` | `true` | Boolean | Whether to include cutting instructions |
|
||||
| `nr` | | String | The number of the pattern part |
|
||||
| `title` | | String | The name of the pattern part. If title is not set or is an empty string, this won't be rendered, and the version will go beneath the nr.|
|
||||
| `prefix` | | String | A prefix to add to the created points. This allow for more than 1 title per part, as long as you give them a different prefix.|
|
||||
| `append` | `false` | Boolean | Set this to `true` to append the `nr` to any text already set in Point `at`'s attributes, rather than overwrite it |
|
||||
| `rotation` | 0 | Number | An optional rotation in degrees |
|
||||
| `scale` | 1 | Number | An optional scaling factor |
|
||||
|
||||
|
@ -64,6 +66,7 @@ macro('title', {
|
|||
|-------------------|-------------|
|
||||
| `points._${prefix}_titleNr` | Point anchoring the part number text |
|
||||
| `points._${prefix}_titleName` | Point anchoring the part name text |
|
||||
| `points._${prefix}_titleCut_${material}_${i} | Points anchoring the cutting instructions, by material key and instruction index |
|
||||
| `points._${prefix}_titlePattern` | Point anchoring the pattern name text |
|
||||
| `points._${prefix}_titleFor` | Point anchoring the name of the person for whom the pattern was made, if that information exists |
|
||||
| `points._${prefix}_exportDate` | Point anchoring the pattern export date |
|
||||
|
|
188
markdown/dev/reference/plugins/cutlist/en.md
Normal file
188
markdown/dev/reference/plugins/cutlist/en.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
---
|
||||
title: plugin-cutlist
|
||||
---
|
||||
|
||||
Published as [@freesewing/plugin-cutlist][1], this plugin provides additional methods to the [part draft function](/reference/api/part/draft) which allow you to configure cutting instructions for your parts.
|
||||
|
||||
<Tip> For an in-depth look at how to add cutting instructions to your part, see our [cutlist how-to](/howtos/design/cutlist) </Tip>
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @freesewing/plugin-cutonfold
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Either [add it as a part plugin](/reference/api/part/config/plugins) in your
|
||||
design, or [add it to a pattern instance with
|
||||
Pattern.use()](/reference/api/pattern/use).
|
||||
|
||||
To import the plugin for use:
|
||||
```js
|
||||
import { cutlistPlugin } from '@freesewing/plugin-cutlist'
|
||||
// or
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
The cutlist plugin adds the following methods to the part draft method parameter
|
||||
|
||||
### addCut
|
||||
|
||||
The `addCut()` method will add a set of cutting instructions for the part
|
||||
|
||||
#### Signature
|
||||
```js
|
||||
addCut(Object so)
|
||||
````
|
||||
|
||||
Pass an object to the `addCut` method with any of the following keys; any you don't provide will be filled with the defaults:
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| :-- | :--- | :------ | :---------- |
|
||||
| cut | Number\|false | 2 | the number of pieces to cut from the specified material. Pass `false` to clear all cutting instructions for the material |
|
||||
| material | String | 'fabric' | the translation key of the material to cut from |
|
||||
| identical | Boolean | false | should even numbers of pieces be cut in the same direction? false for mirrored |
|
||||
| bias | Boolean | false | should the pieces in these cutting instructions be cut on the bias |
|
||||
| ignoreOnFold | Boolean | false | should these cutting instructions ignore any cutOnFold information set by the part |
|
||||
|
||||
<Note>
|
||||
<details>
|
||||
<summary> You can use any `string` you want for your material, but here are some standard ones we have translation for </summary>
|
||||
<div>
|
||||
|
||||
| Key | Translation |
|
||||
|:--|:--|
|
||||
| fabric | Main Fabric |
|
||||
| lining | Lining |
|
||||
| canvas | Canvas |
|
||||
| lmhCanavas | Light to Medium Hair Canvas |
|
||||
| heavyCanvas | Heavyweight Hair Canvas |
|
||||
| interfacing | Interfacing |
|
||||
| plastic | Plastic |
|
||||
| ribbing | Ribbing |
|
||||
|
||||
</div>
|
||||
</details>
|
||||
</Note>
|
||||
|
||||
#### Example
|
||||
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, addCut}) => {
|
||||
// add instructions to cut two from main fabric
|
||||
addCut()
|
||||
// add instructions to cut four on the biad from lining
|
||||
addCut({cut: 4, material: 'lining', bias: true, })
|
||||
return part
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also add multiple sets of cutting instructions for the same material
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, addCut}) => {
|
||||
// add instructions to 1 from lining
|
||||
addCut({cut: 1, material: 'lining'})
|
||||
// add instructions to cut 1 on the bias from lining
|
||||
addCut({cut: 1, material: 'lining', bias: true, })
|
||||
return part
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### removeCut
|
||||
|
||||
The `removeCut()` method will remove cutting instructions from the part
|
||||
|
||||
#### Signature
|
||||
|
||||
```js
|
||||
removeCut(String material)
|
||||
```
|
||||
|
||||
#### Example
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, removeCut}) => {
|
||||
// remove all cutting instructions for all materials
|
||||
removeCut()
|
||||
|
||||
// remove cutting instructions for just one material
|
||||
removeCut('fabric')
|
||||
return part
|
||||
}
|
||||
}
|
||||
```
|
||||
### setGrain
|
||||
|
||||
The `setGrain()` method will record the angle of the grainline annotation. This method is called internally by [`plugin-grainline`](/reference/plugins/grainline) to store information for cutting layout tools. You shouldn't have to call it, but it's there if you need it.
|
||||
|
||||
#### Signature
|
||||
|
||||
```js
|
||||
setGrain(Number grainAngle)
|
||||
```
|
||||
|
||||
#### Example
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, setGrain}) => {
|
||||
// set the grainline angle
|
||||
setGrain(0)
|
||||
return part
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### setCutOnFold
|
||||
The `setCutOnFold()` method will record the points that make up the cut on fold line. This method is called internally by [`plugin-cutonfold`](/reference/plugins/cutonfold) to store information for cutting layout tools. You shouldn't have to call it, but it's there if you need it.
|
||||
|
||||
#### Signature
|
||||
|
||||
```js
|
||||
setCutOnFold(Point p1, Point p2)
|
||||
```
|
||||
|
||||
#### Example
|
||||
```js
|
||||
import {pluginCutlist} from '@freesewing/plugin-cutlist'
|
||||
|
||||
const part = {
|
||||
name: 'example.front',
|
||||
plugins: [pluginCutlist],
|
||||
draft: ({part, points, Point, setCutOnFold}) => {
|
||||
// set the cut on fold line
|
||||
points.p1 = new Point(0, 0)
|
||||
points.p2 = new Point(0, 10)
|
||||
setCutOnFold(points.p1, points.p2)
|
||||
return part
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
The cutlist plugin is part of our [plugin-bundle](/reference/plugins/bundle)
|
||||
|
||||
[1]: https://www.npmjs.com/package/@freesewing/plugin-cutlist
|
|
@ -163,6 +163,9 @@ Pattern.prototype.draftPartForSet = function (partName, set) {
|
|||
try {
|
||||
this.__runHooks('prePartDraft')
|
||||
const result = this.config.parts[partName].draft(this.parts[set][partName].shorthand())
|
||||
if (!this.__wants(partName, set)) {
|
||||
result.hide()
|
||||
}
|
||||
this.__runHooks('postPartDraft')
|
||||
if (typeof result === 'undefined') {
|
||||
this.setStores[set].log.error(
|
||||
|
|
|
@ -9,6 +9,7 @@ import cutonfold from './plugins/cutonfold.yaml'
|
|||
import grainline from './plugins/grainline.yaml'
|
||||
import scalebox from './plugins/scalebox.yaml'
|
||||
import title from './plugins/title.yaml'
|
||||
import cutlist from './plugins/cutlist/yaml'
|
||||
|
||||
const files = {
|
||||
brian,
|
||||
|
@ -22,6 +23,7 @@ const files = {
|
|||
grainline,
|
||||
scalebox,
|
||||
title,
|
||||
cutlist,
|
||||
}
|
||||
|
||||
const messages = {}
|
||||
|
|
13
packages/i18n/src/locales/en/plugin/plugins/cutlist.yaml
Normal file
13
packages/i18n/src/locales/en/plugin/plugins/cutlist.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
canvas: Canvas
|
||||
cut: Cut
|
||||
fabric: Main Fabric
|
||||
heavyCanvas: Heavy Canvas
|
||||
interfacing: Interfacing
|
||||
lining: Lining
|
||||
lmhCanvas: Light to Medium Hair Canvas
|
||||
mirrored: mirrored
|
||||
onFoldLower: on the fold
|
||||
onFoldAndBias: folded on the bias
|
||||
onBias: on the bias
|
||||
plastic: Plastic
|
||||
ribbing: Ribbing
|
|
@ -32,9 +32,9 @@
|
|||
},
|
||||
"peerDependencies": {},
|
||||
"dependencies": {
|
||||
"axios": "1.3.3",
|
||||
"axios": "1.3.4",
|
||||
"chalk": "5.0.1",
|
||||
"execa": "6.1.0",
|
||||
"execa": "7.0.0",
|
||||
"mustache": "4.2.0",
|
||||
"ora": "6.1.0",
|
||||
"prompts": "2.4.2",
|
||||
|
|
|
@ -12,6 +12,7 @@ import { roundPlugin } from '../../plugin-round/src/index.mjs'
|
|||
import { scaleboxPlugin } from '../../plugin-scalebox/src/index.mjs'
|
||||
import { sprinklePlugin } from '../../plugin-sprinkle/src/index.mjs'
|
||||
import { titlePlugin } from '../../plugin-title/src/index.mjs'
|
||||
import { pluginCutlist } from '../../plugin-cutlist/src/index.mjs'
|
||||
import { name, version } from '../data.mjs'
|
||||
|
||||
const bundledPlugins = [
|
||||
|
@ -29,38 +30,44 @@ const bundledPlugins = [
|
|||
scaleboxPlugin,
|
||||
sprinklePlugin,
|
||||
titlePlugin,
|
||||
pluginCutlist,
|
||||
]
|
||||
|
||||
function bundleHooks() {
|
||||
const hooks = {}
|
||||
for (const plugin of bundledPlugins) {
|
||||
for (const i in plugin.hooks) {
|
||||
if (typeof hooks[i] === 'undefined') hooks[i] = []
|
||||
const hook = plugin.hooks[i]
|
||||
if (typeof hook === 'function') hooks[i].push(hook)
|
||||
else if (typeof hook === 'object') {
|
||||
for (let method of hook) hooks[i].push(method)
|
||||
}
|
||||
const hooks = {}
|
||||
const macros = {}
|
||||
const store = []
|
||||
|
||||
function bundleHooks(plugin) {
|
||||
for (const i in plugin.hooks) {
|
||||
if (typeof hooks[i] === 'undefined') hooks[i] = []
|
||||
const hook = plugin.hooks[i]
|
||||
if (typeof hook === 'function') hooks[i].push(hook)
|
||||
else if (typeof hook === 'object') {
|
||||
for (let method of hook) hooks[i].push(method)
|
||||
}
|
||||
}
|
||||
|
||||
return hooks
|
||||
}
|
||||
|
||||
function bundleMacros() {
|
||||
const macros = {}
|
||||
for (const plugin of bundledPlugins) {
|
||||
for (const i in plugin.macros) macros[i] = plugin.macros[i]
|
||||
}
|
||||
function bundleMacros(plugin) {
|
||||
for (const i in plugin.macros) macros[i] = plugin.macros[i]
|
||||
}
|
||||
|
||||
return macros
|
||||
function bundleStore(plugin) {
|
||||
if (plugin.store) store.push(...plugin.store)
|
||||
}
|
||||
|
||||
for (const plugin of bundledPlugins) {
|
||||
bundleHooks(plugin)
|
||||
bundleMacros(plugin)
|
||||
bundleStore(plugin)
|
||||
}
|
||||
|
||||
export const plugin = {
|
||||
name,
|
||||
version,
|
||||
hooks: bundleHooks(),
|
||||
macros: bundleMacros(),
|
||||
hooks,
|
||||
macros,
|
||||
store,
|
||||
}
|
||||
|
||||
// More specifically named exports
|
||||
|
|
|
@ -16,8 +16,19 @@ export const plugin = {
|
|||
export const cutlistPlugin = plugin
|
||||
export const pluginCutlist = plugin
|
||||
|
||||
/** Method to add the cut info */
|
||||
function addCut(store, partName, cut = 2, material = 'fabric', identical = false) {
|
||||
/**
|
||||
* Add a set of cutting instructions for the part
|
||||
* @param {Store} store the Store
|
||||
* @param {string} partName the name of the part
|
||||
* @param {Object} so a set of cutting instructions for a material
|
||||
* @param {number} so.cut = 2 the number of pieces to cut from the specified fabric
|
||||
* @param {string} so.material = fabric the name of the material to cut from
|
||||
* @param {boolean} so.identical = false should even numbers of pieces be cut in the same direction or mirrored
|
||||
* @param {boolean} so.bias = false should the pieces in these cutting instructions be cut on the bias
|
||||
* @param {boolean} so.ignoreOnFold should these cutting instructions ignore any cutOnFold information set by the part
|
||||
*/
|
||||
function addCut(store, partName, so = {}) {
|
||||
const { cut = 2, material = 'fabric', identical = false, bias = false, ignoreOnFold = false } = so
|
||||
if (cut === false) {
|
||||
if (material === false) store.unset(['cutlist', partName, 'materials'])
|
||||
else store.unset(['cutlist', partName, 'materials', material])
|
||||
|
@ -32,15 +43,15 @@ function addCut(store, partName, cut = 2, material = 'fabric', identical = false
|
|||
return store
|
||||
}
|
||||
const path = ['cutlist', partName, 'materials', material]
|
||||
store.set([...path, 'cut'], cut)
|
||||
store.set([...path, 'identical'], identical)
|
||||
const existing = store.get(path) || []
|
||||
store.set(path, existing.concat({ cut, identical, bias, ignoreOnFold }))
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
/** Method to remove the cut info */
|
||||
function removeCut(store, partName, material = false) {
|
||||
return addCut(store, partName, false, material)
|
||||
return addCut(store, partName, { cut: false, material })
|
||||
}
|
||||
|
||||
/** Method to add the grain info */
|
||||
|
|
|
@ -38,15 +38,20 @@ describe('Cutlist Plugin Tests', () => {
|
|||
const Test = new Design({ parts: [part] })
|
||||
const pattern = new Test()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric.cut).to.equal(2)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric.identical).to.equal(false)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric).to.have.lengthOf(1)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric[0]).to.deep.equal({
|
||||
cut: 2,
|
||||
identical: false,
|
||||
bias: false,
|
||||
ignoreOnFold: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('Should handle addCut() with non-defaults', () => {
|
||||
const part = {
|
||||
name: 'example_part',
|
||||
draft: ({ addCut, part }) => {
|
||||
addCut(3, 'lining', true)
|
||||
addCut({ cut: 3, material: 'lining', identical: true })
|
||||
|
||||
return part
|
||||
},
|
||||
|
@ -55,8 +60,13 @@ describe('Cutlist Plugin Tests', () => {
|
|||
const Test = new Design({ parts: [part] })
|
||||
const pattern = new Test()
|
||||
pattern.draft()
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.lining.cut).to.equal(3)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.lining.identical).to.equal(true)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.lining).to.have.lengthOf(1)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.lining[0]).to.deep.equal({
|
||||
cut: 3,
|
||||
identical: true,
|
||||
bias: false,
|
||||
ignoreOnFold: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('Should remove cut info via addCut(false)', () => {
|
||||
|
@ -93,7 +103,7 @@ describe('Cutlist Plugin Tests', () => {
|
|||
const pattern = new Test()
|
||||
pattern.draft()
|
||||
expect(typeof pattern.setStores[0].cutlist.example_part.materials.lining).to.equal('undefined')
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric.cut).to.equal(2)
|
||||
expect(pattern.setStores[0].cutlist.example_part.materials.fabric[0].cut).to.equal(2)
|
||||
})
|
||||
|
||||
it('Should remove cut info for all materials via removeCut(true)', () => {
|
||||
|
|
|
@ -24,7 +24,7 @@ export const plugin = {
|
|||
delete points.cutonfoldTo
|
||||
delete points.cutonfoldVia1
|
||||
delete points.cutonfoldVia2
|
||||
delete paths.cutonfold
|
||||
delete paths.cutonfoldCutonfold
|
||||
// setCutOnFold relies on plugin-cutlist
|
||||
if (typeof setCutOnFold === 'function') {
|
||||
setCutOnFold(false) // Restore default
|
||||
|
|
|
@ -11,10 +11,14 @@ export const plugin = {
|
|||
)
|
||||
return text
|
||||
}
|
||||
const prefix = data.prefix || ''
|
||||
return typeof data.strings[locale][prefix + text] === 'undefined'
|
||||
? text
|
||||
: data.strings[locale][prefix + text]
|
||||
if (data.t) {
|
||||
return data.t(text)
|
||||
} else {
|
||||
const prefix = data.prefix || ''
|
||||
return typeof data.strings[locale][prefix + text] === 'undefined'
|
||||
? text
|
||||
: data.strings[locale][prefix + text]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -31,20 +31,17 @@ export const plugin = {
|
|||
},
|
||||
},
|
||||
macros: {
|
||||
title: function (so, { points, scale, locale, store }) {
|
||||
title: function (so, { points, scale, locale, store, part }) {
|
||||
const prefix = so.prefix || ''
|
||||
let overwrite = !so.append
|
||||
|
||||
// Passing `false` will remove the title
|
||||
if (so === false) {
|
||||
for (const id of [
|
||||
`_${prefix}_titleNr`,
|
||||
`_${prefix}_titleName`,
|
||||
`_${prefix}_titlePattern`,
|
||||
`_${prefix}_titleFor`,
|
||||
`_${prefix}_exportDate`,
|
||||
])
|
||||
delete points[id]
|
||||
return true
|
||||
if (so === false || overwrite) {
|
||||
Object.keys(points).forEach((p) => {
|
||||
if (p.startsWith(`_${prefix}_title`) || p === `_${prefix}_exportDate`) delete points[p]
|
||||
})
|
||||
|
||||
if (so === false) return true
|
||||
}
|
||||
|
||||
const transform = function (anchor) {
|
||||
|
@ -53,44 +50,74 @@ export const plugin = {
|
|||
|
||||
return `matrix(${so.scale}, 0, 0, ${so.scale}, ${cx}, ${cy}) rotate(${so.rotation} ${anchor.x} ${anchor.y})`
|
||||
}
|
||||
let shift = 8
|
||||
const nextPoint = (text, textClass, shiftAmt = shift) => {
|
||||
const newPoint = so.at
|
||||
.shift(-90 - so.rotation, shiftAmt * so.scale)
|
||||
.addText(text, textClass)
|
||||
newPoint.attr('data-text-transform', transform(newPoint))
|
||||
return newPoint
|
||||
}
|
||||
const defaults = {
|
||||
scale: 1,
|
||||
rotation: 0,
|
||||
cutlist: true,
|
||||
}
|
||||
|
||||
so = { ...defaults, ...so }
|
||||
so.scale = so.scale * scale
|
||||
let overwrite = true
|
||||
if (so.append) overwrite = false
|
||||
|
||||
points[`_${prefix}_titleNr`] = so.at
|
||||
.clone()
|
||||
.attr('data-text', so.nr, overwrite)
|
||||
.attr('data-text-class', 'text-4xl fill-note font-bold')
|
||||
.attr('data-text-transform', transform(so.at))
|
||||
let shift = 8
|
||||
|
||||
if (so.title) {
|
||||
points[`_${prefix}_titleName`] = so.at
|
||||
.shift(-90 - so.rotation, shift * so.scale)
|
||||
.attr('data-text', so.title)
|
||||
.attr('data-text-class', 'text-lg fill-current font-bold')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, 13 * so.scale)))
|
||||
points[`_${prefix}_titleName`] = nextPoint(so.title, 'text-lg fill-current font-bold')
|
||||
shift += 8
|
||||
}
|
||||
|
||||
// Cut List instructions
|
||||
const partCutlist = store.get(['cutlist', part.name])
|
||||
// if there's a cutlist and it should be included
|
||||
if (so.cutlist && partCutlist?.materials) {
|
||||
// get the default cutonfold
|
||||
const cutonfold = partCutlist.cutOnFold
|
||||
// each material
|
||||
for (const material in partCutlist.materials) {
|
||||
// each set of instructions
|
||||
partCutlist.materials[material].forEach(({ cut, identical, bias, ignoreOnFold }, c) => {
|
||||
// make a new point for this set of instructions
|
||||
const cutPoint = nextPoint('plugin:cut', 'text-md fill-current').addText(cut)
|
||||
|
||||
// if they're not identical, add that to the point's text
|
||||
if (!identical && cut > 1) cutPoint.addText('plugin:mirrored')
|
||||
|
||||
// if they should be cut on the fold add that, with bias or without
|
||||
if (cutonfold && !ignoreOnFold)
|
||||
cutPoint.addText(bias ? 'plugin:onFoldAndBias' : 'plugin:onFoldLower')
|
||||
// otherwise if they should be on the bias, say so
|
||||
else if (bias) cutPoint.addText('plugin:onBias')
|
||||
|
||||
// add 'from' the material
|
||||
cutPoint.addText('plugin:from').addText('plugin:' + material)
|
||||
|
||||
// save and shift
|
||||
points[`_${prefix}_titleCut_${material}_${c}`] = cutPoint
|
||||
shift += 8
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let name = store.data?.name || 'No Name'
|
||||
name = name.replace('@freesewing/', '')
|
||||
points[`_${prefix}_titlePattern`] = so.at
|
||||
.shift(-90 - so.rotation, shift * so.scale)
|
||||
.attr('data-text', name)
|
||||
.attr('data-text', 'v' + (store.data?.version || 'No Version'))
|
||||
.attr('data-text-class', 'fill-note')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, shift * so.scale)))
|
||||
name += 'v' + (store.data?.version || 'No Version')
|
||||
points[`_${prefix}_titlePattern`] = nextPoint(name, 'fill-note')
|
||||
|
||||
if (store.data.for) {
|
||||
shift += 8
|
||||
points[`_${prefix}_titleFor`] = so.at
|
||||
.shift(-90 - so.rotation, shift * so.scale)
|
||||
.attr('data-text', '( ' + store.data.for + ' )')
|
||||
.attr('data-text-class', 'fill-current font-bold')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, shift * so.scale)))
|
||||
points[`_${prefix}_titleFor`] = nextPoint(`( ${store.data.for} )`, 'fill-current font-bold')
|
||||
}
|
||||
shift += 6
|
||||
const now = new Date()
|
||||
|
@ -98,20 +125,13 @@ export const plugin = {
|
|||
let mins = now.getMinutes()
|
||||
if (hours < 10) hours = `0${hours}`
|
||||
if (mins < 10) mins = `0${mins}`
|
||||
points[`_${prefix}_exportDate`] = so.at
|
||||
.shift(-90 - so.rotation, shift * so.scale)
|
||||
.attr(
|
||||
'data-text',
|
||||
now.toLocaleDateString(locale || 'en', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
)
|
||||
.attr('data-text', `@ ${hours}:${mins}`)
|
||||
.attr('data-text-class', 'text-sm')
|
||||
.attr('data-text-transform', transform(so.at.shift(-90 - so.rotation, shift * so.scale)))
|
||||
const exportDate = now.toLocaleDateString(locale || 'en', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
points[`_${prefix}_exportDate`] = nextPoint(`${exportDate}@ ${hours}:${mins}`, 'text-sm')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -44,15 +44,15 @@
|
|||
"pino": "8.11.0",
|
||||
"qrcode": "1.5.1",
|
||||
"swagger-ui-dist": "4.17.1",
|
||||
"swagger-ui-express": "4.6.0"
|
||||
"swagger-ui-express": "4.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "4.2.0",
|
||||
"chai-http": "4.3.0",
|
||||
"esbuild": "0.17.10",
|
||||
"esbuild": "0.17.11",
|
||||
"mocha": "10.0.0",
|
||||
"mocha-steps": "1.3.0",
|
||||
"nodemon": "2.0.20",
|
||||
"nodemon": "2.0.21",
|
||||
"prisma": "4.10.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -7,11 +7,18 @@ import { ThemePicker } from 'shared/components/theme-picker/index.mjs'
|
|||
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
|
||||
import { getCrumbs } from 'shared/utils.mjs'
|
||||
import { HomeIcon } from 'shared/components/icons.mjs'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const DocsLayout = ({ app, title = false, crumbs = false, children = [] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
const breadcrumbs = crumbs ? crumbs : getCrumbs(app, slug, title)
|
||||
const [slug, setSlug] = useState('')
|
||||
const [breadcrumbs, setBreadcrumbs] = useState(crumbs)
|
||||
|
||||
useEffect(() => {
|
||||
const newSlug = router.asPath.slice(1)
|
||||
setSlug(newSlug)
|
||||
if (!breadcrumbs) setBreadcrumbs(getCrumbs(app, newSlug, title))
|
||||
}, [router.asPath, breadcrumbs, app, title])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch">
|
||||
|
|
|
@ -3,17 +3,17 @@ import { MainSections, ActiveSection } from './primary.mjs'
|
|||
export const AsideNavigation = ({ app, slug, mobileOnly = false, before = [], after = [] }) => (
|
||||
<aside
|
||||
className={`
|
||||
fixed top-0 right-0 h-screen w-screen
|
||||
fixed top-0 right-0 h-screen
|
||||
overflow-y-auto z-20
|
||||
bg-base-100 text-base-content
|
||||
${app.primaryMenu ? '' : 'translate-x-[-120%]'} transition-transform
|
||||
px-6 pb-20 pt-8 shrink-0
|
||||
|
||||
lg:w-auto
|
||||
lg:sticky lg:relative lg:transform-none
|
||||
lg:justify-center
|
||||
lg:border-r-2 lg:border-base-200 lg:bg-base-200 lg:bg-opacity-50
|
||||
${mobileOnly ? 'block lg:hidden' : ''}
|
||||
w-full
|
||||
${mobileOnly ? 'block lg:hidden w-full ' : ''}
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
|
|
|
@ -29,14 +29,14 @@
|
|||
"@mdx-js/mdx": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@mdx-js/runtime": "2.0.0-next.9",
|
||||
"@next/bundle-analyzer": "13.2.3",
|
||||
"@next/bundle-analyzer": "13.2.4",
|
||||
"@tailwindcss/typography": "0.5.9",
|
||||
"algoliasearch": "4.15.0",
|
||||
"daisyui": "2.51.3",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"next": "13.2.3",
|
||||
"next": "13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "5.1.0",
|
||||
"react-dom": "18.2.0",
|
||||
|
@ -55,7 +55,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint-config-next": "13.2.3",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { shouldSkipBuild } from '../../scripts/skip-build-base.mjs'
|
||||
|
||||
shouldSkipBuild('Dev')
|
||||
shouldSkipBuild('Dev', '../shared . ../../markdown/dev')
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"lodash.get": "4.4.2",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"next": "13.2.3",
|
||||
"next": "13.2.4",
|
||||
"next-i18next": "13.1.4",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "5.1.0",
|
||||
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint-config-next": "13.2.3",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"luxon": "3.3.0",
|
||||
"next": "13.2.3",
|
||||
"next": "13.2.4",
|
||||
"react-dropzone": "14.2.3",
|
||||
"react-hotkeys-hook": "4.3.2",
|
||||
"react-instantsearch-dom": "6.39.0",
|
||||
|
@ -59,7 +59,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint-config-next": "13.2.3",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { shouldSkipBuild } from '../../scripts/skip-build-base.mjs'
|
||||
|
||||
shouldSkipBuild('Org')
|
||||
shouldSkipBuild('Org', '../shared . ../../markdown/org')
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"eslint": "8.34.0",
|
||||
"prettier": "2.8.4",
|
||||
"typescript": "4.9.5",
|
||||
"@sanity/cli": "3.2.6"
|
||||
"@sanity/cli": "3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* These icons are kept out of the consolidated icons file because they use different props
|
||||
* I think we should move them to the component where they are used, for I don't think
|
||||
* they are used anywhere else, so there's little use in having them here
|
||||
*/
|
||||
const SheetIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l3.5-2 3.5 2 3.5-2 3.5 2z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SheetIcon
|
|
@ -53,7 +53,7 @@ const XrayText = (props) => (
|
|||
)
|
||||
|
||||
const TextSpans = ({ point, className = '', style = {}, onClick = null }) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const { t } = useTranslation(['plugin'])
|
||||
let text = []
|
||||
// Handle translation
|
||||
let translated = ''
|
||||
|
@ -117,7 +117,7 @@ const XrayTextOnPath = (props) => (
|
|||
)
|
||||
|
||||
export const TextOnPath = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const { t } = useTranslation(['plugin'])
|
||||
// Handle translation (and spaces)
|
||||
let translated = ''
|
||||
for (let string of props.path.attributes.getAsArray('data-text')) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import Worker from 'web-worker'
|
||||
import fileSaver from 'file-saver'
|
||||
import { themePlugin } from '@freesewing/plugin-theme'
|
||||
import { pagesPlugin } from '../layout/print/plugin.mjs'
|
||||
import { pluginI18n } from '@freesewing/plugin-i18n'
|
||||
import { pagesPlugin } from '../layout/plugin-layout-part.mjs'
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
|
||||
export const exportTypes = {
|
||||
|
@ -75,14 +76,7 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE
|
|||
|
||||
// add the theme and translation to the pattern
|
||||
pattern.use(themePlugin, { stripped: format !== 'svg', skipGrid: ['pages'] })
|
||||
pattern.use(
|
||||
{
|
||||
hooks: {
|
||||
insertText: (locale, text, { t }) => t(text),
|
||||
},
|
||||
},
|
||||
{ t }
|
||||
)
|
||||
pattern.use(pluginI18n, { t })
|
||||
|
||||
// a specified size should override the gist one
|
||||
if (format !== 'pdf') {
|
||||
|
|
|
@ -9,7 +9,7 @@ export const ExportDraft = ({ gist, design, app }) => {
|
|||
const [error, setError] = useState(false)
|
||||
const [format, setFormat] = useState(false)
|
||||
|
||||
const { t } = useTranslation(['app'])
|
||||
const { t } = useTranslation(['app', 'plugin'])
|
||||
const doExport = (format) => {
|
||||
setLink(false)
|
||||
setError(false)
|
||||
|
|
|
@ -56,8 +56,6 @@ export const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) =>
|
|||
)
|
||||
|
||||
// use this for better update efficiency
|
||||
// FIXME: This breaks gist updates.
|
||||
// See: https://github.com/freesewing/freesewing/issues/2281
|
||||
const memoVal = useMemo(() => gist.measurements?.[m], [gist])
|
||||
// track validity against the value and the units
|
||||
const valid = useMemo(
|
||||
|
|
|
@ -1,15 +1,132 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { CutLayoutSettings } from './settings.mjs'
|
||||
import { Draft } from '../draft/index.mjs'
|
||||
import { fabricPlugin } from '../plugin-layout-part.mjs'
|
||||
import { cutLayoutPlugin } from './plugin-cut-layout.mjs'
|
||||
import { pluginCutlist } from '@freesewing/plugin-cutlist'
|
||||
import { pluginFlip } from '@freesewing/plugin-flip'
|
||||
import { measurementAsMm } from 'shared/utils.mjs'
|
||||
import { useEffect } from 'react'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const activeFabricPath = ['_state', 'layout', 'forCutting', 'activeFabric']
|
||||
const useFabricSettings = (gist) => {
|
||||
const isImperial = gist.units === 'imperial'
|
||||
const sheetHeight = measurementAsMm(isImperial ? 36 : 100, gist.units)
|
||||
const activeFabric = get(gist, activeFabricPath) || 'fabric'
|
||||
const gistSettings = get(gist, ['_state', 'layout', 'forCutting', 'fabric', activeFabric])
|
||||
const sheetWidth = gistSettings?.sheetWidth || measurementAsMm(isImperial ? 54 : 120, gist.units)
|
||||
const grainDirection =
|
||||
gistSettings?.grainDirection === undefined ? 90 : gistSettings.grainDirection
|
||||
|
||||
return { activeFabric, sheetWidth, grainDirection, sheetHeight }
|
||||
}
|
||||
|
||||
const useFabricDraft = (gist, design, fabricSettings) => {
|
||||
// get the appropriate layout for the view
|
||||
const layout =
|
||||
get(gist, ['layouts', gist._state.view, fabricSettings.activeFabric]) || gist.layout || true
|
||||
// hand it separately to the design
|
||||
const draft = new design({ ...gist, layout })
|
||||
|
||||
const layoutSettings = {
|
||||
sheetWidth: fabricSettings.sheetWidth,
|
||||
sheetHeight: fabricSettings.sheetHeight,
|
||||
}
|
||||
|
||||
let patternProps
|
||||
try {
|
||||
// add the fabric plugin to the draft
|
||||
draft.use(fabricPlugin(layoutSettings))
|
||||
// add the cutLayout plugin
|
||||
draft.use(cutLayoutPlugin(fabricSettings.activeFabric, fabricSettings.grainDirection))
|
||||
// also, pluginCutlist and pluginFlip are needed
|
||||
draft.use(pluginCutlist)
|
||||
draft.use(pluginFlip)
|
||||
|
||||
// draft the pattern
|
||||
draft.draft()
|
||||
patternProps = draft.getRenderProps()
|
||||
} catch (err) {
|
||||
console.log(err, gist)
|
||||
}
|
||||
|
||||
return { draft, patternProps }
|
||||
}
|
||||
|
||||
const useFabricList = (draft) => {
|
||||
const cutList = draft.setStores[0].get('cutlist')
|
||||
const fabricList = ['fabric']
|
||||
for (const partName in cutList) {
|
||||
for (const matName in cutList[partName].materials) {
|
||||
if (!fabricList.includes(matName)) fabricList.push(matName)
|
||||
}
|
||||
}
|
||||
|
||||
return fabricList
|
||||
}
|
||||
|
||||
const bgProps = { fill: 'none' }
|
||||
export const CutLayout = (props) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
const { t } = useTranslation(['workbench', 'plugin'])
|
||||
const { gist, design, updateGist } = props
|
||||
|
||||
let name = props.design.designConfig.data.name
|
||||
// disable xray
|
||||
useEffect(() => {
|
||||
if (gist?._state?.xray?.enabled) updateGist(['_state', 'xray', 'enabled'], false)
|
||||
})
|
||||
|
||||
const fabricSettings = useFabricSettings(gist)
|
||||
const { draft, patternProps } = useFabricDraft(gist, design, fabricSettings)
|
||||
const fabricList = useFabricList(draft)
|
||||
|
||||
const setCutFabric = (newFabric) => {
|
||||
updateGist(activeFabricPath, newFabric)
|
||||
}
|
||||
|
||||
let name = design.designConfig.data.name
|
||||
name = name.replace('@freesewing/', '')
|
||||
return (
|
||||
|
||||
const settingsProps = {
|
||||
gist,
|
||||
updateGist,
|
||||
patternProps,
|
||||
unsetGist: props.unsetGist,
|
||||
...fabricSettings,
|
||||
}
|
||||
|
||||
return patternProps ? (
|
||||
<div>
|
||||
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2>
|
||||
<CutLayoutSettings {...props} />
|
||||
<CutLayoutSettings {...settingsProps} />
|
||||
<div className="my-4">
|
||||
{fabricList.length > 1 ? (
|
||||
<div className="tabs">
|
||||
{fabricList.map((title) => (
|
||||
<button
|
||||
key={title}
|
||||
className={`text-xl font-bold capitalize tab tab-bordered grow ${
|
||||
fabricSettings.activeFabric === title ? 'tab-active' : ''
|
||||
}`}
|
||||
onClick={() => setCutFabric(title)}
|
||||
>
|
||||
{t('plugin:' + title)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<Draft
|
||||
draft={draft}
|
||||
gist={gist}
|
||||
updateGist={updateGist}
|
||||
patternProps={patternProps}
|
||||
bgProps={bgProps}
|
||||
gistReady={props.gistReady}
|
||||
layoutPart="fabric"
|
||||
layoutType={['cuttingLayout', fabricSettings.activeFabric]}
|
||||
layoutSetType="forCutting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
const prefix = 'mirroredOnFold'
|
||||
|
||||
// types of path operations
|
||||
const opTypes = ['to', 'cp1', 'cp2']
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
// get the part that's just been drafted
|
||||
const part = pattern.parts[pattern.activeSet][pattern.activePart]
|
||||
// if it's a duplicated cut part, the fabric part, or it's hidden, leave it alone
|
||||
if (pattern.activePart.startsWith('cut.') || pattern.activePart === 'fabric' || part.hidden)
|
||||
return
|
||||
|
||||
// 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
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 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: {
|
||||
// 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(/^(cutonfold|grainline|__scalebox|__miniscale)/))
|
||||
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 = Math.abs(grainAngle - partGrain)
|
||||
// don't over rotate
|
||||
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()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,7 +1,139 @@
|
|||
export const CutLayoutSettings = () => {
|
||||
import { ClearIcon, IconWrapper } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { formatFraction128, measurementAsMm, round, formatMm } from 'shared/utils.mjs'
|
||||
import { ShowButtonsToggle } from '../draft/buttons.mjs'
|
||||
|
||||
const SheetIcon = (props) => (
|
||||
<IconWrapper {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l3.5-2 3.5 2 3.5-2 3.5 2z"
|
||||
/>
|
||||
</IconWrapper>
|
||||
)
|
||||
|
||||
const GrainIcon = (props) => (
|
||||
<IconWrapper {...props}>
|
||||
<g>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 17l-5-5 5-5" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12l8 0" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M18 7l5 5-5 5" />
|
||||
</g>
|
||||
</IconWrapper>
|
||||
)
|
||||
const FabricSizer = ({ gist, updateGist, activeFabric, sheetWidth }) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
let val = formatMm(sheetWidth, gist.units, 'none')
|
||||
// onChange
|
||||
const update = (evt) => {
|
||||
evt.stopPropagation()
|
||||
let evtVal = evt.target.value
|
||||
// set Val immediately so that the input reflects it
|
||||
val = evtVal
|
||||
|
||||
let useVal = measurementAsMm(evtVal, gist.units)
|
||||
// only set to the gist if it's valid
|
||||
if (!isNaN(useVal)) {
|
||||
updateGist(['_state', 'layout', 'forCutting', 'fabric', activeFabric, 'sheetWidth'], useVal)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Fixme: Cut layout settings here</p>
|
||||
<label className="input-group">
|
||||
<span className="label-text font-bold">{`${t(activeFabric)} ${t('width')}`}</span>
|
||||
<input
|
||||
key="input-fabricWidth"
|
||||
type="text"
|
||||
className="input input-bordered grow text-base-content border-r-0"
|
||||
value={val}
|
||||
onChange={update}
|
||||
/>
|
||||
<span className="bg-transparent border input-bordered">
|
||||
{gist.units == 'metric' ? 'cm' : 'in'}
|
||||
</span>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
export const GrainDirectionPicker = ({ grainDirection, activeFabric, updateGist }) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`
|
||||
btn btn-primary flex flex-row gap-2 items-center
|
||||
hover:text-primary-content ml-4
|
||||
`}
|
||||
onClick={() =>
|
||||
updateGist(
|
||||
['_state', 'layout', 'forCutting', 'fabric', activeFabric, 'grainDirection'],
|
||||
grainDirection === 0 ? 90 : 0
|
||||
)
|
||||
}
|
||||
>
|
||||
<GrainIcon className={`h-6 w-6 mr-2 ${grainDirection === 0 ? '' : 'rotate-90'}`} />
|
||||
<span>{t(`grainDirection`)}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const useFabricLength = (isImperial, height) => {
|
||||
// regular conversion from mm to inches or cm
|
||||
const unit = isImperial ? 25.4 : 10
|
||||
// conversion from inches or cm to yards or meters
|
||||
const fabricUnit = isImperial ? 36 : 100
|
||||
// for fabric, these divisions are granular enough
|
||||
const rounder = isImperial ? 16 : 10
|
||||
|
||||
// we convert the used fabric height to the right units so we can round it
|
||||
const inFabricUnits = height / (fabricUnit * unit)
|
||||
// we multiply it by the rounder, round it up, then divide by the rounder again to get the rounded amount
|
||||
const roundCount = Math.ceil(rounder * inFabricUnits) / rounder
|
||||
// format as a fraction for imperial, a decimal for metric
|
||||
const count = isImperial ? formatFraction128(roundCount, 'none') : round(roundCount, 1)
|
||||
|
||||
return `${count}${isImperial ? 'yds' : 'm'}`
|
||||
}
|
||||
|
||||
export const CutLayoutSettings = ({
|
||||
gist,
|
||||
patternProps,
|
||||
unsetGist,
|
||||
updateGist,
|
||||
activeFabric,
|
||||
sheetWidth,
|
||||
grainDirection,
|
||||
}) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
const fabricLength = useFabricLength(gist.units === 'imperial', patternProps.height)
|
||||
|
||||
return (
|
||||
<div className="flex flex-row justify-between mb-2 items-center">
|
||||
<div className="flex">
|
||||
<FabricSizer {...{ gist, updateGist, activeFabric, sheetWidth }} />
|
||||
<GrainDirectionPicker {...{ updateGist, activeFabric, grainDirection }} />
|
||||
</div>
|
||||
<div>
|
||||
<SheetIcon className="h-6 w-6 mr-2 inline align-middle" />
|
||||
<span className="text-xl font-bold align-middle">{fabricLength}</span>
|
||||
</div>
|
||||
<div>
|
||||
<ShowButtonsToggle
|
||||
gist={gist}
|
||||
updateGist={updateGist}
|
||||
layoutSetType="forCutting"
|
||||
></ShowButtonsToggle>
|
||||
<button
|
||||
key="reset"
|
||||
onClick={() => unsetGist(['layouts', 'cuttingLayout', activeFabric])}
|
||||
className="btn btn-primary btn-outline"
|
||||
>
|
||||
<ClearIcon className="h-6 w-6 mr-2" />
|
||||
{t('reset')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => (
|
||||
<path
|
||||
|
@ -53,6 +54,25 @@ const Button = ({ onClickCb, transform, Icon, children }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const ShowButtonsToggle = ({ gist, layoutSetType, updateGist }) => {
|
||||
const { t } = useTranslation('workbench')
|
||||
const path = ['_state', 'layout', layoutSetType, 'showButtons']
|
||||
const showButtons = get(gist, path, true)
|
||||
const setShowButtons = () => updateGist(path, !showButtons)
|
||||
|
||||
return (
|
||||
<label htmlFor="showButtons" className="label">
|
||||
<span className="mr-2">{t('showButtons')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
checked={showButtons}
|
||||
onChange={setShowButtons}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
/** buttons for manipulating the part */
|
||||
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90 }) => {
|
||||
const { t } = useTranslation('workbench')
|
||||
|
|
|
@ -2,10 +2,10 @@ import { useRef } from 'react'
|
|||
import { Stack } from './stack.mjs'
|
||||
import { SvgWrapper } from '../../draft/svg.mjs'
|
||||
import { PartInner } from '../../draft/part.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
export const Draft = (props) => {
|
||||
const {
|
||||
draft,
|
||||
patternProps,
|
||||
gist,
|
||||
updateGist,
|
||||
|
@ -13,12 +13,14 @@ export const Draft = (props) => {
|
|||
bgProps = {},
|
||||
fitLayoutPart = false,
|
||||
layoutType = 'printingLayout',
|
||||
layoutSetType = 'forPrinting',
|
||||
} = props
|
||||
|
||||
const svgRef = useRef(null)
|
||||
if (!patternProps) return null
|
||||
// keep a fresh copy of the layout because we might manipulate it without saving to the gist
|
||||
let layout = draft.settings[0].layouts?.printingLayout || {
|
||||
const layoutPath = ['layouts'].concat(layoutType)
|
||||
let layout = get(patternProps.settings[0], layoutPath) || {
|
||||
...patternProps.autoLayout,
|
||||
width: patternProps.width,
|
||||
height: patternProps.height,
|
||||
|
@ -53,7 +55,7 @@ export const Draft = (props) => {
|
|||
newLayout.topLeft = topLeft
|
||||
|
||||
if (history) {
|
||||
updateGist(['layouts', layoutType], newLayout, history)
|
||||
updateGist(layoutPath, newLayout, history)
|
||||
} else {
|
||||
// we don't put it in the gist if it shouldn't contribute to history because we need some of the data calculated here for rendering purposes on the initial layout, but we don't want to actually save a layout until the user manipulates it. This is what allows the layout to respond appropriately to settings changes. Once the user has starting playing with the layout, all bets are off
|
||||
layout = newLayout
|
||||
|
@ -97,6 +99,7 @@ export const Draft = (props) => {
|
|||
gist,
|
||||
updateLayout,
|
||||
isLayoutPart: stackName === props.layoutPart,
|
||||
layoutSetType: layoutSetType,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -50,6 +50,7 @@ import { getProps, angle } from '../../draft/utils.mjs'
|
|||
import { drag } from 'd3-drag'
|
||||
import { select } from 'd3-selection'
|
||||
import { Buttons } from './buttons.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
export const Stack = (props) => {
|
||||
const { layout, stack, stackName, gist } = props
|
||||
|
@ -230,6 +231,7 @@ export const Stack = (props) => {
|
|||
// don't render if the part is empty
|
||||
// if (Object.keys(part.snippets).length === 0 && Object.keys(part.paths).length === 0) return null;
|
||||
|
||||
const showButtons = get(gist, ['_state', 'layout', props.layoutSetType, 'showButtons'], true)
|
||||
return (
|
||||
<g id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}>
|
||||
<g id={`stack-inner-${stackName}`} ref={innerRef}>
|
||||
|
@ -249,17 +251,19 @@ export const Stack = (props) => {
|
|||
id={`${stackName}-layout-rect`}
|
||||
onClick={toggleDragRotate}
|
||||
/>
|
||||
<Buttons
|
||||
transform={`translate(${center.x}, ${center.y}) rotate(${-rotation}) scale(${
|
||||
flipX ? -1 : 1
|
||||
},${flipY ? -1 : 1})`}
|
||||
flip={flip}
|
||||
rotate={rotate}
|
||||
setRotate={setRotate}
|
||||
resetPart={resetPart}
|
||||
rotate90={rotate90}
|
||||
partName={stackName}
|
||||
/>
|
||||
{showButtons ? (
|
||||
<Buttons
|
||||
transform={`translate(${center.x}, ${center.y}) rotate(${-rotation}) scale(${
|
||||
flipX ? -1 : 1
|
||||
},${flipY ? -1 : 1})`}
|
||||
flip={flip}
|
||||
rotate={rotate}
|
||||
setRotate={setRotate}
|
||||
resetPart={resetPart}
|
||||
rotate90={rotate90}
|
||||
partName={stackName}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
|
|
|
@ -50,6 +50,14 @@ export const pagesPlugin = ({ size = 'a4', ...settings }) => {
|
|||
return basePlugin({ ...settings, sheetWidth, sheetHeight })
|
||||
}
|
||||
|
||||
export const fabricPlugin = (settings) => {
|
||||
return basePlugin({
|
||||
...settings,
|
||||
partName: 'fabric',
|
||||
responsiveColumns: false,
|
||||
})
|
||||
}
|
||||
|
||||
/** check if there is anything to render on the given section of the svg so that we can skip empty pages */
|
||||
const doScanForBlanks = (stacks, layout, x, y, w, h) => {
|
||||
let hasContent = false
|
||||
|
@ -86,13 +94,26 @@ const doScanForBlanks = (stacks, layout, x, y, w, h) => {
|
|||
return hasContent
|
||||
}
|
||||
|
||||
function addToOnly(pattern, partName) {
|
||||
const only = pattern.settings[0].only
|
||||
if (only && !only.includes(partName)) {
|
||||
pattern.settings[0].only = [].concat(only, partName)
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromOnly(pattern, partName) {
|
||||
const only = pattern.settings[0].only
|
||||
if (only && only.includes(partName)) {
|
||||
pattern.settings[0].only.splice(only.indexOf(partName), 1)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The base plugin for adding a layout helper part like pages or fabric
|
||||
* sheetWidth: the width of the helper part
|
||||
* sheetHeight: the height of the helper part
|
||||
* boundary: should the helper part calculate its boundary?
|
||||
* responsiveColumns: should the part make more columns if the pattern exceed its width? (for pages you want this, for fabric you don't)
|
||||
* printStyle: hould the pages be rendered for printing or for screen viewing?
|
||||
* printStyle: should the pages be rendered for printing or for screen viewing?
|
||||
* */
|
||||
const basePlugin = ({
|
||||
sheetWidth,
|
||||
|
@ -108,7 +129,12 @@ const basePlugin = ({
|
|||
name,
|
||||
version,
|
||||
hooks: {
|
||||
preLayout: function (pattern) {
|
||||
preDraft: function (pattern) {
|
||||
if (!responsiveColumns) {
|
||||
pattern.settings[0].maxWidth = sheetWidth
|
||||
}
|
||||
|
||||
addToOnly(pattern, partName)
|
||||
// Add part
|
||||
pattern.addPart({
|
||||
name: partName,
|
||||
|
@ -124,20 +150,14 @@ const basePlugin = ({
|
|||
return shorthand.part
|
||||
},
|
||||
})
|
||||
|
||||
// Re-calculate the pattern's config
|
||||
pattern.getConfig()
|
||||
// create the part so that a stack gets made for it during packing
|
||||
// but don't draft it so that it doesn't have a size
|
||||
pattern.createPartForSet(partName, pattern.activeSet)
|
||||
},
|
||||
postLayout: function (pattern) {
|
||||
let { height, width, stacks } = pattern
|
||||
if (!responsiveColumns) width = sheetWidth
|
||||
// get the layout
|
||||
const layout =
|
||||
typeof pattern.settings[0].layout === 'object'
|
||||
? pattern.settings[0].layout
|
||||
typeof pattern.settings[pattern.activeSet].layout === 'object'
|
||||
? pattern.settings[pattern.activeSet].layout
|
||||
: pattern.autoLayout
|
||||
|
||||
// if the layout doesn't start at 0,0 we want to account for that in our height and width
|
||||
|
@ -164,22 +184,14 @@ const basePlugin = ({
|
|||
pattern.width = sheetWidth * generatedPageData.cols
|
||||
pattern.height = sheetHeight * generatedPageData.rows
|
||||
}
|
||||
|
||||
removeFromOnly(pattern, partName)
|
||||
},
|
||||
preRender: function (svg) {
|
||||
const pattern = svg.pattern
|
||||
const only = pattern.settings[pattern.activeStack || 0].only
|
||||
// add the layout part to the include list if there is one so that it gets rendered
|
||||
if (Array.isArray(only) && !only.includes(partName)) {
|
||||
pattern.settings[pattern.activeStack || 0].only.push(partName)
|
||||
}
|
||||
addToOnly(svg.pattern, partName)
|
||||
},
|
||||
postRender: function (svg) {
|
||||
const pattern = svg.pattern
|
||||
const only = pattern.settings[pattern.activeStack || 0].only
|
||||
// remove the layout part from the include list if there is one so that we don't pollute the settings
|
||||
if (Array.isArray(only) && only.includes(partName)) {
|
||||
pattern.settings[pattern.activeStack || 0].only.splice(only.indexOf(partName), 1)
|
||||
}
|
||||
removeFromOnly(svg.pattern, partName)
|
||||
},
|
||||
},
|
||||
macros: {
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { PrintLayoutSettings } from './settings.mjs'
|
||||
import { Draft } from '../draft/index.mjs'
|
||||
import { pagesPlugin } from './plugin.mjs'
|
||||
import { pagesPlugin } from '../plugin-layout-part.mjs'
|
||||
import {
|
||||
handleExport,
|
||||
defaultPdfSettings,
|
||||
|
@ -15,7 +15,7 @@ export const PrintLayout = (props) => {
|
|||
if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false)
|
||||
})
|
||||
|
||||
const { t } = useTranslation(['workbench'])
|
||||
const { t } = useTranslation(['workbench', 'plugin'])
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const draft = props.draft
|
||||
|
|
|
@ -2,6 +2,7 @@ import { PageSizePicker } from './pagesize-picker.mjs'
|
|||
import { PageOrientationPicker } from './orientation-picker.mjs'
|
||||
import { PrintIcon, RightIcon, ClearIcon, ExportIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ShowButtonsToggle } from '../draft/buttons.mjs'
|
||||
|
||||
export const PrintLayoutSettings = (props) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
@ -34,6 +35,11 @@ export const PrintLayoutSettings = (props) => {
|
|||
<PageOrientationPicker {...props} />
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<ShowButtonsToggle
|
||||
gist={props.gist}
|
||||
updateGist={props.updateGist}
|
||||
layoutSetType="forPrinting"
|
||||
></ShowButtonsToggle>
|
||||
<button
|
||||
key="export"
|
||||
onClick={props.exportIt}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next'
|
|||
|
||||
export const CoreSettingOnly = (props) => {
|
||||
const { t } = useTranslation(['app', 'parts', 'settings'])
|
||||
const list = props.draft.config.draftOrder
|
||||
const list = props.design.patternConfig.draftOrder
|
||||
const partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
|
||||
|
||||
const togglePart = (part) => {
|
||||
|
|
|
@ -21,7 +21,7 @@ export const OptionComponent = (props) => {
|
|||
const Value = values[capitalize(type)]
|
||||
|
||||
try {
|
||||
const hide = opt.hide && opt.hide(props.draft.settings)
|
||||
const hide = opt.hide && opt.hide(props.gist)
|
||||
|
||||
if (hide) return null
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Hooks
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { useGist } from 'shared/hooks/useGist'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import { pluginTheme } from '@freesewing/plugin-theme'
|
||||
import { pluginI18n } from '@freesewing/plugin-i18n'
|
||||
import { preloaders } from 'shared/components/workbench/preloaders.mjs'
|
||||
// Components
|
||||
import { WorkbenchMenu } from 'shared/components/workbench/menu/index.mjs'
|
||||
|
@ -70,6 +72,8 @@ export const WorkbenchWrapper = ({
|
|||
const [messages, setMessages] = useState([])
|
||||
const [popup, setPopup] = useState(false)
|
||||
const [preloaded, setPreloaded] = useState(false)
|
||||
// we'll only use this if the renderer is svg, but we can't call hooks conditionally
|
||||
const { t } = useTranslation(['plugin'])
|
||||
|
||||
// We'll use this in more than one location
|
||||
const hasRequiredMeasurements = hasRequiredMeasurementsMethod(design, gist)
|
||||
|
@ -128,7 +132,10 @@ export const WorkbenchWrapper = ({
|
|||
//draft.__init()
|
||||
|
||||
// add theme to svg renderer
|
||||
if (gist.renderer === 'svg') draft.use(pluginTheme, { skipGrid: ['pages'] })
|
||||
if (gist.renderer === 'svg') {
|
||||
draft.use(pluginI18n, { t })
|
||||
draft.use(pluginTheme, { skipGrid: ['pages'] })
|
||||
}
|
||||
|
||||
// draft it for draft and event views. Other views may add plugins, etc and we don't want to draft twice
|
||||
try {
|
||||
|
@ -146,7 +153,6 @@ export const WorkbenchWrapper = ({
|
|||
updateGist: updateWBGist,
|
||||
unsetGist,
|
||||
setGist,
|
||||
draft,
|
||||
feedback,
|
||||
gistReady,
|
||||
showInfo: setPopup,
|
||||
|
@ -176,7 +182,7 @@ export const WorkbenchWrapper = ({
|
|||
<LayoutComponent {...layoutProps}>
|
||||
{messages}
|
||||
<ErrorBoundary {...errorProps}>
|
||||
<Component {...componentProps} />
|
||||
<Component {...componentProps} draft={draft} />
|
||||
{popup && <Modal cancel={() => setPopup(false)}>{popup}</Modal>}
|
||||
</ErrorBoundary>
|
||||
</LayoutComponent>
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.unset": "4.5.2",
|
||||
"lodash.get": "4.4.2",
|
||||
"mdast-util-toc": "6.1.1",
|
||||
"pdfkit": "0.13.0",
|
||||
"postcss-for": "2.1.1",
|
||||
|
@ -39,7 +40,7 @@
|
|||
"react-markdown": "8.0.5",
|
||||
"react-sizeme": "3.0.2",
|
||||
"react-timeago": "7.1.0",
|
||||
"react-zoom-pan-pinch": "2.6.1",
|
||||
"react-zoom-pan-pinch": "3.0.2",
|
||||
"rehype-autolink-headings": "6.1.1",
|
||||
"rehype-highlight": "6.0.0",
|
||||
"remark-smartypants": "2.0.0",
|
||||
|
|
|
@ -26,35 +26,52 @@ export const formatImperial = (neg, inch, numo = false, deno = false, format = '
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* format a value to the nearest fraction with a denominator that is a power of 2
|
||||
* or a decimal if the value is between fractions
|
||||
* NOTE: this method does not convert mm to inches. It will turn any given value directly into its equivalent fractional representation
|
||||
*
|
||||
* fraction: the value to process
|
||||
* format: the type of formatting to apply. html, notags, or anything else which will only return numbers
|
||||
*/
|
||||
export const formatFraction128 = (fraction, format = 'html') => {
|
||||
let negative = ''
|
||||
let inches = ''
|
||||
let rest = ''
|
||||
if (fraction < 0) {
|
||||
fraction = fraction * -1
|
||||
negative = '-'
|
||||
}
|
||||
if (Math.abs(fraction) < 1) rest = fraction
|
||||
else {
|
||||
inches = Math.floor(fraction)
|
||||
rest = fraction - inches
|
||||
}
|
||||
let fraction128 = Math.round(rest * 128)
|
||||
if (fraction128 == 0) return formatImperial(negative, inches, false, false, format)
|
||||
|
||||
for (let i = 1; i < 7; i++) {
|
||||
const numoFactor = Math.pow(2, 7 - i)
|
||||
if (fraction128 % numoFactor === 0)
|
||||
return formatImperial(negative, inches, fraction128 / numoFactor, Math.pow(2, i), format)
|
||||
}
|
||||
|
||||
return (
|
||||
negative +
|
||||
Math.round(fraction * 100) / 100 +
|
||||
(format === 'html' || format === 'notags' ? '"' : '')
|
||||
)
|
||||
}
|
||||
|
||||
// Format a value in mm based on the user's units
|
||||
// Format can be html, notags, or anything else which will only return numbers
|
||||
export const formatMm = (val, units, format = 'html') => {
|
||||
val = roundMm(val)
|
||||
if (units === 'imperial') {
|
||||
if (val == 0) return formatImperial('', 0, false, false, format)
|
||||
let negative = ''
|
||||
let inches = ''
|
||||
let rest = ''
|
||||
let fraction = val / 25.4
|
||||
if (fraction < 0) {
|
||||
fraction = fraction * -1
|
||||
negative = '-'
|
||||
}
|
||||
if (Math.abs(fraction) < 1) rest = fraction
|
||||
else {
|
||||
inches = Math.floor(fraction)
|
||||
rest = fraction - inches
|
||||
}
|
||||
let fraction128 = Math.round(rest * 128)
|
||||
if (fraction128 == 0) return formatImperial(negative, inches, false, false, format)
|
||||
if (fraction128 % 64 == 0) return formatImperial(negative, inches, fraction128 / 64, 2, format)
|
||||
if (fraction128 % 32 == 0) return formatImperial(negative, inches, fraction128 / 32, 4, format)
|
||||
if (fraction128 % 16 == 0) return formatImperial(negative, inches, fraction128 / 16, 8, format)
|
||||
if (fraction128 % 8 == 0) return formatImperial(negative, inches, fraction128 / 8, 16, format)
|
||||
if (fraction128 % 4 == 0) return formatImperial(negative, inches, fraction128 / 4, 32, format)
|
||||
if (fraction128 % 2 == 0) return formatImperial(negative, inches, fraction128 / 2, 64, format)
|
||||
|
||||
return negative + Math.round(fraction * 100) / 100 + '"'
|
||||
let fraction = val / 25.4
|
||||
return formatFraction128(fraction, format)
|
||||
} else {
|
||||
if (format === 'html' || format === 'notags') return roundMm(val / 10) + 'cm'
|
||||
else return roundMm(val / 10)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue