From 7408b1feca2f452635a740976f156605aa614da9 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 11 Nov 2026 01:51:29 -0500 Subject: [PATCH] Rack: Sliger fronts in red, white and blue, and the UniFi brush panel From the rack photo on the wiki, the three Sligers have their coloured perforated fronts fitted, not the faceplate-off view: Front drawings "Sliger CX4712 4U, red/white/blue front" draw the hex-perforated panel (about 20 rows of holes, the drive cages faint behind it), black rack ears, the power button with Gandalf's status ring, and the USB recess. UniFi UACC-Rack-Panel-Brush-1U is drawn from Ubiquiti's product images: silver face, ear plates with rounded slots, a recessed chamfered frame and two rows of bristles meeting at a seam. Co-Authored-By: Claude Opus 5.5 --- README.md | 3 ++ cutsheet.py | 6 +++- static/fronts.js | 74 ++++++++++++++++++++++++++++++++++++++++++ static/style.css | 14 ++++++++ tests/test_cutsheet.py | 3 +- 5 files changed, 98 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 993e0f3..b3dcceb 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,9 @@ The rack front, drawn from the rack units set on cutsheet items, top first. with a DAC attached; its round dot is Gandalf's link mark for enp1s0 (the slot is PCI bus 1), not an LED on the card. UniFi desk switches (Flex Mini, Flex) on a shelf are drawn with their live faceplate at their real width. Anything else is a labelled box. +- **More drawn fronts**: the Sliger CX4712 with its red, white or blue perforated front fitted (as in + the rack photo, drive cages faint behind the mesh), and the UniFi brush panel + (UACC-Rack-Panel-Brush-1U, from Ubiquiti's product images). - **Gear resting on other gear**: a shelf item with Front drawing "No shelf: gear resting on what is below" draws no tray, so a switch lying on top of the NVR is a "shelf" in the unit above the NVR whose contents sit directly on it. diff --git a/cutsheet.py b/cutsheet.py index 9111bfd..9921c94 100644 --- a/cutsheet.py +++ b/cutsheet.py @@ -37,7 +37,11 @@ CHASSIS = { 'uacc-patch-24': 'UniFi Patch Panel Blank 24 (UACC-Rack-Panel-Patch-Blank-24)', 'usw-pro-max-24-poe': 'UniFi Switch Pro Max 24 PoE (until UniFi reports it)', 'usw-pro-max-48-poe': 'UniFi Switch Pro Max 48 PoE (until UniFi reports it)', - 'sliger-cx4712': 'Sliger CX4712 4U (10-bay)', + 'sliger-cx4712': 'Sliger CX4712 4U (10-bay, faceplate off)', + 'sliger-cx4712-red': 'Sliger CX4712 4U, red front', + 'sliger-cx4712-white': 'Sliger CX4712 4U, white front', + 'sliger-cx4712-blue': 'Sliger CX4712 4U, blue front', + 'uacc-brush-1u': 'UniFi Rack Panel Brush 1U (UACC-Rack-Panel-Brush-1U)', 'rosewill-rsv-l4500u': 'Rosewill RSV-L4500U 4U (15-bay)', 'zimaboard-832': 'IceWhale ZimaBoard 832 (shelf gear)', 'zimaboard-832-cx3': 'IceWhale ZimaBoard 832 + ConnectX-3 SFP+ in PCIe (shelf gear)', diff --git a/static/fronts.js b/static/fronts.js index f7a38b6..7fdfead 100644 --- a/static/fronts.js +++ b/static/fronts.js @@ -106,12 +106,86 @@ const RACK_FRONTS = { }, }, + // Sliger CX4712 with its coloured front fitted, as in the rack photo (wiki: rack-2026.jpg): + // a hex-perforated panel over most of the face, the drive cages faintly visible through it, + // black rack ears, and a power button over two USB ports at the top right. + 'sliger-cx4712-red': sligerFront('#c8161d', '#e9e5e0'), + 'sliger-cx4712-white': sligerFront('#eceae6', '#9aa0a6'), + 'sliger-cx4712-blue': sligerFront('#1f5fae', '#e9e5e0'), + + // Ubiquiti UACC-Rack-Panel-Brush-1U, from Ubiquiti's product images (store.ui.com, UACC-Rack-Panel + // variant Brush 1U): a light silver face with separate ear plates (two rounded slots each), a + // recessed chamfered frame around the opening (shadowed along the top, bright bevel along the + // bottom), and two rows of black bristles meeting at a seam along the middle. + 'uacc-brush-1u': { + label: 'UniFi Rack Panel Brush 1U (UACC-Rack-Panel-Brush-1U)', units: 1, width: 1100, height: 105, + draw() { + // Bristle tufts: thin strands of varying shade, deterministic so the drawing never flickers + const strands = (y0, y1, seed) => Array.from({ length: 300 }, (_, i) => { + const n = (i * 7919 + seed * 104729) % 97; + return ``; + }).join(''); + const slot = x => ` + `; + return ` + + + + + + + + + ${slot(16)}${slot(1084)} + + + + + ${strands(31, 53, 1)}${strands(54, 76, 2)} + `; + }, + }, + // Generic servers, for gear without a specific drawing 'server-1u': genericServer(1, 4), 'server-2u': genericServer(2, 12), 'server-4u': genericServer(4, 24), }; +function sligerFront(colour, hole) { + const W = 1100, H = 420; + // Hex perforations: a pattern of hexagons cut out of the panel through a mask + const hex = (cx, cy, r) => Array.from({ length: 6 }, (_, i) => { + const a = Math.PI / 6 + i * Math.PI / 3; + return `${(cx + r * Math.cos(a)).toFixed(1)},${(cy + r * Math.sin(a)).toFixed(1)}`; + }).join(' '); + // Hole size and pitch from the rack photo: about 20 rows of holes over the panel's height + const r = 6.9, dx = 18, dy = 15.6; + const holes = []; + for (let row = 0, y = 46; y < H - 34; row++, y += dy) { + for (let x = 82 + (row % 2 ? dx / 2 : 0); x < 925; x += dx) holes.push(``); + } + const bays = Array.from({ length: 10 }, (_, i) => ``).join(''); + // The cages show only faintly through the mesh + return { + label: 'Sliger CX4712 4U (10-bay)', units: 4, width: W, height: H, + draw({ status }) { + const id = `sl-${colour.slice(1)}`; + return `${holes.join('')} + ${bays} + + + + + + + + ${usbA(977, 146)}${usbA(977, 168)}${usbC(980, 192)}`; + }, + }; +} + function genericServer(units, bays) { const H = 105 * units; return { diff --git a/static/style.css b/static/style.css index 0313b95..4cbc1b2 100644 --- a/static/style.css +++ b/static/style.css @@ -1630,3 +1630,17 @@ body.fp-marks-off .fp-svg .fp-mark { display: none; } .cx-tab { fill: none; stroke: #c9ccd0; stroke-width: 9; stroke-linejoin: round; } .cx-link { stroke: #101418; stroke-width: 3; } .fp-speedbar { fill: #b3b5ba; } +.rf-sl-inside { fill: #141619; } +.rf-sl-ear { fill: #17181a; } +.rf-sl-edge { fill: none; stroke: rgba(0,0,0,.35); stroke-width: 2; } +.rf-sl-button { fill: #c9ccd0; stroke: #8a8e93; stroke-width: 2; } +.rf-sl-bay { opacity: .45; } +.rf-sl-io { fill: #0e0f11; } +.ua-ear-plate { fill: #d3d5d8; stroke: #b2b4b8; stroke-width: 1; } +.ua-ear-seam { fill: #9fa1a6; } +.ua-ear-slot { fill: #2a2c30; } +.ub-frame { fill: #bfc1c5; stroke: #a5a7ac; stroke-width: 1; } +.ub-frame-shadow { fill: #8c8e93; } +.ub-frame-bevel { fill: #f1f2f3; } +.ub-opening { fill: #0d0e10; } +.ub-seam { fill: #050506; } diff --git a/tests/test_cutsheet.py b/tests/test_cutsheet.py index e59349f..6d49bb2 100644 --- a/tests/test_cutsheet.py +++ b/tests/test_cutsheet.py @@ -370,7 +370,8 @@ class TestChassis: assert all(cs.CHASSIS.values()) assert {'sliger-cx4712', 'rosewill-rsv-l4500u', 'unvr-pro', 'uacc-patch-24', 'usw-pro-max-24-poe', 'usw-pro-max-48-poe', 'zimaboard-832', 'zimaboard-832-cx3', - 'resting'} <= set(cs.CHASSIS) + 'resting', 'uacc-brush-1u', 'sliger-cx4712-red', 'sliger-cx4712-white', + 'sliger-cx4712-blue'} <= set(cs.CHASSIS) class TestFacingAndSlots: