Initial commit — Dossier Rhino 8 Plugin
OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac): - Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde), Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel), Treppen (gerade · L · Wendel mit Schrittmass-Validierung) - Live-Previews mit Step-Lines + Soll-Range-Clamping - Bidirektionale Selection-Sync zwischen Source-Linie und Volume - Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz - Layouts mit PDF-Export - Ausschnitte / Massstab / Override-Regeln - Petrol-Gruen Theme (Rapport-konform) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* rhinoBridge.js — Kommunikation React ↔ Rhino/Python
|
||||
* Lange Payloads werden in Chunks <700 Zeichen aufgeteilt (document.title-Limit).
|
||||
*/
|
||||
const CHUNK_SIZE = 700
|
||||
|
||||
let _queue = []
|
||||
let _busy = false
|
||||
|
||||
function _flush() {
|
||||
if (_busy || _queue.length === 0) return
|
||||
_busy = true
|
||||
document.title = 'RHINOMSG::' + _queue.shift()
|
||||
setTimeout(() => { _busy = false; _flush() }, 80)
|
||||
}
|
||||
|
||||
// Mac WKWebView -> .NET String-Bruecke kann mit non-ASCII-Bytes haengen
|
||||
// (Umlaute wie A-Umlaut/O-Umlaut/U-Umlaut -> "Unable to translate bytes from specified code page").
|
||||
// Vor Transport ASCII-eskapen, Python's json.loads dekodiert die \uXXXX wieder.
|
||||
const _NON_ASCII = /[^\x00-\x7f]/g
|
||||
function _asciiEscape(s) {
|
||||
return s.replace(_NON_ASCII, (c) =>
|
||||
'\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4))
|
||||
}
|
||||
|
||||
function send(type, payload = {}) {
|
||||
if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return }
|
||||
const json = _asciiEscape(JSON.stringify({ type, payload }))
|
||||
if (json.length <= CHUNK_SIZE) {
|
||||
_queue.push(json)
|
||||
} else {
|
||||
const total = Math.ceil(json.length / CHUNK_SIZE)
|
||||
for (let i = 0; i < total; i++) {
|
||||
_queue.push(_asciiEscape(JSON.stringify({
|
||||
_chunk: { i, n: total },
|
||||
d: json.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
|
||||
})))
|
||||
}
|
||||
}
|
||||
_flush()
|
||||
}
|
||||
|
||||
const _handlers = {}
|
||||
export function onMessage(type, handler) { _handlers[type] = handler }
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.onRhinoMessage = (data) => {
|
||||
const h = _handlers[data.type]
|
||||
if (h) h(data.payload)
|
||||
else console.log('[Bridge] ←', data.type, data.payload)
|
||||
}
|
||||
}
|
||||
|
||||
export function notifyReady() {
|
||||
const tryReady = () => {
|
||||
if (window.RHINO_MODE) send('READY', {})
|
||||
else setTimeout(tryReady, 50)
|
||||
}
|
||||
tryReady()
|
||||
}
|
||||
|
||||
export function applyAll(zeichnungsebenen, ebenen) {
|
||||
send('APPLY', { zeichnungsebenen, ebenen })
|
||||
}
|
||||
|
||||
export function setLayerVisibility(code, visible) {
|
||||
send('LAYER_VISIBILITY', { code, visible })
|
||||
}
|
||||
|
||||
export function setLayerLock(code, locked) {
|
||||
send('LAYER_LOCK', { code, locked })
|
||||
}
|
||||
|
||||
export function setLayerStyle(code, color, lw) {
|
||||
send('LAYER_STYLE', { code, color, lw })
|
||||
}
|
||||
|
||||
export function setActiveZeichnungsebene(z) {
|
||||
send('SET_ACTIVE', { id: z.id, name: z.name, isGeschoss: !!z.isGeschoss, okff: z.okff ?? null })
|
||||
}
|
||||
|
||||
export function setActiveEbene(code) {
|
||||
send('SET_ACTIVE_LAYER', { code })
|
||||
}
|
||||
|
||||
export function deleteEbene(code, moveTo) {
|
||||
send('DELETE_EBENE', { code, moveTo: moveTo || null })
|
||||
}
|
||||
|
||||
export function moveSelectionToEbene(code) {
|
||||
send('MOVE_SELECTION_TO_LAYER', { code })
|
||||
}
|
||||
|
||||
export function setClippingEnabled(enabled) {
|
||||
send('SET_CLIPPING', { enabled: !!enabled })
|
||||
}
|
||||
|
||||
// --- Ausschnitte ---
|
||||
export function listAusschnitte() { send('LIST', {}) }
|
||||
export function saveAusschnitt(name) { send('SAVE', { name }) }
|
||||
export function updateAusschnitt(id) { send('UPDATE', { id }) }
|
||||
export function restoreAusschnitt(id) { send('RESTORE', { id }) }
|
||||
export function applyAusschnittToDetail(id) { send('APPLY_TO_DETAIL', { id }) }
|
||||
export function renameAusschnitt(id, name) { send('RENAME', { id, name }) }
|
||||
export function deleteAusschnitt(id) { send('DELETE', { id }) }
|
||||
export function setAusschnittFolder(id, folder) { send('SET_FOLDER', { id, folder }) }
|
||||
export function setAusschnittScale(id, scale) { send('SET_SCALE', { id, scale }) }
|
||||
export function duplicateAusschnitt(id) { send('DUPLICATE', { id }) }
|
||||
export function addAusschnittFolder(name) { send('ADD_FOLDER', { name }) }
|
||||
export function removeAusschnittFolder(name) { send('REMOVE_FOLDER', { name }) }
|
||||
export function getAusschnittLayers(id) { send('GET_LAYERS', { id }) }
|
||||
export function updateAusschnittLayers(id, layers) { send('UPDATE_LAYERS', { id, layers }) }
|
||||
export function saveLayerPreset(name, layers) { send('SAVE_PRESET', { name, layers }) }
|
||||
export function deleteLayerPreset(name) { send('DELETE_PRESET', { name }) }
|
||||
|
||||
// --- Gestaltung-Panel ---
|
||||
export function requestSelection() {
|
||||
send('GET_SELECTION', {})
|
||||
}
|
||||
|
||||
export function setColorSource(source, color) {
|
||||
send('SET_COLOR_SOURCE', { source, color: color || null })
|
||||
}
|
||||
|
||||
export function setLwSource(source, lw) {
|
||||
send('SET_LW_SOURCE', { source, lw: lw == null ? null : Number(lw) })
|
||||
}
|
||||
|
||||
export function setLinetypeSource(source, name) {
|
||||
send('SET_LINETYPE_SOURCE', { source, name: name || null })
|
||||
}
|
||||
|
||||
export function setLinetypeScale(scale) {
|
||||
send('SET_LINETYPE_SCALE', { scale: scale == null ? null : Number(scale) })
|
||||
}
|
||||
|
||||
export function setFill(enabled, source, color, pattern, scale, rotation) {
|
||||
send('SET_FILL', {
|
||||
enabled: !!enabled,
|
||||
source: source || 'object',
|
||||
color: color || null,
|
||||
pattern: pattern || null,
|
||||
scale: scale == null ? null : Number(scale),
|
||||
rotation: rotation == null ? null : Number(rotation),
|
||||
})
|
||||
}
|
||||
|
||||
// --- Massstab-Panel ---
|
||||
export function requestMassstab() { send('REQUEST_STATE', {}) }
|
||||
export function setMassstab(ratio) { send('SET_SCALE', { ratio: Number(ratio) }) }
|
||||
export function zoomOneToOne() { send('ZOOM_ONE_TO_ONE', {}) }
|
||||
export function zoomExtents() { send('ZOOM_EXTENTS', {}) }
|
||||
export function zoomSelection() { send('ZOOM_SELECTION', {}) }
|
||||
export function setMassstabDpi(dpi) { send('SET_DPI', { dpi: Number(dpi) }) }
|
||||
export function detectMassstabDpi() { send('DETECT_DPI', {}) }
|
||||
export function setShowLineweights(on) { send('SET_LINEWEIGHTS',{ enabled: !!on }) }
|
||||
|
||||
// --- Werkzeuge-Panel ---
|
||||
export function runRhinoCommand(cmd) { send('RUN', { cmd }) }
|
||||
|
||||
// --- Oberleiste-Panel ---
|
||||
export function setView(view) { send('SET_VIEW', { view }) }
|
||||
export function setDisplayMode(name) { send('SET_DISPLAY_MODE', { name }) }
|
||||
export function toggleOrtho(on) { send('TOGGLE_ORTHO', { enabled: !!on }) }
|
||||
export function toggleGridSnap(on) { send('TOGGLE_GRID_SNAP', { enabled: !!on }) }
|
||||
export function toggleOsnap(on) { send('TOGGLE_OSNAP', { enabled: !!on }) }
|
||||
export function toggleOverrides(on) { send('TOGGLE_OVERRIDES', { enabled: !!on }) }
|
||||
export function setOverridesPreset(name) { send('SET_OVERRIDES_PRESET', { name: name || null }) }
|
||||
export function saveOverridesPreset(name) { send('SAVE_OVERRIDES_PRESET', { name }) }
|
||||
export function openOverridesPanel() { send('OPEN_OVERRIDES_PANEL', {}) }
|
||||
export function runCommand(cmd) { send('RUN_COMMAND', { cmd }) }
|
||||
export function sendKeys(text, enter) { send('SEND_KEYS', { text, enter: enter !== false }) }
|
||||
export function cancelCommand() { send('CANCEL_COMMAND', {}) }
|
||||
export function toggleRhinoCmdLine() { send('TOGGLE_RHINO_CMD_LINE', {}) }
|
||||
|
||||
// --- Overrides-Panel ---
|
||||
export function setOverridesEnabled(on) { send('SET_ENABLED', { enabled: !!on }) }
|
||||
export function addRule(rule) { send('ADD_RULE', { rule }) }
|
||||
export function updateRule(id, rule) { send('UPDATE_RULE', { id, rule }) }
|
||||
export function deleteRule(id) { send('DELETE_RULE', { id }) }
|
||||
export function reorderRules(order) { send('REORDER_RULES', { order }) }
|
||||
export function duplicateRule(id) { send('DUPLICATE_RULE', { id }) }
|
||||
export function reapplyOverrides() { send('REAPPLY', {}) }
|
||||
export function clearOverrideRules() { send('CLEAR_RULES', {}) }
|
||||
export function savePreset(name) { send('SAVE_PRESET', { name }) }
|
||||
export function loadPreset(name, mode) { send('LOAD_PRESET', { name, mode: mode || 'replace' }) }
|
||||
export function deletePreset(name) { send('DELETE_PRESET', { name }) }
|
||||
|
||||
// --- Dimensionen-Panel ---
|
||||
export function setRefPoint(x, y, z) { send('SET_REF_POINT', { x, y, z }) }
|
||||
export function setCoordSystem(mode) { send('SET_COORD_SYSTEM', { mode }) }
|
||||
export function setDimPosition(axis, value) { send('SET_POSITION', { axis, value }) }
|
||||
export function setDimDimension(axis, value) { send('SET_DIMENSION', { axis, value }) }
|
||||
export function setDimRotationZ(angle) { send('SET_ROTATION_Z', { angle }) }
|
||||
export function setCircleRadius(value) { send('SET_CIRCLE_RADIUS', { value }) }
|
||||
export function setLineLength(value) { send('SET_LINE_LENGTH', { value }) }
|
||||
export function setRectangleDims(width, height) { send('SET_RECTANGLE', { width, height }) }
|
||||
|
||||
// --- Layouts-Panel ---
|
||||
export function listLayouts() { send('LIST', {}) }
|
||||
export function newLayout(name, format, landscape, customWidth, customHeight) {
|
||||
send('NEW_LAYOUT', { name, format, landscape: !!landscape,
|
||||
customWidth, customHeight })
|
||||
}
|
||||
export function deleteLayout(id) { send('DELETE_LAYOUT', { id }) }
|
||||
export function renameLayout(id, name) { send('RENAME_LAYOUT', { id, name }) }
|
||||
export function activateLayout(id) { send('ACTIVATE_LAYOUT', { id }) }
|
||||
export function addDetail(pageId, ausschnittId) {
|
||||
send('ADD_DETAIL', { pageId, ausschnittId: ausschnittId || null })
|
||||
}
|
||||
export function deleteDetail(pageId, detailId) {
|
||||
send('DELETE_DETAIL', { pageId, detailId })
|
||||
}
|
||||
export function bindAusschnitt(pageId, detailId, ausschnittId) {
|
||||
send('BIND_AUSSCHNITT', { pageId, detailId, ausschnittId: ausschnittId || null })
|
||||
}
|
||||
export function syncDetail(pageId, detailId) { send('SYNC_DETAIL', { pageId, detailId }) }
|
||||
export function syncLayout(id) { send('SYNC_LAYOUT', { id }) }
|
||||
export function setPageSize(id, format, landscape, customWidth, customHeight) {
|
||||
send('SET_PAGE_SIZE', { id, format, landscape: !!landscape,
|
||||
customWidth, customHeight })
|
||||
}
|
||||
export function exportPdf(id, dpi) { send('EXPORT_PDF', { id, dpi: dpi || 300 }) }
|
||||
export function exportPdfAll(dpi) { send('EXPORT_PDF', { dpi: dpi || 300 }) }
|
||||
export function exportPdfMany(ids, dpi) { send('EXPORT_PDF', { ids, dpi: dpi || 300 }) }
|
||||
export function addLayoutFolder(name) { send('ADD_FOLDER', { name }) }
|
||||
export function removeLayoutFolder(name) { send('REMOVE_FOLDER', { name }) }
|
||||
export function setLayoutFolder(id, folder) { send('SET_FOLDER', { id, folder }) }
|
||||
|
||||
// --- Elemente-Panel (Smart Architektur-Elemente) ---
|
||||
export function listElemente() { send('LIST', {}) }
|
||||
export function createWall(p) { send('CREATE_WALL', p || {}) }
|
||||
export function createDecke(p) { send('CREATE_DECKE', p || {}) }
|
||||
export function createDach(p) { send('CREATE_DACH', p || {}) }
|
||||
export function createFenster(p) { send('CREATE_FENSTER', p || {}) }
|
||||
export function createTuer(p) { send('CREATE_TUER', p || {}) }
|
||||
export function createTreppe(p) { send('CREATE_TREPPE', p || {}) }
|
||||
export function updateElement(id, patch) { send('UPDATE_ELEMENT', { id, ...(patch || {}) }) }
|
||||
export function deleteElement(id) { send('DELETE_ELEMENT', { id }) }
|
||||
// Backwards-Compat-Aliases
|
||||
export function updateWall(id, patch) { send('UPDATE_WALL', { id, ...(patch || {}) }) }
|
||||
export function deleteWall(id) { send('DELETE_WALL', { id }) }
|
||||
export function regenerateAllWalls() { send('REGENERATE_ALL', {}) }
|
||||
export function regenerateAllElements() { send('REGENERATE_ALL', {}) }
|
||||
|
||||
|
||||
let _visTimer = null
|
||||
let _visArgs = null
|
||||
|
||||
// --- EBENEN: Ebenenkombinationen (geteilter Store mit AUSSCHNITTE) ---
|
||||
export function getCombination() { send('GET_COMBINATION', {}) }
|
||||
export function applyCombination(layersOrPreset) {
|
||||
// Akzeptiert entweder ein Array von Layer-States (alt) oder ein Preset-
|
||||
// Objekt {layers, dossierEbenen, dossierZeichnungsebenen} (neu). Backend kommt
|
||||
// mit beiden Formaten klar.
|
||||
if (Array.isArray(layersOrPreset)) {
|
||||
send('APPLY_COMBINATION', { layers: layersOrPreset })
|
||||
} else if (layersOrPreset && typeof layersOrPreset === 'object') {
|
||||
send('APPLY_COMBINATION', layersOrPreset)
|
||||
} else {
|
||||
send('APPLY_COMBINATION', { layers: [] })
|
||||
}
|
||||
}
|
||||
export function saveCombinationPreset(name, layers) { send('SAVE_PRESET', { name, layers }) }
|
||||
export function saveCurrentAsCombination(name) { send('SAVE_CURRENT_AS_PRESET', { name }) }
|
||||
export function deleteCombinationPreset(name) { send('DELETE_PRESET', { name }) }
|
||||
|
||||
export function applyVisibility(activeZ, zeichnungsebenen, activeCode, ebenen, zMode, eMode) {
|
||||
_visArgs = { activeZ, zeichnungsebenen, activeCode, ebenen, zMode, eMode }
|
||||
if (_visTimer) clearTimeout(_visTimer)
|
||||
_visTimer = setTimeout(() => {
|
||||
_visTimer = null
|
||||
const a = _visArgs
|
||||
const slimZ = a.zeichnungsebenen.map(z => ({
|
||||
id: z.id, name: z.name, visible: z.visible !== false,
|
||||
}))
|
||||
const slimE = a.ebenen.map(e => ({
|
||||
code: e.code, visible: e.visible !== false, locked: e.locked === true,
|
||||
}))
|
||||
send('SET_VISIBILITY', {
|
||||
activeZ: { id: a.activeZ.id },
|
||||
activeCode: a.activeCode,
|
||||
zeichnungsebenen: slimZ,
|
||||
ebenen: slimE,
|
||||
zMode: a.zMode,
|
||||
eMode: a.eMode,
|
||||
})
|
||||
}, 30)
|
||||
}
|
||||
Reference in New Issue
Block a user