-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: add Fireplace node with animated procedural fire #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| /// <reference types="next" /> | ||
| /// <reference types="next/image-types/global" /> | ||
| import "./.next/dev/types/routes.d.ts"; | ||
| import "./.next/types/routes.d.ts"; | ||
|
|
||
| // NOTE: This file should not be edited | ||
| // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import dedent from 'dedent' | ||
| import { z } from 'zod' | ||
| import { BaseNode, nodeType, objectId } from '../base' | ||
| import { MaterialSchema } from '../material' | ||
|
|
||
| export const FireplaceMaterialRole = z.enum(['mantel', 'surround', 'hearth', 'firebox']) | ||
| export type FireplaceMaterialRole = z.infer<typeof FireplaceMaterialRole> | ||
|
|
||
| export const FireplaceStyle = z.enum(['wall', 'freestanding', 'corner', 'double-sided']) | ||
| export type FireplaceStyle = z.infer<typeof FireplaceStyle> | ||
|
|
||
| export const FireStyle = z.enum(['none', 'small', 'medium', 'large', 'roaring']) | ||
| export type FireStyle = z.infer<typeof FireStyle> | ||
|
|
||
| export const FireplaceNode = BaseNode.extend({ | ||
| id: objectId('fireplace'), | ||
| type: nodeType('fireplace'), | ||
|
|
||
| material: MaterialSchema.optional(), | ||
| materialPreset: z.string().optional(), | ||
| mantelMaterial: MaterialSchema.optional(), | ||
| mantelMaterialPreset: z.string().optional(), | ||
| hearthMaterial: MaterialSchema.optional(), | ||
| hearthMaterialPreset: z.string().optional(), | ||
| fireboxMaterial: MaterialSchema.optional(), | ||
| fireboxMaterialPreset: z.string().optional(), | ||
|
|
||
| position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]), | ||
| rotation: z.number().default(0), | ||
|
|
||
| style: FireplaceStyle.default('wall'), | ||
|
|
||
| width: z.number().min(0.6).max(4).default(1.5), | ||
| height: z.number().min(0.8).max(4).default(2.2), | ||
| depth: z.number().min(0.3).max(1.5).default(0.6), | ||
|
|
||
| fireboxWidth: z.number().min(0.3).max(3).default(0.9), | ||
| fireboxHeight: z.number().min(0.3).max(3).default(0.7), | ||
| fireboxDepth: z.number().min(0.2).max(1.2).default(0.4), | ||
| fireboxSillHeight: z.number().min(0).max(1.5).default(0.3), | ||
|
|
||
| mantelHeight: z.number().min(0.05).max(0.5).default(0.12), | ||
| mantelOverhang: z.number().min(0).max(0.3).default(0.08), | ||
| mantelThickness: z.number().min(0.03).max(0.2).default(0.06), | ||
| mantelWidth: z.number().min(0).max(1).default(0.1), | ||
|
|
||
| hearthDepth: z.number().min(0).max(0.8).default(0.35), | ||
| hearthHeight: z.number().min(0.02).max(0.2).default(0.05), | ||
| hearthWidth: z.number().min(0).max(1.5).default(0.15), | ||
|
|
||
| surroundWidth: z.number().min(0.05).max(0.5).default(0.15), | ||
| lintelHeight: z.number().min(0.05).max(0.4).default(0.12), | ||
|
|
||
| cornerAngle: z.number().min(30).max(90).default(45), | ||
|
|
||
| fire: FireStyle.default('medium'), | ||
| fireColor: z.enum(['orange', 'amber', 'blue', 'white']).default('orange'), | ||
| }).describe( | ||
| dedent` | ||
| Fireplace — an architectural fireplace with mantel, hearth, surround, | ||
| firebox, and optional animated fire. Hosted on a level; placed against | ||
| a wall (wall), free in space (freestanding), in a corner (corner), or | ||
| penetrating a wall (double-sided). The fire is a procedural particle | ||
| system rendered in the viewer — toggle with the \`fire\` field. | ||
| `, | ||
| ) | ||
|
|
||
| export type FireplaceNode = z.infer<typeof FireplaceNode> | ||
| export type FireplaceNodeId = FireplaceNode['id'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import type { | ||
| FireplaceNode as FireplaceNodeType, | ||
| HandleDescriptor, | ||
| NodeDefinition, | ||
| } from '@pascal-app/core' | ||
| import { buildFireplaceGeometry } from './geometry' | ||
| import { fireplaceParametrics } from './parametrics' | ||
| import { FireplaceNode } from './schema' | ||
|
|
||
| const SIDE_HANDLE_OFFSET = 0.18 | ||
| const HEIGHT_HANDLE_OFFSET = 0.22 | ||
| const ROTATE_CORNER_OFFSET = 0.32 | ||
| const ROTATE_RING_OFFSET = 0.04 | ||
| const MOVE_FRONT_OFFSET = 0.35 | ||
|
|
||
| const MIN_FP_WIDTH = 0.6 | ||
| const MIN_FP_DEPTH = 0.3 | ||
| const MIN_FP_HEIGHT = 0.8 | ||
|
|
||
| function fireplaceWidthHandle(): HandleDescriptor<FireplaceNodeType> { | ||
| return { | ||
| kind: 'linear-resize', | ||
| axis: 'x', | ||
| anchor: 'center', | ||
| min: MIN_FP_WIDTH, | ||
| currentValue: (n) => n.width, | ||
| apply: (_n, newValue) => ({ width: newValue }), | ||
| placement: { | ||
| position: (n) => [n.width / 2 + SIDE_HANDLE_OFFSET, n.height / 2, 0], | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| function fireplaceDepthHandle(): HandleDescriptor<FireplaceNodeType> { | ||
| return { | ||
| kind: 'linear-resize', | ||
| axis: 'z', | ||
| anchor: 'center', | ||
| min: MIN_FP_DEPTH, | ||
| currentValue: (n) => n.depth, | ||
| apply: (_n, newValue) => ({ depth: newValue }), | ||
| placement: { | ||
| position: (n) => [0, n.height / 2, n.depth / 2 + SIDE_HANDLE_OFFSET], | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| function fireplaceHeightHandle(): HandleDescriptor<FireplaceNodeType> { | ||
| return { | ||
| kind: 'linear-resize', | ||
| axis: 'y', | ||
| anchor: 'min', | ||
| min: MIN_FP_HEIGHT, | ||
| currentValue: (n) => n.height, | ||
| apply: (_n, newValue) => ({ height: newValue }), | ||
| placement: { | ||
| position: (n) => [0, n.height + HEIGHT_HANDLE_OFFSET, 0], | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| function fireplaceRotateHandle(): HandleDescriptor<FireplaceNodeType> { | ||
| return { | ||
| kind: 'arc-resize', | ||
| axis: 'angular', | ||
| shape: 'rotate', | ||
| apply: (initial, delta) => ({ | ||
| rotation: (initial.rotation ?? 0) - delta, | ||
| }), | ||
| placement: { | ||
| position: (n) => [n.width / 2, n.height / 2, n.depth / 2 + ROTATE_CORNER_OFFSET], | ||
| rotationY: () => -Math.PI / 4, | ||
| }, | ||
| decoration: { | ||
| kind: 'ring', | ||
| radius: (n) => Math.hypot(n.width / 2, n.depth / 2) + ROTATE_RING_OFFSET, | ||
| y: (n) => n.height / 2, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| function fireplaceMoveHandle(): HandleDescriptor<FireplaceNodeType> { | ||
| return { | ||
| kind: 'translate', | ||
| placement: { | ||
| position: (n) => [0, 0.02, n.depth / 2 + MOVE_FRONT_OFFSET], | ||
| }, | ||
| apply: (_n, pos) => ({ position: [pos[0], pos[1], pos[2]] }), | ||
| snapExtents: (n) => [n.width, n.depth], | ||
| } | ||
| } | ||
|
|
||
| function fireplaceHandles(_node: FireplaceNodeType): HandleDescriptor<FireplaceNodeType>[] { | ||
| return [ | ||
| fireplaceWidthHandle(), | ||
| fireplaceDepthHandle(), | ||
| fireplaceHeightHandle(), | ||
| fireplaceRotateHandle(), | ||
| fireplaceMoveHandle(), | ||
| ] | ||
| } | ||
|
|
||
| export const fireplaceDefinition: NodeDefinition<typeof FireplaceNode> = { | ||
| kind: 'fireplace', | ||
| snapProfile: 'item', | ||
| facingIndicator: true, | ||
| schemaVersion: 1, | ||
| schema: FireplaceNode, | ||
| category: 'furnish', | ||
| surfaceRole: 'furnishing', | ||
|
|
||
| defaults: () => ({ | ||
| object: 'node', | ||
| parentId: null, | ||
| visible: true, | ||
| metadata: {}, | ||
| position: [0, 0, 0], | ||
| rotation: 0, | ||
| style: 'wall', | ||
| width: 1.5, | ||
| height: 2.2, | ||
| depth: 0.6, | ||
| fireboxWidth: 0.9, | ||
| fireboxHeight: 0.7, | ||
| fireboxDepth: 0.4, | ||
| fireboxSillHeight: 0.3, | ||
| mantelHeight: 0.12, | ||
| mantelOverhang: 0.08, | ||
| mantelThickness: 0.06, | ||
| mantelWidth: 0.1, | ||
| hearthDepth: 0.35, | ||
| hearthHeight: 0.05, | ||
| hearthWidth: 0.15, | ||
| surroundWidth: 0.15, | ||
| lintelHeight: 0.12, | ||
| cornerAngle: 45, | ||
| fire: 'medium', | ||
| fireColor: 'orange', | ||
| }), | ||
|
|
||
| capabilities: { | ||
| movable: { axes: ['x', 'z'], gridSnap: true }, | ||
| rotatable: { | ||
| axes: ['y'], | ||
| snapAngles: [0, Math.PI / 4, Math.PI / 2, (3 * Math.PI) / 4, Math.PI], | ||
| }, | ||
| selectable: { hitVolume: 'bbox' }, | ||
| duplicable: true, | ||
| deletable: true, | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing slab elevation supportMedium Severity Unlike shelf, column, spawn, and item, the fireplace definition never declares Additional Locations (1)Reviewed by Cursor Bugbot for commit a8d3b37. Configure here. |
||
|
|
||
| handles: fireplaceHandles, | ||
| parametrics: fireplaceParametrics, | ||
| geometry: buildFireplaceGeometry, | ||
| renderer: { kind: 'parametric', module: () => import('./renderer') }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate fireplace geometry mountedHigh Severity The fireplace geometry is rendered twice. It's defined in Additional Locations (1)Reviewed by Cursor Bugbot for commit a8d3b37. Configure here. |
||
| tool: () => import('./tool'), | ||
| presentation: { | ||
| label: 'Fireplace', | ||
| icon: { kind: 'iconify', name: 'lucide:flame' }, | ||
| paletteSection: 'furnish', | ||
| }, | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated Next env change
Low Severity
This commit changes
apps/ifc-converter/next-env.d.ts(an auto-generated Next.js types stub marked “should not be edited”) with no connection to the Fireplace feature. It looks like an accidental local regen included in the PR.Reviewed by Cursor Bugbot for commit a8d3b37. Configure here.