1
0
Fork 0

rotate parts to be along grain

This commit is contained in:
Enoch Riese 2023-03-01 12:34:18 -06:00
parent 58b0633fd1
commit a51665f30d
5 changed files with 204 additions and 123 deletions

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

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

View file

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

View file

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

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.0",
"pdfkit": "0.13.0",
"postcss-for": "2.1.1",