rotate parts to be along grain
This commit is contained in:
parent
58b0633fd1
commit
a51665f30d
5 changed files with 204 additions and 123 deletions
|
@ -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
|
|
@ -10,96 +10,121 @@ import { useState, useEffect, useCallback, useRef } from 'react'
|
|||
import { Tabs } from 'shared/components/mdx/tabs.mjs'
|
||||
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 { gist, design, updateGist } = props
|
||||
|
||||
// disable xray
|
||||
useEffect(() => {
|
||||
if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false)
|
||||
if (gist?._state?.xray?.enabled) updateGist(['_state', 'xray', 'enabled'], false)
|
||||
})
|
||||
|
||||
const isImperial = props.gist.units === 'imperial'
|
||||
const fabricSettings = useFabricSettings(gist)
|
||||
const { draft, patternProps } = useFabricDraft(gist, design, fabricSettings)
|
||||
const fabricList = useFabricList(draft)
|
||||
|
||||
const [patternProps, setPatternProps] = useState(undefined)
|
||||
const [cutFabrics, setCutFabrics] = useState(['fabric'])
|
||||
const [draft, setDraft] = useState()
|
||||
const [cutFabric, setCutFabric] = useState('fabric')
|
||||
const setCutFabric = (newFabric) => {
|
||||
updateGist(activeFabricPath, newFabric)
|
||||
}
|
||||
|
||||
const gistSettings = get(props.gist, ['_state', 'layout', 'forCutting', 'fabric', cutFabric])
|
||||
const sheetWidth =
|
||||
gistSettings?.sheetWidth || measurementAsMm(isImperial ? 54 : 120, props.gist.units)
|
||||
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(cutLayoutPlugin(cutFabric))
|
||||
draft.use(pluginCutlist)
|
||||
draft.use(pluginFlip)
|
||||
// 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])
|
||||
|
||||
const bgProps = { fill: 'url(#page)' }
|
||||
|
||||
let name = props.design.designConfig.data.name
|
||||
let name = design.designConfig.data.name
|
||||
name = name.replace('@freesewing/', '')
|
||||
|
||||
const settingsProps = {
|
||||
gist,
|
||||
updateGist,
|
||||
patternProps,
|
||||
unsetGist: props.unsetGist,
|
||||
...fabricSettings,
|
||||
}
|
||||
|
||||
return patternProps ? (
|
||||
<div>
|
||||
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2>
|
||||
<CutLayoutSettings
|
||||
{...{ ...props, patternProps, cutFabric, sheetWidth }}
|
||||
patternProps={patternProps}
|
||||
cutFabric={cutFabric}
|
||||
/>
|
||||
<CutLayoutSettings {...settingsProps} />
|
||||
<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>
|
||||
{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)}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<Draft
|
||||
draft={draft}
|
||||
gist={props.gist}
|
||||
updateGist={props.updateGist}
|
||||
gist={gist}
|
||||
updateGist={updateGist}
|
||||
patternProps={patternProps}
|
||||
bgProps={bgProps}
|
||||
gistReady={props.gistReady}
|
||||
layoutPart="fabric"
|
||||
layoutType={['cuttingLayout', cutFabric]}
|
||||
layoutType={['cuttingLayout', fabricSettings.activeFabric]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,11 +6,14 @@ const redraftAndFlip = ({ part, macro }) => {
|
|||
return part
|
||||
}
|
||||
|
||||
export const cutLayoutPlugin = function (material) {
|
||||
const opTypes = ['to', 'cp1', 'cp2']
|
||||
export const cutLayoutPlugin = function (material, grainAngle) {
|
||||
return {
|
||||
hooks: {
|
||||
postPartDraft: (pattern) => {
|
||||
if (pattern.activePart.startsWith('cut.') || pattern.activePart === 'fabric') return
|
||||
const part = pattern.parts[pattern.activeSet][pattern.activePart]
|
||||
if (pattern.activePart.startsWith('cut.') || pattern.activePart === 'fabric' || part.hidden)
|
||||
return
|
||||
|
||||
const partCutlist = pattern.setStores[pattern.activeSet].get([
|
||||
'cutlist',
|
||||
|
@ -18,15 +21,23 @@ export const cutLayoutPlugin = function (material) {
|
|||
])
|
||||
|
||||
if (partCutlist?.materials ? !partCutlist.materials[material] : material !== 'fabric') {
|
||||
pattern.parts[pattern.activeSet][pattern.activePart].hide()
|
||||
part.hide()
|
||||
return
|
||||
}
|
||||
|
||||
const { macro } = part.shorthand()
|
||||
if (partCutlist?.cutOnFold) {
|
||||
const { macro } = pattern.parts[pattern.activeSet][pattern.activePart].shorthand()
|
||||
macro('mirrorOnFold', { fold: partCutlist.cutOnFold })
|
||||
}
|
||||
|
||||
if (
|
||||
partCutlist?.grain !== undefined &&
|
||||
partCutlist.grain !== grainAngle &&
|
||||
partCutlist.grain + 180 != grainAngle
|
||||
) {
|
||||
macro('rotateToGrain', { partGrain: partCutlist.grain, grainAngle })
|
||||
}
|
||||
|
||||
const matCutConfig = partCutlist?.materials?.[material]
|
||||
if (!matCutConfig) return
|
||||
|
||||
|
@ -83,6 +94,35 @@ export const cutLayoutPlugin = function (material) {
|
|||
})
|
||||
}
|
||||
},
|
||||
rotateToGrain: ({ partGrain, grainAngle }, { paths, snippets, Point, points }) => {
|
||||
const pivot = points.grainlineFrom || points.cutonfoldFrom || new Point(0, 0)
|
||||
|
||||
const toRotate = grainAngle - partGrain
|
||||
|
||||
for (const pathName in paths) {
|
||||
const path = paths[pathName]
|
||||
if (paths[pathName].hidden) continue
|
||||
|
||||
path.ops.forEach((op) => {
|
||||
opTypes.forEach((t) => {
|
||||
if (op[t]) op[t] = op[t].rotate(toRotate, pivot)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
for (const snippetName in snippets) {
|
||||
snippets[snippetName].anchor = snippets[snippetName].anchor.rotate(toRotate, pivot)
|
||||
}
|
||||
|
||||
for (const pointName in points) {
|
||||
const point = points[pointName]
|
||||
const pointAttrs = point.attributes
|
||||
if (Object.keys(pointAttrs.list).length) {
|
||||
points[pointName] = point.rotate(toRotate, pivot)
|
||||
points[pointName].attributes = pointAttrs.clone()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,29 @@
|
|||
import { useMemo, useEffect, useState, useCallback, useRef } from 'react'
|
||||
import { ClearIcon, PageIcon } from 'shared/components/icons.mjs'
|
||||
import { ClearIcon, IconWrapper } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { formatFraction128, measurementAsMm, round, formatMm } from 'shared/utils.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const FabricSizer = ({ gist, updateGist, cutFabric, sheetWidth }) => {
|
||||
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')
|
||||
|
@ -18,28 +37,45 @@ const FabricSizer = ({ gist, updateGist, cutFabric, sheetWidth }) => {
|
|||
let useVal = measurementAsMm(evtVal, gist.units)
|
||||
// only set to the gist if it's valid
|
||||
if (!isNaN(useVal)) {
|
||||
updateGist(['_state', 'layout', 'forCutting', 'fabric', cutFabric, 'sheetWidth'], useVal)
|
||||
updateGist(['_state', 'layout', 'forCutting', 'fabric', activeFabric, 'sheetWidth'], useVal)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 px-0 font-bold items-center">
|
||||
<div className="form-control mb-2 flex flex-row" key="wrap-fabricWidth">
|
||||
<label className="input-group input-group-xs">
|
||||
<span className="label-text font-bold">{`${t(cutFabric)} ${t('width')}`}</span>
|
||||
<input
|
||||
key="input-fabricWidth"
|
||||
type="text"
|
||||
className="input input-sm 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>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -66,23 +102,27 @@ export const CutLayoutSettings = ({
|
|||
patternProps,
|
||||
unsetGist,
|
||||
updateGist,
|
||||
cutFabric,
|
||||
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-baseline">
|
||||
<FabricSizer {...{ gist, updateGist, cutFabric, sheetWidth }} />
|
||||
<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>
|
||||
<PageIcon className="h-6 w-6 mr-2 inline align-middle" />
|
||||
<SheetIcon 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])}
|
||||
onClick={() => unsetGist(['layouts', 'cuttingLayout', activeFabric])}
|
||||
className="btn btn-primary btn-outline"
|
||||
>
|
||||
<ClearIcon className="h-6 w-6 mr-2" />
|
||||
|
|
|
@ -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.0",
|
||||
"pdfkit": "0.13.0",
|
||||
"postcss-for": "2.1.1",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue