1
0
Fork 0

Merge branch 'develop' into eriese-hiding

This commit is contained in:
Joost De Cock 2023-03-10 18:57:19 +01:00 committed by GitHub
commit dcb9833a9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 1950 additions and 1497 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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})
}
}
```

View file

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

View file

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

View 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

View file

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

View file

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

View 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

View file

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

View file

@ -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,11 +30,14 @@ const bundledPlugins = [
scaleboxPlugin,
sprinklePlugin,
titlePlugin,
pluginCutlist,
]
function bundleHooks() {
const hooks = {}
for (const plugin of bundledPlugins) {
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]
@ -44,23 +48,26 @@ function bundleHooks() {
}
}
return hooks
}
function bundleMacros() {
const macros = {}
for (const plugin of bundledPlugins) {
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

View file

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

View file

@ -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)', () => {

View file

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

View file

@ -11,10 +11,14 @@ export const plugin = {
)
return 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]
}
},
},
}

View file

@ -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', {
const exportDate = 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)))
points[`_${prefix}_exportDate`] = nextPoint(`${exportDate}@ ${hours}:${mins}`, 'text-sm')
},
},
}

View file

@ -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": {

View file

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

View file

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

View file

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

View file

@ -1,3 +1,3 @@
import { shouldSkipBuild } from '../../scripts/skip-build-base.mjs'
shouldSkipBuild('Dev')
shouldSkipBuild('Dev', '../shared . ../../markdown/dev')

View file

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

View file

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

View file

@ -1,3 +1,3 @@
import { shouldSkipBuild } from '../../scripts/skip-build-base.mjs'
shouldSkipBuild('Org')
shouldSkipBuild('Org', '../shared . ../../markdown/org')

View file

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

View file

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

View file

@ -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')) {

View file

@ -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') {

View file

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

View file

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

View file

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

View file

@ -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()
}
}
},
},
}
}

View file

@ -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 (
<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>
<p>Fixme: Cut layout settings here</p>
<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>
)
}

View file

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

View file

@ -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,
}}
/>
)

View file

@ -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,6 +251,7 @@ export const Stack = (props) => {
id={`${stackName}-layout-rect`}
onClick={toggleDragRotate}
/>
{showButtons ? (
<Buttons
transform={`translate(${center.x}, ${center.y}) rotate(${-rotation}) scale(${
flipX ? -1 : 1
@ -260,6 +263,7 @@ export const Stack = (props) => {
rotate90={rotate90}
partName={stackName}
/>
) : null}
</>
)}
</g>

View file

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

View file

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

View file

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

View file

@ -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) => {

View file

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

View file

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

View file

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

View file

@ -26,16 +26,18 @@ export const formatImperial = (neg, inch, numo = false, deno = false, format = '
}
}
// 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)
/**
* 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 = ''
let fraction = val / 25.4
if (fraction < 0) {
fraction = fraction * -1
negative = '-'
@ -47,14 +49,29 @@ export const formatMm = (val, units, format = 'html') => {
}
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 + '"'
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 fraction = val / 25.4
return formatFraction128(fraction, format)
} else {
if (format === 'html' || format === 'notags') return roundMm(val / 10) + 'cm'
else return roundMm(val / 10)

1980
yarn.lock

File diff suppressed because it is too large Load diff