feat: add WALL_CABLE electrical type and room outlet/switch count stats
Add wall cable item — a bare cable exit from the wall for direct consumer connection without an outlet. Includes 2D symbol (circle + cable stub), 3D mesh (round plate + protruding cable), and palette entry. Also add outlet and switch count metrics to the room info section in the properties panel.
This commit is contained in:
@@ -242,6 +242,8 @@
|
|||||||
"properties.curtainLeftOpen": "Left open",
|
"properties.curtainLeftOpen": "Left open",
|
||||||
"properties.curtainRightOpen": "Right open",
|
"properties.curtainRightOpen": "Right open",
|
||||||
"properties.curtainFabricColor": "Fabric color",
|
"properties.curtainFabricColor": "Fabric color",
|
||||||
|
"properties.outletCountStat": "Outlets",
|
||||||
|
"properties.switchCountStat": "Switches",
|
||||||
"properties.outletWidth": "Outlet width",
|
"properties.outletWidth": "Outlet width",
|
||||||
"properties.outletHeight": "Outlet height",
|
"properties.outletHeight": "Outlet height",
|
||||||
"properties.outletCount": "Count",
|
"properties.outletCount": "Count",
|
||||||
@@ -293,6 +295,7 @@
|
|||||||
"electrical.junction": "Junction",
|
"electrical.junction": "Junction",
|
||||||
"electrical.lights": "Lights",
|
"electrical.lights": "Lights",
|
||||||
"electrical.cable": "Cable",
|
"electrical.cable": "Cable",
|
||||||
|
"electrical.wallCable": "Wall Cable",
|
||||||
|
|
||||||
"furniture.title": "Furniture",
|
"furniture.title": "Furniture",
|
||||||
"furniture.searchPlaceholder": "Search furniture\u2026",
|
"furniture.searchPlaceholder": "Search furniture\u2026",
|
||||||
|
|||||||
@@ -245,6 +245,8 @@
|
|||||||
"properties.curtainLeftOpen": "Левая створка",
|
"properties.curtainLeftOpen": "Левая створка",
|
||||||
"properties.curtainRightOpen": "Правая створка",
|
"properties.curtainRightOpen": "Правая створка",
|
||||||
"properties.curtainFabricColor": "Цвет ткани",
|
"properties.curtainFabricColor": "Цвет ткани",
|
||||||
|
"properties.outletCountStat": "Розетки",
|
||||||
|
"properties.switchCountStat": "Выключатели",
|
||||||
"properties.outletWidth": "Ширина розетки",
|
"properties.outletWidth": "Ширина розетки",
|
||||||
"properties.outletHeight": "Высота розетки",
|
"properties.outletHeight": "Высота розетки",
|
||||||
"properties.outletCount": "Количество",
|
"properties.outletCount": "Количество",
|
||||||
@@ -296,6 +298,7 @@
|
|||||||
"electrical.junction": "Распределительная коробка",
|
"electrical.junction": "Распределительная коробка",
|
||||||
"electrical.lights": "Освещение",
|
"electrical.lights": "Освещение",
|
||||||
"electrical.cable": "Кабель",
|
"electrical.cable": "Кабель",
|
||||||
|
"electrical.wallCable": "Кабель из стены",
|
||||||
|
|
||||||
"furniture.title": "Мебель",
|
"furniture.title": "Мебель",
|
||||||
"furniture.searchPlaceholder": "Поиск мебели\u2026",
|
"furniture.searchPlaceholder": "Поиск мебели\u2026",
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ export function PropertiesPanel() {
|
|||||||
)}
|
)}
|
||||||
<PropertyRow label={t('properties.wallHeight')} value={`${room.wallHeight}m`} />
|
<PropertyRow label={t('properties.wallHeight')} value={`${room.wallHeight}m`} />
|
||||||
<PropertyRow label={t('properties.plinthHeight')} value={`${Math.round(room.plinthHeight * 1000) / 10}cm`} />
|
<PropertyRow label={t('properties.plinthHeight')} value={`${Math.round(room.plinthHeight * 1000) / 10}cm`} />
|
||||||
|
<PropertyRow
|
||||||
|
label={t('properties.outletCountStat')}
|
||||||
|
value={String(electricalItems.filter((e) => e.type === 'OUTLET').reduce((sum, e) => sum + Math.max(1, e.count), 0))}
|
||||||
|
/>
|
||||||
|
<PropertyRow
|
||||||
|
label={t('properties.switchCountStat')}
|
||||||
|
value={String(electricalItems.filter((e) => e.type === 'SWITCH').length)}
|
||||||
|
/>
|
||||||
{/* Stretch ceiling drop (натяжной потолок). Stored in meters,
|
{/* Stretch ceiling drop (натяжной потолок). Stored in meters,
|
||||||
edited in cm for ergonomics. 0 = disabled. */}
|
edited in cm for ergonomics. 0 = disabled. */}
|
||||||
<EditablePropertyRow
|
<EditablePropertyRow
|
||||||
|
|||||||
@@ -290,8 +290,27 @@ export function ProjectionElectrical({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
{item.type === 'WALL_CABLE' && (
|
||||||
|
<>
|
||||||
|
{/* Wall plate circle */}
|
||||||
|
<Circle
|
||||||
|
x={center.x}
|
||||||
|
y={center.y}
|
||||||
|
radius={half * 0.7}
|
||||||
|
fill={fillColor}
|
||||||
|
stroke={strokeColor}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
{/* Cable stub hanging down */}
|
||||||
|
<Line
|
||||||
|
points={[center.x, center.y + half * 0.7, center.x, center.y + half * 1.6]}
|
||||||
|
stroke="#555555"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{/* Fallback for other wall-mounted types */}
|
{/* Fallback for other wall-mounted types */}
|
||||||
{item.type !== 'OUTLET' && item.type !== 'SWITCH' && item.type !== 'LIGHT_WALL' && (
|
{item.type !== 'OUTLET' && item.type !== 'SWITCH' && item.type !== 'LIGHT_WALL' && item.type !== 'WALL_CABLE' && (
|
||||||
<Rect
|
<Rect
|
||||||
x={center.x - half}
|
x={center.x - half}
|
||||||
y={center.y - half}
|
y={center.y - half}
|
||||||
@@ -315,7 +334,9 @@ export function ProjectionElectrical({
|
|||||||
? 'OUT'
|
? 'OUT'
|
||||||
: item.type === 'SWITCH'
|
: item.type === 'SWITCH'
|
||||||
? 'SW'
|
? 'SW'
|
||||||
: 'WL'
|
: item.type === 'WALL_CABLE'
|
||||||
|
? 'CBL'
|
||||||
|
: 'WL'
|
||||||
}
|
}
|
||||||
align="center"
|
align="center"
|
||||||
fontSize={8}
|
fontSize={8}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const ELECTRICAL_SYMBOL_DEFS: readonly ElectricalSymbolDef[] = [
|
|||||||
{ type: 'LIGHT_CEILING', label: 'Ceiling Light', category: 'light', wallMounted: false, coverageRadius: 2.0 },
|
{ type: 'LIGHT_CEILING', label: 'Ceiling Light', category: 'light', wallMounted: false, coverageRadius: 2.0 },
|
||||||
{ type: 'LIGHT_WALL', label: 'Wall Light', category: 'light', wallMounted: true, coverageRadius: 1.5 },
|
{ type: 'LIGHT_WALL', label: 'Wall Light', category: 'light', wallMounted: true, coverageRadius: 1.5 },
|
||||||
{ type: 'CABLE_ROUTE', label: 'Cable Route', category: 'cable', wallMounted: false },
|
{ type: 'CABLE_ROUTE', label: 'Cable Route', category: 'cable', wallMounted: false },
|
||||||
|
{ type: 'WALL_CABLE', label: 'Wall Cable', category: 'cable', wallMounted: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Get the variant from an electrical item's metadata. Used by switches; outlets use `count`. */
|
/** Get the variant from an electrical item's metadata. Used by switches; outlets use `count`. */
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const ELECTRICAL_COLORS: Record<ElectricalType, string> = {
|
|||||||
LIGHT_CEILING: '#fff8dc',
|
LIGHT_CEILING: '#fff8dc',
|
||||||
LIGHT_WALL: '#fff8dc',
|
LIGHT_WALL: '#fff8dc',
|
||||||
CABLE_ROUTE: '#ff6b35',
|
CABLE_ROUTE: '#ff6b35',
|
||||||
|
WALL_CABLE: '#c0c0c0',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SELECTED_COLOR = '#6fa8dc';
|
const SELECTED_COLOR = '#6fa8dc';
|
||||||
@@ -375,6 +376,24 @@ function WallLightMesh({ color, style, cordLength, lampSize }: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Wall cable: round plate on wall with a cable stub protruding into the room. */
|
||||||
|
function WallCableMesh({ color }: { readonly color: string }) {
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
{/* Wall plate */}
|
||||||
|
<mesh castShadow>
|
||||||
|
<cylinderGeometry args={[0.03, 0.03, 0.01, 16]} />
|
||||||
|
<meshStandardMaterial color={color} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Cable stub protruding from the wall */}
|
||||||
|
<mesh position={[0, 0, 0.025]} rotation={[Math.PI / 2, 0, 0]} castShadow>
|
||||||
|
<cylinderGeometry args={[0.006, 0.006, 0.04, 8]} />
|
||||||
|
<meshStandardMaterial color="#333333" roughness={0.7} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Cable route: small orange marker */
|
/** Cable route: small orange marker */
|
||||||
function CableRouteMesh({ color }: { readonly color: string }) {
|
function CableRouteMesh({ color }: { readonly color: string }) {
|
||||||
return (
|
return (
|
||||||
@@ -513,6 +532,7 @@ export function ElectricalMeshWithHeight({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === 'CABLE_ROUTE' && <CableRouteMesh color={color} />}
|
{item.type === 'CABLE_ROUTE' && <CableRouteMesh color={color} />}
|
||||||
|
{item.type === 'WALL_CABLE' && <WallCableMesh color={color} />}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ export const ELECTRICAL_TYPES = [
|
|||||||
'LIGHT_CEILING',
|
'LIGHT_CEILING',
|
||||||
'LIGHT_WALL',
|
'LIGHT_WALL',
|
||||||
'CABLE_ROUTE',
|
'CABLE_ROUTE',
|
||||||
|
'WALL_CABLE',
|
||||||
] as const;
|
] as const;
|
||||||
export type ElectricalType = (typeof ELECTRICAL_TYPES)[number];
|
export type ElectricalType = (typeof ELECTRICAL_TYPES)[number];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user