diff --git a/packages/editor/src/components/ui/controls/material-paint-panel.tsx b/packages/editor/src/components/ui/controls/material-paint-panel.tsx index ca3f3b65..d2fd2d24 100644 --- a/packages/editor/src/components/ui/controls/material-paint-panel.tsx +++ b/packages/editor/src/components/ui/controls/material-paint-panel.tsx @@ -1,9 +1,15 @@ 'use client' -import { type AnyNodeId, useScene } from '@pascal-app/core' +import { + type AnyNodeId, + generateSceneMaterialId, + type SceneMaterialId, + toSceneMaterialRef, + useScene, +} from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { Eraser, RotateCcw } from 'lucide-react' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { buildResetSurfaceMaterialUpdates, resolvePaintTargetFromSelection, @@ -27,6 +33,8 @@ export function MaterialPaintPanel() { const setActivePaintTarget = useEditor((state) => state.setActivePaintTarget) const paintEraser = useEditor((state) => state.paintEraser) const setPaintEraser = useEditor((state) => state.setPaintEraser) + // Id of a just-created scene material whose inline editor should open on mount. + const [autoEditMaterialId, setAutoEditMaterialId] = useState(null) const selectedIds = useViewer((state) => state.selection.selectedIds) const nodes = useScene((state) => state.nodes) const materialCount = useScene((state) => Object.keys(state.materials).length) @@ -73,7 +81,18 @@ export function MaterialPaintPanel() { { - setActivePaintMaterial({ material, sourceTarget: activePaintTarget }) + // Custom-create: pre-create a scene material and select it as the + // brush via a `scene:` ref so painting stores the ref and edits to + // it propagate everywhere. The user edits it inline in the scene- + // material list below (auto-opened) — no separate right-side pane. + const id = generateSceneMaterialId() + const count = Object.keys(useScene.getState().materials).length + useScene.getState().addSceneMaterial({ id, name: `Material ${count + 1}`, material }) + setActivePaintMaterial({ + materialPreset: toSceneMaterialRef(id), + sourceTarget: activePaintTarget, + }) + setAutoEditMaterialId(id) }} onSelectMaterialPreset={(materialPreset) => { setActivePaintMaterial({ materialPreset, sourceTarget: activePaintTarget }) @@ -83,7 +102,7 @@ export function MaterialPaintPanel() { /> {materialCount > 0 ? ( - + ) : null} diff --git a/packages/editor/src/components/ui/controls/material-picker.tsx b/packages/editor/src/components/ui/controls/material-picker.tsx index 90487cb4..e0b5469f 100644 --- a/packages/editor/src/components/ui/controls/material-picker.tsx +++ b/packages/editor/src/components/ui/controls/material-picker.tsx @@ -11,7 +11,6 @@ import { } from '@pascal-app/core' import { useEffect, useState } from 'react' import { triggerSFX } from '../../../lib/sfx-bus' -import useEditor from '../../../store/use-editor' type MaterialPickerProps = { value?: MaterialSchema @@ -34,7 +33,6 @@ export function MaterialPicker({ onSelectMaterialPreset, disabled = false, }: MaterialPickerProps) { - const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen) const [showCustom, setShowCustom] = useState(!!value?.properties) const [selectedCategory, setSelectedCategory] = useState<(typeof MATERIAL_CATEGORIES)[number]>( MATERIAL_CATEGORIES[0], @@ -70,14 +68,14 @@ export function MaterialPicker({ const handleCatalogSelect = (materialId: string) => { if (disabled) return setShowCustom(false) - setPaintPanelOpen(false) onSelectMaterialPreset?.(toLibraryMaterialRef(materialId)) } + // Seed a new custom material from the current/forked colour and hand it to + // the host (MaterialPaintPanel), which pre-creates a scene material the user + // edits inline in the build pane — no separate right-side editor pane. const handleCustomOpen = () => { if (disabled) return - setShowCustom(true) - setPaintPanelOpen(true) const forkColor = selectedMaterialPreset ? (selectedCatalogEntry?.previewColor ?? '#ffffff') : '#ffffff' @@ -112,9 +110,6 @@ export function MaterialPicker({ if (showCustom) { setShowCustom(false) } - if (category !== 'colors') { - setPaintPanelOpen(false) - } }} type="button" > diff --git a/packages/editor/src/components/ui/controls/material-properties-editor.tsx b/packages/editor/src/components/ui/controls/material-properties-editor.tsx index f4e54e76..53340009 100644 --- a/packages/editor/src/components/ui/controls/material-properties-editor.tsx +++ b/packages/editor/src/components/ui/controls/material-properties-editor.tsx @@ -2,6 +2,7 @@ import type { MaterialProperties, MaterialSchema } from '@pascal-app/core' import { Input } from '../primitives/input' +import { SliderControl } from './slider-control' const DEFAULT_MATERIAL_PROPERTIES: MaterialProperties = { color: '#ffffff', @@ -44,7 +45,7 @@ export function MaterialPropertiesEditor({
updateMaterial({ color: e.target.value })} type="color" value={currentProps.color} @@ -56,68 +57,35 @@ export function MaterialPropertiesEditor({
-
-
- - - {currentProps.roughness.toFixed(2)} - -
- updateMaterial({ roughness: Number.parseFloat(e.target.value) })} - step={0.01} - type="range" - value={currentProps.roughness} - /> -
+ updateMaterial({ roughness: value })} + precision={2} + step={0.01} + value={currentProps.roughness} + /> -
-
- - - {currentProps.metalness.toFixed(2)} - -
- updateMaterial({ metalness: Number.parseFloat(e.target.value) })} - step={0.01} - type="range" - value={currentProps.metalness} - /> -
+ updateMaterial({ metalness: value })} + precision={2} + step={0.01} + value={currentProps.metalness} + /> -
-
- - - {currentProps.opacity.toFixed(2)} - -
- { - const opacity = Number.parseFloat(e.target.value) - updateMaterial({ opacity }, opacity < 1 || currentProps.transparent) - }} - step={0.01} - type="range" - value={currentProps.opacity} - /> -
+ updateMaterial({ opacity: value }, value < 1 || currentProps.transparent)} + precision={2} + step={0.01} + value={currentProps.opacity} + />
diff --git a/packages/editor/src/components/ui/panels/paint-panel.tsx b/packages/editor/src/components/ui/panels/paint-panel.tsx deleted file mode 100644 index cc097e76..00000000 --- a/packages/editor/src/components/ui/panels/paint-panel.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client' - -import useEditor from '../../../store/use-editor' -import { MaterialPropertiesEditor } from '../controls/material-properties-editor' -import { PanelSection } from '../controls/panel-section' -import { PanelWrapper } from './panel-wrapper' - -export function PaintPanel() { - const activePaintMaterial = useEditor((state) => state.activePaintMaterial) - const activePaintTarget = useEditor((state) => state.activePaintTarget) - const setActivePaintMaterial = useEditor((state) => state.setActivePaintMaterial) - const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen) - - const customMaterial = - activePaintMaterial?.material?.properties && !activePaintMaterial.materialPreset - ? activePaintMaterial.material - : null - - if (!customMaterial) return null - - return ( - setPaintPanelOpen(false)} title="Material" width={320}> - - - setActivePaintMaterial({ - material, - sourceTarget: activePaintMaterial?.sourceTarget ?? activePaintTarget, - }) - } - value={customMaterial} - /> - - - ) -} diff --git a/packages/editor/src/components/ui/panels/panel-manager.tsx b/packages/editor/src/components/ui/panels/panel-manager.tsx index 7ff45474..d16a6913 100644 --- a/packages/editor/src/components/ui/panels/panel-manager.tsx +++ b/packages/editor/src/components/ui/panels/panel-manager.tsx @@ -29,7 +29,6 @@ import useEditor from '../../../store/use-editor' import { MobilePanelSheet } from './mobile-panel-sheet' import { MobileSelectionBar } from './mobile-selection-bar' import { getNodeDisplay } from './node-display' -import { PaintPanel } from './paint-panel' import { ParametricInspector } from './parametric-inspector' import { ReferencePanel } from './reference-panel' @@ -174,9 +173,6 @@ export function PanelManager({ inspectorFooter }: { inspectorFooter?: React.Reac const selectedZoneId = useViewer((s) => s.selection.zoneId) const setSelection = useViewer((s) => s.setSelection) const selectedReferenceId = useEditor((s) => s.selectedReferenceId) - const isPaintPanelOpen = useEditor((s) => s.isPaintPanelOpen) - const mode = useEditor((s) => s.mode) - const activePaintMaterial = useEditor((s) => s.activePaintMaterial) // Only subscribe to the *type* of the single-selected node — string primitive // so we don't re-render on unrelated scene mutations. const selectedNodeType = useScene((s) => { @@ -208,15 +204,6 @@ export function PanelManager({ inspectorFooter }: { inspectorFooter?: React.Reac return } - if ( - isPaintPanelOpen && - mode === 'material-paint' && - activePaintMaterial?.material?.properties && - !activePaintMaterial.materialPreset - ) { - return - } - if (selectedZoneId && selectedIds.length === 0) { return ( MaterialPaintSelectionSnapshot hoveredPaintTarget: PaintableMaterialTarget | null setHoveredPaintTarget: (target: PaintableMaterialTarget | null) => void - isPaintPanelOpen: boolean - setPaintPanelOpen: (open: boolean) => void selectedReferenceId: string | null setSelectedReferenceId: (id: string | null) => void guideUi: Record @@ -854,8 +852,6 @@ const useEditor = create()( set((state) => state.hoveredPaintTarget === target ? state : { hoveredPaintTarget: target }, ), - isPaintPanelOpen: false, - setPaintPanelOpen: (open) => set({ isPaintPanelOpen: open }), selectedReferenceId: null, setSelectedReferenceId: (id) => set({ selectedReferenceId: id }), guideUi: {}, diff --git a/packages/nodes/src/item/paint.ts b/packages/nodes/src/item/paint.ts index f4ad1f6e..b8b191e7 100644 --- a/packages/nodes/src/item/paint.ts +++ b/packages/nodes/src/item/paint.ts @@ -169,7 +169,14 @@ function buildPreviewMaterial( materialPreset: string | undefined, ): Material | null { const shading = useViewer.getState().shading - if (materialPreset) return createMaterialFromPresetRef(materialPreset, shading) + if (materialPreset) { + const parsed = parseMaterialRef(materialPreset) + if (parsed?.kind === 'scene') { + const sceneMaterial = useScene.getState().materials[parsed.id as SceneMaterialId] + return sceneMaterial ? createMaterial(sceneMaterial.material, shading) : null + } + return createMaterialFromPresetRef(materialPreset, shading) + } if (material) return createMaterial(material, shading) return null } diff --git a/packages/nodes/src/shared/slot-paint.ts b/packages/nodes/src/shared/slot-paint.ts index c2d24066..3e46173a 100644 --- a/packages/nodes/src/shared/slot-paint.ts +++ b/packages/nodes/src/shared/slot-paint.ts @@ -134,7 +134,14 @@ export function buildSlotPreviewMaterial( materialPreset: string | undefined, ): Material | null { const shading = useViewer.getState().shading - if (materialPreset) return createMaterialFromPresetRef(materialPreset, shading) + if (materialPreset) { + const parsed = parseMaterialRef(materialPreset) + if (parsed?.kind === 'scene') { + const sceneMaterial = useScene.getState().materials[parsed.id as SceneMaterialId] + return sceneMaterial ? createMaterial(sceneMaterial.material, shading) : null + } + return createMaterialFromPresetRef(materialPreset, shading) + } if (material) return createMaterial(material, shading) return null }