Simplify paint material compatibility logic
This commit is contained in:
@@ -40,7 +40,6 @@ export {
|
|||||||
export { baseMaterial, glassMaterial } from './materials'
|
export { baseMaterial, glassMaterial } from './materials'
|
||||||
export {
|
export {
|
||||||
getCatalogMaterialById,
|
getCatalogMaterialById,
|
||||||
getMaterialCategoryTargets,
|
|
||||||
getLibraryMaterialIdFromRef,
|
getLibraryMaterialIdFromRef,
|
||||||
getMaterialPresetByRef,
|
getMaterialPresetByRef,
|
||||||
getMaterialsForCategory,
|
getMaterialsForCategory,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
type MaterialPresetPayload,
|
type MaterialPresetPayload,
|
||||||
type MaterialTarget,
|
|
||||||
MaterialTarget as MaterialTargetSchema,
|
|
||||||
} from './schema/material'
|
} from './schema/material'
|
||||||
|
|
||||||
export type MaterialCatalogItem = {
|
export type MaterialCatalogItem = {
|
||||||
@@ -24,40 +22,6 @@ export const MATERIAL_CATEGORIES = [
|
|||||||
] as const
|
] as const
|
||||||
export type MaterialCategory = (typeof MATERIAL_CATEGORIES)[number]
|
export type MaterialCategory = (typeof MATERIAL_CATEGORIES)[number]
|
||||||
|
|
||||||
export const MATERIAL_CATEGORY_TARGETS: Record<MaterialCategory, MaterialTarget[]> = {
|
|
||||||
wood: [
|
|
||||||
MaterialTargetSchema.enum.wall,
|
|
||||||
MaterialTargetSchema.enum.slab,
|
|
||||||
MaterialTargetSchema.enum.stair,
|
|
||||||
MaterialTargetSchema.enum['stair-segment'],
|
|
||||||
MaterialTargetSchema.enum.fence,
|
|
||||||
MaterialTargetSchema.enum.roof,
|
|
||||||
MaterialTargetSchema.enum['roof-segment'],
|
|
||||||
],
|
|
||||||
wallpaper: [MaterialTargetSchema.enum.wall],
|
|
||||||
parquet: [
|
|
||||||
MaterialTargetSchema.enum.slab,
|
|
||||||
MaterialTargetSchema.enum.stair,
|
|
||||||
MaterialTargetSchema.enum['stair-segment'],
|
|
||||||
],
|
|
||||||
granite: [MaterialTargetSchema.enum.slab],
|
|
||||||
marble: [
|
|
||||||
MaterialTargetSchema.enum.slab,
|
|
||||||
MaterialTargetSchema.enum.stair,
|
|
||||||
MaterialTargetSchema.enum['stair-segment'],
|
|
||||||
],
|
|
||||||
other: [
|
|
||||||
MaterialTargetSchema.enum.wall,
|
|
||||||
MaterialTargetSchema.enum.roof,
|
|
||||||
MaterialTargetSchema.enum['roof-segment'],
|
|
||||||
MaterialTargetSchema.enum.stair,
|
|
||||||
MaterialTargetSchema.enum['stair-segment'],
|
|
||||||
MaterialTargetSchema.enum.fence,
|
|
||||||
MaterialTargetSchema.enum.slab,
|
|
||||||
MaterialTargetSchema.enum.ceiling,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MATERIAL_CATALOG: MaterialCatalogItem[] = [
|
export const MATERIAL_CATALOG: MaterialCatalogItem[] = [
|
||||||
{
|
{
|
||||||
id: 'wall-wood1',
|
id: 'wall-wood1',
|
||||||
@@ -609,10 +573,6 @@ export function getMaterialsForCategory(category: MaterialCategory): MaterialCat
|
|||||||
return MATERIAL_CATALOG.filter((item) => item.category === category)
|
return MATERIAL_CATALOG.filter((item) => item.category === category)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMaterialCategoryTargets(category: MaterialCategory): MaterialTarget[] {
|
|
||||||
return MATERIAL_CATEGORY_TARGETS[category]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCatalogMaterialById(id?: string): MaterialCatalogItem | undefined {
|
export function getCatalogMaterialById(id?: string): MaterialCatalogItem | undefined {
|
||||||
if (!id) return undefined
|
if (!id) return undefined
|
||||||
return MATERIAL_CATALOG.find((item) => item.id === id)
|
return MATERIAL_CATALOG.find((item) => item.id === id)
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {
|
|||||||
buildStairSurfaceMaterialPatch,
|
buildStairSurfaceMaterialPatch,
|
||||||
buildWallSurfaceMaterialPatch,
|
buildWallSurfaceMaterialPatch,
|
||||||
hasActivePaintMaterial,
|
hasActivePaintMaterial,
|
||||||
isActivePaintMaterialCompatible,
|
|
||||||
resolveActivePaintMaterialFromSelection,
|
resolveActivePaintMaterialFromSelection,
|
||||||
} from '../../lib/material-paint'
|
} from '../../lib/material-paint'
|
||||||
import { sfxEmitter } from '../../lib/sfx-bus'
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
||||||
@@ -741,8 +740,7 @@ export const SelectionManager = () => {
|
|||||||
|
|
||||||
if (node.type === 'wall') {
|
if (node.type === 'wall') {
|
||||||
const role = resolveWallMaterialTarget(event as WallEvent)
|
const role = resolveWallMaterialTarget(event as WallEvent)
|
||||||
const compatible =
|
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
||||||
role !== null && isActivePaintMaterialCompatible(activePaintMaterial, 'wall')
|
|
||||||
return {
|
return {
|
||||||
key: `wall:${node.id}:${role ?? 'unsupported'}`,
|
key: `wall:${node.id}:${role ?? 'unsupported'}`,
|
||||||
hoveredId: node.id as AnyNodeId,
|
hoveredId: node.id as AnyNodeId,
|
||||||
@@ -783,8 +781,7 @@ export const SelectionManager = () => {
|
|||||||
if (!roofNode || roofNode.type !== 'roof') return null
|
if (!roofNode || roofNode.type !== 'roof') return null
|
||||||
|
|
||||||
const role = resolveRoofMaterialTarget(event as RoofEvent | RoofSegmentEvent)
|
const role = resolveRoofMaterialTarget(event as RoofEvent | RoofSegmentEvent)
|
||||||
const compatible =
|
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
||||||
role !== null && isActivePaintMaterialCompatible(activePaintMaterial, 'roof')
|
|
||||||
return {
|
return {
|
||||||
key: `roof:${roofNode.id}:${role ?? 'unsupported'}`,
|
key: `roof:${roofNode.id}:${role ?? 'unsupported'}`,
|
||||||
hoveredId: roofNode.id as AnyNodeId,
|
hoveredId: roofNode.id as AnyNodeId,
|
||||||
@@ -825,8 +822,7 @@ export const SelectionManager = () => {
|
|||||||
if (!stairNode || stairNode.type !== 'stair') return null
|
if (!stairNode || stairNode.type !== 'stair') return null
|
||||||
|
|
||||||
const role = resolveStairMaterialTarget(event as StairEvent | StairSegmentEvent)
|
const role = resolveStairMaterialTarget(event as StairEvent | StairSegmentEvent)
|
||||||
const compatible =
|
const compatible = role !== null && hasActivePaintMaterial(activePaintMaterial)
|
||||||
role !== null && isActivePaintMaterialCompatible(activePaintMaterial, 'stair')
|
|
||||||
return {
|
return {
|
||||||
key: `stair:${stairNode.id}:${role ?? 'unsupported'}`,
|
key: `stair:${stairNode.id}:${role ?? 'unsupported'}`,
|
||||||
hoveredId: stairNode.id as AnyNodeId,
|
hoveredId: stairNode.id as AnyNodeId,
|
||||||
@@ -858,13 +854,10 @@ export const SelectionManager = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'fence' || node.type === 'slab' || node.type === 'ceiling') {
|
if (node.type === 'fence' || node.type === 'slab' || node.type === 'ceiling') {
|
||||||
const target = node.type
|
const compatible = hasActivePaintMaterial(activePaintMaterial)
|
||||||
const compatible =
|
|
||||||
isActivePaintMaterialCompatible(activePaintMaterial, target) &&
|
|
||||||
hasActivePaintMaterial(activePaintMaterial)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: `${target}:${node.id}:surface`,
|
key: `${node.type}:${node.id}:surface`,
|
||||||
hoveredId: node.id as AnyNodeId,
|
hoveredId: node.id as AnyNodeId,
|
||||||
hoverMode: compatible ? 'paint-ready' : 'paint-disabled',
|
hoverMode: compatible ? 'paint-ready' : 'paint-disabled',
|
||||||
apply: compatible
|
apply: compatible
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ function PaintMaterialTray() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-[42rem] max-w-[calc(100vw-2rem)]">
|
<div className="w-[42rem] max-w-[calc(100vw-2rem)]">
|
||||||
<MaterialPicker
|
<MaterialPicker
|
||||||
onChange={(material, category) => {
|
onChange={(material) => {
|
||||||
setActivePaintMaterial({ material, category, sourceTarget: activePaintTarget })
|
setActivePaintMaterial({ material, sourceTarget: activePaintTarget })
|
||||||
}}
|
}}
|
||||||
onSelectMaterialPreset={(materialPreset, category) => {
|
onSelectMaterialPreset={(materialPreset) => {
|
||||||
setActivePaintMaterial({ materialPreset, category, sourceTarget: activePaintTarget })
|
setActivePaintMaterial({ materialPreset, sourceTarget: activePaintTarget })
|
||||||
}}
|
}}
|
||||||
selectedMaterialPreset={activePaintMaterial?.materialPreset}
|
selectedMaterialPreset={activePaintMaterial?.materialPreset}
|
||||||
value={activePaintMaterial?.material}
|
value={activePaintMaterial?.material}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
getMaterialsForCategory,
|
getMaterialsForCategory,
|
||||||
MATERIAL_CATEGORIES,
|
MATERIAL_CATEGORIES,
|
||||||
toLibraryMaterialRef,
|
toLibraryMaterialRef,
|
||||||
type MaterialCategory,
|
|
||||||
type MaterialSchema,
|
type MaterialSchema,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
@@ -15,8 +14,8 @@ import useEditor from '../../../store/use-editor'
|
|||||||
type MaterialPickerProps = {
|
type MaterialPickerProps = {
|
||||||
value?: MaterialSchema
|
value?: MaterialSchema
|
||||||
selectedMaterialPreset?: string
|
selectedMaterialPreset?: string
|
||||||
onChange?: (material: MaterialSchema, category: MaterialCategory) => void
|
onChange?: (material: MaterialSchema) => void
|
||||||
onSelectMaterialPreset?: (materialPreset: string, category: MaterialCategory) => void
|
onSelectMaterialPreset?: (materialPreset: string) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +28,9 @@ export function MaterialPicker({
|
|||||||
}: MaterialPickerProps) {
|
}: MaterialPickerProps) {
|
||||||
const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen)
|
const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen)
|
||||||
const [showCustom, setShowCustom] = useState<boolean>(!!value?.properties)
|
const [showCustom, setShowCustom] = useState<boolean>(!!value?.properties)
|
||||||
const [selectedCategory, setSelectedCategory] = useState<MaterialCategory>(MATERIAL_CATEGORIES[0])
|
const [selectedCategory, setSelectedCategory] = useState<(typeof MATERIAL_CATEGORIES)[number]>(
|
||||||
|
MATERIAL_CATEGORIES[0],
|
||||||
|
)
|
||||||
const catalogScrollRef = useRef<HTMLDivElement>(null)
|
const catalogScrollRef = useRef<HTMLDivElement>(null)
|
||||||
const categoryScrollRef = useRef<HTMLDivElement>(null)
|
const categoryScrollRef = useRef<HTMLDivElement>(null)
|
||||||
const catalogItems =
|
const catalogItems =
|
||||||
@@ -62,9 +63,7 @@ export function MaterialPicker({
|
|||||||
if (disabled) return
|
if (disabled) return
|
||||||
setShowCustom(false)
|
setShowCustom(false)
|
||||||
setPaintPanelOpen(false)
|
setPaintPanelOpen(false)
|
||||||
const category = getCatalogMaterialById(materialId)?.category
|
onSelectMaterialPreset?.(toLibraryMaterialRef(materialId))
|
||||||
if (!category) return
|
|
||||||
onSelectMaterialPreset?.(toLibraryMaterialRef(materialId), category)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -113,20 +112,17 @@ export function MaterialPicker({
|
|||||||
if (disabled) return
|
if (disabled) return
|
||||||
setShowCustom(true)
|
setShowCustom(true)
|
||||||
setPaintPanelOpen(true)
|
setPaintPanelOpen(true)
|
||||||
onChange?.(
|
onChange?.({
|
||||||
{
|
preset: 'custom',
|
||||||
preset: 'custom',
|
properties: {
|
||||||
properties: {
|
color: value?.properties?.color || '#ffffff',
|
||||||
color: value?.properties?.color || '#ffffff',
|
roughness: value?.properties?.roughness ?? 0.5,
|
||||||
roughness: value?.properties?.roughness ?? 0.5,
|
metalness: value?.properties?.metalness ?? 0,
|
||||||
metalness: value?.properties?.metalness ?? 0,
|
opacity: value?.properties?.opacity ?? 1,
|
||||||
opacity: value?.properties?.opacity ?? 1,
|
transparent: value?.properties?.transparent ?? false,
|
||||||
transparent: value?.properties?.transparent ?? false,
|
side: value?.properties?.side ?? 'front',
|
||||||
side: value?.properties?.side ?? 'front',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
'other',
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export function PaintPanel() {
|
|||||||
transparent: nextTransparent,
|
transparent: nextTransparent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: 'other',
|
|
||||||
sourceTarget: activePaintMaterial?.sourceTarget ?? activePaintTarget,
|
sourceTarget: activePaintMaterial?.sourceTarget ?? activePaintTarget,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import {
|
|||||||
getEffectiveStairSurfaceMaterial,
|
getEffectiveStairSurfaceMaterial,
|
||||||
getEffectiveWallSurfaceMaterial,
|
getEffectiveWallSurfaceMaterial,
|
||||||
getLibraryMaterialIdFromRef,
|
getLibraryMaterialIdFromRef,
|
||||||
getMaterialCategoryTargets,
|
|
||||||
type MaterialCategory,
|
|
||||||
type MaterialSchema,
|
type MaterialSchema,
|
||||||
type MaterialTarget,
|
type MaterialTarget,
|
||||||
type RoofNode,
|
type RoofNode,
|
||||||
@@ -31,7 +29,6 @@ export type SingleSurfaceMaterialRole = 'surface'
|
|||||||
export type ActivePaintMaterial = {
|
export type ActivePaintMaterial = {
|
||||||
material?: MaterialSchema
|
material?: MaterialSchema
|
||||||
materialPreset?: string
|
materialPreset?: string
|
||||||
category?: MaterialCategory
|
|
||||||
sourceTarget: PaintableMaterialTarget
|
sourceTarget: PaintableMaterialTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,34 +47,10 @@ function getCatalogEntryForActivePaintMaterial(material: ActivePaintMaterial | n
|
|||||||
return getCatalogMaterialById(catalogId)
|
return getCatalogMaterialById(catalogId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePaintMaterialCategory(
|
|
||||||
material: MaterialSchema | undefined,
|
|
||||||
materialPreset: string | undefined,
|
|
||||||
): MaterialCategory | undefined {
|
|
||||||
const catalogId = getLibraryMaterialIdFromRef(materialPreset) ?? material?.id ?? undefined
|
|
||||||
const catalogEntry = getCatalogMaterialById(catalogId)
|
|
||||||
if (catalogEntry?.category) return catalogEntry.category
|
|
||||||
if (material || materialPreset === undefined) return 'other'
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getActivePaintMaterialLabel(material: ActivePaintMaterial | null | undefined) {
|
export function getActivePaintMaterialLabel(material: ActivePaintMaterial | null | undefined) {
|
||||||
return getCatalogEntryForActivePaintMaterial(material)?.label ?? 'Custom'
|
return getCatalogEntryForActivePaintMaterial(material)?.label ?? 'Custom'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isActivePaintMaterialCompatible(
|
|
||||||
material: ActivePaintMaterial | null | undefined,
|
|
||||||
target: PaintableMaterialTarget,
|
|
||||||
) {
|
|
||||||
if (!hasActivePaintMaterial(material)) return false
|
|
||||||
|
|
||||||
const catalogEntry = getCatalogEntryForActivePaintMaterial(material)
|
|
||||||
const category = material?.category ?? catalogEntry?.category
|
|
||||||
if (!category) return false
|
|
||||||
|
|
||||||
return getMaterialCategoryTargets(category).includes(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildWallSurfaceMaterialPatch(
|
export function buildWallSurfaceMaterialPatch(
|
||||||
node: WallNode,
|
node: WallNode,
|
||||||
targetSide: WallSurfaceSide,
|
targetSide: WallSurfaceSide,
|
||||||
@@ -200,7 +173,6 @@ export function resolveActivePaintMaterialFromSelection(params: {
|
|||||||
? {
|
? {
|
||||||
material: surface.material,
|
material: surface.material,
|
||||||
materialPreset: surface.materialPreset,
|
materialPreset: surface.materialPreset,
|
||||||
category: resolvePaintMaterialCategory(surface.material, surface.materialPreset),
|
|
||||||
sourceTarget: 'wall',
|
sourceTarget: 'wall',
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
@@ -221,7 +193,6 @@ export function resolveActivePaintMaterialFromSelection(params: {
|
|||||||
? {
|
? {
|
||||||
material: surface.material,
|
material: surface.material,
|
||||||
materialPreset: surface.materialPreset,
|
materialPreset: surface.materialPreset,
|
||||||
category: resolvePaintMaterialCategory(surface.material, surface.materialPreset),
|
|
||||||
sourceTarget: 'roof',
|
sourceTarget: 'roof',
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
@@ -242,7 +213,6 @@ export function resolveActivePaintMaterialFromSelection(params: {
|
|||||||
? {
|
? {
|
||||||
material: surface.material,
|
material: surface.material,
|
||||||
materialPreset: surface.materialPreset,
|
materialPreset: surface.materialPreset,
|
||||||
category: resolvePaintMaterialCategory(surface.material, surface.materialPreset),
|
|
||||||
sourceTarget: 'stair',
|
sourceTarget: 'stair',
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
@@ -263,10 +233,6 @@ export function resolveActivePaintMaterialFromSelection(params: {
|
|||||||
? {
|
? {
|
||||||
material: selectedNode.material,
|
material: selectedNode.material,
|
||||||
materialPreset: selectedNode.materialPreset,
|
materialPreset: selectedNode.materialPreset,
|
||||||
category: resolvePaintMaterialCategory(
|
|
||||||
selectedNode.material,
|
|
||||||
selectedNode.materialPreset,
|
|
||||||
),
|
|
||||||
sourceTarget: target,
|
sourceTarget: target,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|||||||
Reference in New Issue
Block a user