1
0
Fork 0

cutting layout page has tabs for each fabric type. parts duplication

This commit is contained in:
Enoch Riese 2023-02-27 17:47:34 -06:00
parent cd034a7cf3
commit 29e3ba6323
12 changed files with 224 additions and 158 deletions

View file

@ -380,6 +380,7 @@ shared:
'lodash.clonedeep': '4.5.0' 'lodash.clonedeep': '4.5.0'
'lodash.orderby': *_orderby 'lodash.orderby': *_orderby
'lodash.unset': *_unset 'lodash.unset': *_unset
'lodash.get': *_get
'mdast-util-toc': '6.1.0' 'mdast-util-toc': '6.1.0'
'pdfkit': '0.13.0' 'pdfkit': '0.13.0'
'postcss-for': '2.1.1' 'postcss-for': '2.1.1'

View file

@ -65,6 +65,7 @@ export const back = {
measurements, measurements,
log, log,
part, part,
addCut,
}) => { }) => {
// Get to work // Get to work
points.cbNeck = new Point(0, measurements.neck * options.backNeckCutout) points.cbNeck = new Point(0, measurements.neck * options.backNeckCutout)
@ -269,6 +270,8 @@ export const back = {
on: ['armholePitch', 'bustCenter'], on: ['armholePitch', 'bustCenter'],
}) })
addCut()
if (sa) paths.sa = paths.saBase.offset(sa).attr('class', 'fabric sa') if (sa) paths.sa = paths.saBase.offset(sa).attr('class', 'fabric sa')
if (paperless) { if (paperless) {

View file

@ -16,6 +16,7 @@ function draftCarltonBack({
paths, paths,
Path, Path,
part, part,
addCut,
}) { }) {
calculateRatios(part) calculateRatios(part)
// Belt width // Belt width
@ -95,6 +96,8 @@ function draftCarltonBack({
.line(points.bpStart) .line(points.bpStart)
.attr('class', 'dashed') .attr('class', 'dashed')
addCut(2)
addCut(2, 'lining')
if (complete) { if (complete) {
macro('sprinkle', { macro('sprinkle', {
snippet: 'bnotch', snippet: 'bnotch',

View file

@ -12,6 +12,7 @@ import { roundPlugin } from '../../plugin-round/src/index.mjs'
import { scaleboxPlugin } from '../../plugin-scalebox/src/index.mjs' import { scaleboxPlugin } from '../../plugin-scalebox/src/index.mjs'
import { sprinklePlugin } from '../../plugin-sprinkle/src/index.mjs' import { sprinklePlugin } from '../../plugin-sprinkle/src/index.mjs'
import { titlePlugin } from '../../plugin-title/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' import { name, version } from '../data.mjs'
const bundledPlugins = [ const bundledPlugins = [
@ -29,38 +30,44 @@ const bundledPlugins = [
scaleboxPlugin, scaleboxPlugin,
sprinklePlugin, sprinklePlugin,
titlePlugin, titlePlugin,
pluginCutlist,
] ]
function bundleHooks() { const hooks = {}
const hooks = {} const macros = {}
for (const plugin of bundledPlugins) { const store = []
for (const i in plugin.hooks) {
if (typeof hooks[i] === 'undefined') hooks[i] = [] function bundleHooks(plugin) {
const hook = plugin.hooks[i] for (const i in plugin.hooks) {
if (typeof hook === 'function') hooks[i].push(hook) if (typeof hooks[i] === 'undefined') hooks[i] = []
else if (typeof hook === 'object') { const hook = plugin.hooks[i]
for (let method of hook) hooks[i].push(method) 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() { function bundleMacros(plugin) {
const macros = {} for (const i in plugin.macros) macros[i] = plugin.macros[i]
for (const plugin of bundledPlugins) { }
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, hooks)
bundleMacros(plugin, macros)
bundleStore(plugin, store)
} }
export const plugin = { export const plugin = {
name, name,
version, version,
hooks: bundleHooks(), hooks,
macros: bundleMacros(), macros,
store,
} }
// More specifically named exports // More specifically named exports

View file

@ -4,8 +4,10 @@ import { Draft } from '../draft/index.mjs'
import { fabricPlugin } from '../plugin-layout-part.mjs' import { fabricPlugin } from '../plugin-layout-part.mjs'
import { cutLayoutPlugin } from './plugin-cut-layout.mjs' import { cutLayoutPlugin } from './plugin-cut-layout.mjs'
import { pluginCutlist } from '@freesewing/plugin-cutlist' import { pluginCutlist } from '@freesewing/plugin-cutlist'
import { useEffect } from 'react'
import { measurementAsMm } from 'shared/utils.mjs' import { measurementAsMm } from 'shared/utils.mjs'
import { useState, useEffect, useCallback, useRef } from 'react'
import { Tabs } from 'shared/components/mdx/tabs.mjs'
import get from 'lodash.get'
export const CutLayout = (props) => { export const CutLayout = (props) => {
const { t } = useTranslation(['workbench']) const { t } = useTranslation(['workbench'])
@ -15,46 +17,89 @@ export const CutLayout = (props) => {
if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false) if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false)
}) })
const draft = props.draft
const isImperial = props.gist.units === 'imperial' const isImperial = props.gist.units === 'imperial'
const gistSettings = props.gist?._state?.layout?.forCutting?.fabric || {}
// add the pages plugin to the draft const [patternProps, setPatternProps] = useState(undefined)
const layoutSettings = { const [cutFabrics, setCutFabrics] = useState(['fabric'])
sheetWidth: gistSettings.sheetWidth || measurementAsMm(isImperial ? 54 : 120, props.gist.units), const [draft, setDraft] = useState()
sheetHeight: const [cutFabric, setCutFabric] = useState('fabric')
gistSettings.sheetHeight || measurementAsMm(isImperial ? 36 : 100, props.gist.units),
} const gistSettings = get(props.gist, ['_state', 'layout', 'forCutting', 'fabric', cutFabric])
draft.use(fabricPlugin(layoutSettings)) const sheetWidth =
draft.use(pluginCutlist) gistSettings?.sheetWidth || measurementAsMm(isImperial ? 54 : 120, props.gist.units)
draft.use(cutLayoutPlugin) const gist = props.gist
const sheetHeight = measurementAsMm(isImperial ? 36 : 100, props.gist.units)
useEffect(() => {
try {
// get the appropriate layout for the view
const layout = gist.layouts?.[gist._state.view]?.[cutFabric] || gist.layout || true
// hand it separately to the design
const draft = new props.design({ ...gist, layout })
// add the pages plugin to the draft
const layoutSettings = {
sheetWidth,
sheetHeight,
}
draft.use(fabricPlugin(layoutSettings))
draft.use(pluginCutlist)
draft.use(cutLayoutPlugin(cutFabric))
// draft the pattern
draft.draft()
setPatternProps(draft.getRenderProps())
const cutList = draft.setStores[0].get('cutlist')
const cf = ['fabric']
for (const partName in cutList) {
for (const matName in cutList[partName].materials) {
if (!cf.includes(matName)) cf.push(matName)
}
}
setCutFabrics(cf)
} catch (err) {
console.log(err, props.gist)
}
}, [cutFabric, isImperial, gist])
let patternProps
try {
// draft the pattern
draft.draft()
patternProps = draft.getRenderProps()
} catch (err) {
console.log(err, props.gist)
}
const bgProps = { fill: 'url(#page)' } const bgProps = { fill: 'url(#page)' }
let name = props.design.designConfig.data.name let name = props.design.designConfig.data.name
name = name.replace('@freesewing/', '') name = name.replace('@freesewing/', '')
return (
return patternProps ? (
<div> <div>
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2> <h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2>
<CutLayoutSettings {...props} patternProps={patternProps} /> <CutLayoutSettings
<Draft {...{ ...props, patternProps, cutFabric, sheetWidth }}
draft={draft}
gist={props.gist}
updateGist={props.updateGist}
patternProps={patternProps} patternProps={patternProps}
bgProps={bgProps} cutFabric={cutFabric}
gistReady={props.gistReady}
layoutPart="fabric"
layoutType="cuttingLayout"
/> />
<div className="my-4">
<div className="tabs">
{cutFabrics.map((title) => (
<button
key={title}
className={`text-xl font-bold capitalize tab tab-bordered grow ${
cutFabric === title ? 'tab-active' : ''
}`}
onClick={() => setCutFabric(title)}
>
{title}
</button>
))}
</div>
<Draft
draft={draft}
gist={props.gist}
updateGist={props.updateGist}
patternProps={patternProps}
bgProps={bgProps}
gistReady={props.gistReady}
layoutPart="fabric"
layoutType={['cuttingLayout', cutFabric]}
/>
</div>
</div> </div>
) ) : null
} }

View file

@ -1,70 +1,80 @@
const prefix = 'mirroredOnFold' const prefix = 'mirroredOnFold'
const redraft = ({ part }) => part const redraft = ({ part }) => part
export const cutLayoutPlugin = { export const cutLayoutPlugin = function (material) {
hooks: { return {
postPartDraft: (pattern) => { hooks: {
const partCutlist = pattern.setStores[pattern.activeSet].get(['cutlist', pattern.activePart]) postPartDraft: (pattern) => {
if (!partCutlist) return if (pattern.activePart.startsWith('cut.') || pattern.activePart === 'fabric') return
const { macro } = pattern.parts[pattern.activeSet][pattern.activePart].shorthand() const partCutlist = pattern.setStores[pattern.activeSet].get([
if (partCutlist.cutOnFold) macro('mirrorOnFold', { fold: partCutlist.cutOnFold }) 'cutlist',
pattern.activePart,
])
if (partCutlist.materials) { if (!partCutlist?.materials?.[material] && material !== 'fabric') {
for (const material in partCutlist.materials) { pattern.parts[pattern.activeSet][pattern.activePart].hide()
for (var i = 1; i < partCutlist.materials[material]; i++) { return
pattern.addPart({
name: `${pattern.activePart}_${material}_${i}`,
from: pattern.activePart,
draft: redraft,
})
}
} }
}
if (partCutlist?.cutOnFold) {
const { macro } = pattern.parts[pattern.activeSet][pattern.activePart].shorthand()
macro('mirrorOnFold', { fold: partCutlist.cutOnFold })
}
for (var i = 1; i < partCutlist?.materials?.[material].cut; i++) {
const dupPartName = `cut.${pattern.activePart}.${material}_${i + 1}`
pattern.addPart({
name: dupPartName,
from: pattern.config.parts[pattern.activePart],
draft: redraft,
})
}
},
}, },
}, macros: {
macros: { mirrorOnFold: ({ fold }, { paths, snippets, utils, macro, points }) => {
mirrorOnFold: ({ fold }, { paths, snippets, utils, macro, points }) => { const mirrorPaths = []
const mirrorPaths = [] for (const p in paths) {
for (const p in paths) { if (!paths[p].hidden && !p.startsWith(prefix)) mirrorPaths.push(paths[p])
if (!paths[p].hidden && !p.startsWith(prefix)) mirrorPaths.push(paths[p]) }
}
const mirrorPoints = [] const mirrorPoints = []
const snippetsByType = {} const snippetsByType = {}
for (var s in snippets) { for (var s in snippets) {
const snip = snippets[s] const snip = snippets[s]
if (['logo'].indexOf(snip.def) > -1) continue if (['logo'].indexOf(snip.def) > -1) continue
snippetsByType[snip.def] = snippetsByType[snip.def] || [] snippetsByType[snip.def] = snippetsByType[snip.def] || []
mirrorPoints.push(snip.anchor) mirrorPoints.push(snip.anchor)
for (var pName in points) { for (var pName in points) {
if (points[pName] === snip.anchor) { if (points[pName] === snip.anchor) {
snippetsByType[snip.def].push(prefix + utils.capitalize(pName)) snippetsByType[snip.def].push(prefix + utils.capitalize(pName))
break break
}
} }
} }
}
let unnamed = 0 let unnamed = 0
macro('mirror', { macro('mirror', {
paths: Object.values(mirrorPaths), paths: Object.values(mirrorPaths),
points: mirrorPoints, points: mirrorPoints,
mirror: fold, mirror: fold,
prefix, prefix,
nameFormat: (path) => { nameFormat: (path) => {
unnamed++ unnamed++
return `${prefix}_${unnamed}` return `${prefix}_${unnamed}`
}, },
})
for (var def in snippetsByType) {
macro('sprinkle', {
snippet: def,
on: snippetsByType[def],
}) })
}
for (var def in snippetsByType) {
macro('sprinkle', {
snippet: def,
on: snippetsByType[def],
})
}
},
}, },
}, }
} }

View file

@ -1,39 +1,32 @@
import { useMemo, useEffect, useState, useCallback } from 'react' import { useMemo, useEffect, useState, useCallback, useRef } from 'react'
import { ClearIcon } from 'shared/components/icons.mjs' import { ClearIcon, PageIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { formatFraction128, measurementAsMm, round, formatMm } from 'shared/utils.mjs' import { formatFraction128, measurementAsMm, round, formatMm } from 'shared/utils.mjs'
import get from 'lodash.get'
const FabricSizer = ({ gist, updateGist }) => { const FabricSizer = ({ gist, updateGist, cutFabric, sheetWidth }) => {
const { t } = useTranslation(['workbench']) const { t } = useTranslation(['workbench'])
const [val, setVal] = useState(500)
useEffect(() => {
setVal(formatMm(gist._state?.layout?.forCutting?.fabric.sheetWidth || 500, gist.units, 'none'))
}, [gist])
const setFabricWidth = (width) => {}
let val = formatMm(sheetWidth, gist.units, 'none')
// onChange // onChange
const update = useCallback( const update = (evt) => {
(evt) => { evt.stopPropagation()
evt.stopPropagation() let evtVal = evt.target.value
let evtVal = evt.target.value // set Val immediately so that the input reflects it
// set Val immediately so that the input reflects it val = evtVal
setVal(evtVal)
let useVal = measurementAsMm(evtVal, gist.units) let useVal = measurementAsMm(evtVal, gist.units)
// only set to the gist if it's valid // only set to the gist if it's valid
if (!isNaN(useVal)) { if (!isNaN(useVal)) {
updateGist(['_state', 'layout', 'forCutting', 'fabric', 'sheetWidth'], useVal) updateGist(['_state', 'layout', 'forCutting', 'fabric', cutFabric, 'sheetWidth'], useVal)
} }
}, }
[gist.units]
)
return ( return (
<div className="flex gap-4"> <div className="flex gap-4 px-0 font-bold items-center">
<div className="form-control mb-2 flex flex-row" key="wrap-fabricWidth"> <div className="form-control mb-2 flex flex-row" key="wrap-fabricWidth">
<label className="input-group input-group-xs"> <label className="input-group input-group-xs">
<span className="label-text font-bold">{t('fabricWidth')}</span> <span className="label-text font-bold">{`${t(cutFabric)} ${t('width')}`}</span>
<input <input
key="input-fabricWidth" key="input-fabricWidth"
type="text" type="text"
@ -68,30 +61,33 @@ const useFabricLength = (isImperial, height) => {
return `${count}${isImperial ? 'yds' : 'm'}` return `${count}${isImperial ? 'yds' : 'm'}`
} }
export const CutLayoutSettings = ({ gist, patternProps, unsetGist, updateGist }) => { export const CutLayoutSettings = ({
gist,
patternProps,
unsetGist,
updateGist,
cutFabric,
sheetWidth,
}) => {
const { t } = useTranslation(['workbench']) const { t } = useTranslation(['workbench'])
const fabricLength = useFabricLength(gist.units === 'imperial', patternProps.height) const fabricLength = useFabricLength(gist.units === 'imperial', patternProps.height)
return ( return (
<div> <div className="flex flex-row justify-between mb-2 items-baseline">
<div <FabricSizer {...{ gist, updateGist, cutFabric, sheetWidth }} />
className="flex flex-row justify-between <div>
mb-2" <PageIcon className="h-6 w-6 mr-2 inline align-middle" />
<span className="text-xl font-bold align-middle">{fabricLength}</span>
</div>
<button
key="reset"
onClick={() => unsetGist(['layouts', 'cuttingLayout', cutFabric])}
className="btn btn-primary btn-outline"
> >
<button <ClearIcon className="h-6 w-6 mr-2" />
key="reset" {t('reset')}
onClick={() => unsetGist(['layouts', 'cuttingLayout'])} </button>
className="btn btn-primary btn-outline"
>
<ClearIcon className="h-6 w-6 mr-2" />
{t('reset')}
</button>
</div>
<div className="flex flex-row font-bold items-center px-0">
<FabricSizer {...{ gist, updateGist }} />
<span className="ml-2">{fabricLength}</span>
</div>
</div> </div>
) )
} }

View file

@ -2,10 +2,10 @@ import { useRef } from 'react'
import { Stack } from './stack.mjs' import { Stack } from './stack.mjs'
import { SvgWrapper } from '../../draft/svg.mjs' import { SvgWrapper } from '../../draft/svg.mjs'
import { PartInner } from '../../draft/part.mjs' import { PartInner } from '../../draft/part.mjs'
import get from 'lodash.get'
export const Draft = (props) => { export const Draft = (props) => {
const { const {
draft,
patternProps, patternProps,
gist, gist,
updateGist, updateGist,
@ -18,7 +18,8 @@ export const Draft = (props) => {
const svgRef = useRef(null) const svgRef = useRef(null)
if (!patternProps) return null if (!patternProps) return null
// keep a fresh copy of the layout because we might manipulate it without saving to the gist // keep a fresh copy of the layout because we might manipulate it without saving to the gist
let layout = draft.settings[0].layouts?.[layoutType] || { const layoutPath = ['layouts'].concat(layoutType)
let layout = get(patternProps.settings[0], layoutPath) || {
...patternProps.autoLayout, ...patternProps.autoLayout,
width: patternProps.width, width: patternProps.width,
height: patternProps.height, height: patternProps.height,
@ -53,7 +54,7 @@ export const Draft = (props) => {
newLayout.topLeft = topLeft newLayout.topLeft = topLeft
if (history) { if (history) {
updateGist(['layouts', layoutType], newLayout, history) updateGist(layoutPath, newLayout, history)
} else { } 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 // 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 layout = newLayout

View file

@ -117,6 +117,9 @@ const basePlugin = ({
version, version,
hooks: { hooks: {
preLayout: function (pattern) { preLayout: function (pattern) {
if (!responsiveColumns) {
pattern.settings[0].maxWidth = sheetWidth
}
// Add part // Add part
pattern.addPart({ pattern.addPart({
name: partName, name: partName,

View file

@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next'
export const CoreSettingOnly = (props) => { export const CoreSettingOnly = (props) => {
const { t } = useTranslation(['app', 'parts', 'settings']) 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 partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
const togglePart = (part) => { const togglePart = (part) => {

View file

@ -21,7 +21,7 @@ export const OptionComponent = (props) => {
const Value = values[capitalize(type)] const Value = values[capitalize(type)]
try { try {
const hide = opt.hide && opt.hide(props.draft.settings) const hide = opt.hide && opt.hide(props.gist)
if (hide) return null if (hide) return null
} catch (e) { } catch (e) {

View file

@ -119,9 +119,7 @@ export const WorkbenchWrapper = ({
// Generate the draft here so we can pass it down to both the view and the options menu // Generate the draft here so we can pass it down to both the view and the options menu
let draft = false let draft = false
if ( if (['draft', 'logs', 'test', 'printingLayout'].indexOf(gist._state?.view) !== -1) {
['draft', 'logs', 'test', 'printingLayout', 'cuttingLayout'].indexOf(gist._state?.view) !== -1
) {
gist.embed = true gist.embed = true
// get the appropriate layout for the view // get the appropriate layout for the view
const layout = gist.layouts?.[gist._state.view] || gist.layout || true const layout = gist.layouts?.[gist._state.view] || gist.layout || true
@ -148,7 +146,6 @@ export const WorkbenchWrapper = ({
updateGist: updateWBGist, updateGist: updateWBGist,
unsetGist, unsetGist,
setGist, setGist,
draft,
feedback, feedback,
gistReady, gistReady,
showInfo: setPopup, showInfo: setPopup,
@ -178,7 +175,7 @@ export const WorkbenchWrapper = ({
<LayoutComponent {...layoutProps}> <LayoutComponent {...layoutProps}>
{messages} {messages}
<ErrorBoundary {...errorProps}> <ErrorBoundary {...errorProps}>
<Component {...componentProps} /> <Component {...componentProps} draft={draft} />
{popup && <Modal cancel={() => setPopup(false)}>{popup}</Modal>} {popup && <Modal cancel={() => setPopup(false)}>{popup}</Modal>}
</ErrorBoundary> </ErrorBoundary>
</LayoutComponent> </LayoutComponent>