Rhino Sync

Rhino Sync

Live Rhino 8 ↔ QGIS link through a shared folder
version 0.2.2 · manual generated on 2026-11-05 from 06-plugin/help/rhino-sync/en · online version

What Rhino Sync is

Draw or correct in Rhino 8 and see the result in QGIS; send layers from QGIS and see them in Rhino, with no export / import steps. The link is independent of MapQuilt: the same connection works in any Rhino + QGIS project (a masterplan, an urban design sketch, a measured survey). MapQuilt only embeds it in its dock, as a tab.

"Live" does not mean a socket but two file watchers on the same folder: Rhino writes rhino_push.geojson, QGIS loads it within 0.5 s; QGIS writes qgis_pull.geojson, Rhino draws it. That is enough for working on the same machine or on a shared folder.

Who made it

Rhino Sync, like MapQuilt, was created by Radu Andrei, of the "Gheorghe Asachi" Technical University of Iași, Faculty of Architecture "G. M. Cantacuzino", with the help of Claude, the AI model by Anthropic, which wrote the code, tests and documentation under the author's guidance, on the scans of the ICPROM Iași cadastral plans (1988–1990) and on the benchmark drawn by the author in Rhino. The research questions, the method decisions, the ground-truth verification and every confirmation on the real sheets are the author's; the test reports in the repository (T1–T19) record what was tried and with what figures.

The user rule

You draw on SCHITA, you read CONTEXT, and QGIS answers you.

Rhino layer family Who writes it What it means
SCHITA::<CLASS>[::<SUBTYPE>] you the only family that leaves on push (SCHITA = sketch); the second level is the class (SCHITA::CLADIRI → building), the third the subtype (SCHITA::CLADIRI::P+4 → subtype P+4, and RH if it is a height regime)
CONTEXT::<LAYER> QGIS → Rhino ("Send context") the existing situation (CONTEXT = context; cadastre, buildings, boundaries…), drawn locked, replaced at every send, never exported
QGIS::<LAYER> QGIS → Rhino ("Send to Rhino") the answers: what was taken over / edited in QGIS; replaced at every pull, never exported
PARCELE, CLADIRI … (plain) older drawings the plain convention still works

Every entity sent carries rhino_id (the GUID of the Rhino object), class, subtype, RH, and the fills (fill_pattern, fill_color, fill_scale, fill_angle).

Georeferencing convention

The Rhino model is in meters, and the coordinates are those of the projected CRS (Stereo 70 by default, EPSG:3844): the origin is not moved and no transformation is applied. The point 695 500 / 633 200 in the drawing is the point 695 500 / 633 200 on the map. EarthAnchorPoint is ignored (the command warns if it is set); a document that is not in meters gets a warning.

Installation

With one click

08-rhino\installer\dist\RhinoSyncSetup.exe copies the scripts to Documents\rhino-sync, writes the aliases RhinoSync, RhinoSyncPush, RhinoSyncPull, RhinoSyncWatch into the Rhino 8 settings and puts the Rhino Sync plugin into the QGIS profile, enabled. Close Rhino and QGIS first (the aliases are written to settings-Scheme__Default.xml, which Rhino rewrites when it closes).

Manually, in Rhino 8

  1. Copy 08-rhino (or at least mapquilt_rhino.py, RhinoSync.py, MapQuiltPush.py, MapQuiltPull.py, MapQuiltWatch.py) somewhere stable, e.g. Documents\rhino-sync\.
  2. Options › Aliases, one per command (a single pair of quotes; the path is yours):
RhinoSync        ! _-RunPythonScript "C:\Users\<you>\Documents\rhino-sync\RhinoSync.py"
RhinoSyncPush    ! _-RunPythonScript "C:\Users\<you>\Documents\rhino-sync\MapQuiltPush.py"
RhinoSyncPull    ! _-RunPythonScript "C:\Users\<you>\Documents\rhino-sync\MapQuiltPull.py"
RhinoSyncWatch   ! _-RunPythonScript "C:\Users\<you>\Documents\rhino-sync\MapQuiltWatch.py"

The script runs on CPython 3 (the default in Rhino 8); the panel needs Eto.Forms, hence CPython. The first run on an unsaved document asks for the folder.

Manually, in QGIS

Copy 08-rhino/qgis-plugin/rhino_sync into %APPDATA%\QGIS\QGIS4\profiles\default\python\plugins\ and enable Rhino Sync from Plugins › Manage; the dock opens from the Vector menu or from the toolbar. With MapQuilt installed, the same box is the Rhino sync tab of the MapQuilt dock.

The shared folder

By default the folder is rhino-sync next to the .3dm file; Rhino remembers it in the document's user text, QGIS in its settings. One folder = one project.

<folder>/rhino_push.geojson    written by Rhino, read by QGIS
<folder>/qgis_pull.geojson     written by QGIS, read by Rhino (onto QGIS::*)
<folder>/qgis_context.geojson  written by QGIS, read by Rhino (onto CONTEXT::*, locked)
<folder>/.alive-rhino, .alive-qgis   heartbeat of each side while it is on Play (3 s)
<folder>/.mapquilt-sync.json   sidecar (who, when, what, CRS, layer map)
<folder>/sync-config.json      optional: CRS, layer map, parent layer names, styles
<folder>/rhino_sync.gpkg       created by QGIS: the sketch mirror, one layer per class (draft_*)
<folder>/project.gpkg          created by QGIS on Promote: the project tables, with rhino_id

Files are written atomically (temporary file + replace, retrying for one second if Windows answers "Access is denied" because the other side is reading).

The Play panel

The same on both sides.

Rhino: RhinoSync opens a modeless "Rhino sync" window: Project folder (+ "…"), a lamp with a caption, Play / Stop, Send now, Get now, Get context and the last message. Play = live mode (automatic push 1.5 s after the last change, automatic pull and context, heartbeat in .alive-rhino); the lamp is green while QGIS is on Play too. Running the command again brings the window to the front instead of opening another one.

QGIS: the "Rhino sync" dock (or the tab in MapQuilt) has the same elements: Project folder, lamp, Play / Stop, Send to Rhino, Get now, plus Send context, Promote to project, Fields…, Send image. Play = watching the folder (every push from Rhino loads by itself) and the heartbeat in .alive-qgis; the Play state is remembered between sessions.

The typical cycle

  1. In Rhino: RhinoSync › Play. In QGIS: Play.
  2. Draw on SCHITA::PARCELE or SCHITA::CLADIRI::P+4; a second or two after you lift your hand, the parcel appears in QGIS, in the Rhino › Draft from Rhino group, over the plan sheet and the satellite image.
  3. Correct a face in QGIS, then Send to Rhino: you see it at once on QGIS::PARCELE, in the color of the QGIS symbol.
  4. Send context sends the selected layers (the existing cadastre, the buildings) to CONTEXT::<LAYER NAME>, locked in Rhino.

Draft / Project in QGIS

Labels, fields, image

Conventions

Layers and geometries

Rhino layer class geometry
STRAZI street closed polyline
PARCELE parcel (includes the building) closed polyline
CLADIRI building closed polyline
ANEXE annex closed polyline
AMENAJARI amenity closed polyline
CODURI indicator circle (→ point with radius_m) or text (→ point with code)
any the same class hatch → polygon with holes + fill (fill_pattern, fill_color, fill_scale, fill_angle)

Only closed outlines go through (an open polyline with a gap under 1 m is closed automatically and reported; over 1 m it is skipped). Curves that are not polylines (splines, polycurves with arcs) are sampled at 200 vertices; NURBS circles are recognized by circle fitting. Nested layers go by their last name (Default::PARCELE = PARCELE). A SCHITA::<unmapped name> layer gives the class <name> in lower case (SCHITA::ZF_PROPUSA → zf_propusa).

Fills and colors, both ways

sync-config.json

QGIS creates it with the default values when you choose the folder; you can edit it. Both sides read it.

{"crs": "EPSG:3844",
 "layers": {"STRAZI": "street", "PARCELE": "parcel", "CLADIRI": "building", "ANEXE": "annex", "AMENAJARI": "amenity", "CODURI": "indicator"},
 "all_layers": false,
 "draft_parent_layer": "SCHITA", "context_parent_layer": "CONTEXT", "pull_parent_layer": "QGIS",
 "qgis_group": "Rhino",
 "style": {"parcel":   {"color": "#ffc800", "fill_pattern": "Solid",  "opacity": 0.16, "stroke_width": 0.5},
           "building": {"color": "#dc0000", "fill_pattern": "Hatch1", "opacity": 0.4,  "stroke_width": 0.5}},
 "color_by": "object",
 "label_height_m": 2,
 "nominal_scale": 1000}

CLI without Rhino, batch checks

mapquilt_rhino.py also works without Rhino, with rhino3dm:

python 08-rhino\mapquilt_rhino.py push desen.3dm C:\tmp\sync\rhino_push.geojson [--all-layers] [--crs=EPSG:xxxx]
python 08-rhino\mapquilt_rhino.py pull qgis_pull.geojson out.3dm [--plain]

On the Țicău benchmark (cadru_ticau.3dm), push gives exactly the benchmark counts (3 streets, 32 parcels, 47 buildings, 27 annexes, 7 amenities, 41 indicators), with vertices within 1 mm of etalon_ticau.geojson. pull draws into a new .3dm; with --plain it draws on the base layers, not under QGIS::, so that it can be re-exported identically.

Limits

Literature

Changelog

From the plugin's metadata.txt, newest first.

Created by Radu Andrei, "Gheorghe Asachi" Technical University of Iași, Faculty of Architecture "G. M. Cantacuzino", with the help of Claude (Anthropic). Plugin GPL-2+, independent of MapQuilt. The manual is generated from 06-plugin/help with build_help.py.
Rhino Sync

Rhino Sync

Legătura live Rhino 8 ↔ QGIS printr-un folder comun
versiunea 0.2.2 · manual generat la 2026-11-05 din 06-plugin/help/rhino-sync/ro · varianta online

Ce este Rhino Sync

Desenați sau corectați în Rhino 8 și vedeți rezultatul în QGIS; trimiteți straturi din QGIS și le vedeți în Rhino, fără pași de export / import. Legătura este independentă de MapQuilt: aceeași conexiune merge în orice proiect Rhino + QGIS (un masterplan, o schiță de urbanism, un releveu). MapQuilt doar o încorporează în dock-ul său, ca tab.

„Live” nu înseamnă un socket, ci două supraveghetoare de fișiere pe același folder: Rhino scrie rhino_push.geojson, QGIS îl încarcă în 0,5 s; QGIS scrie qgis_pull.geojson, Rhino îl desenează. E suficient pentru lucrul pe aceeași mașină sau pe un folder partajat.

Cine l-a făcut

Rhino Sync, ca și MapQuilt, este creat de Radu Andrei, de la Universitatea Tehnică „Gheorghe Asachi” din Iași, Facultatea de Arhitectură „G. M. Cantacuzino”, cu ajutorul lui Claude, modelul de inteligență artificială al companiei Anthropic, care a scris codul, testele și documentația sub îndrumarea autorului, pe scanările planurilor cadastrale ICPROM Iași (1988–1990) și pe etalonul desenat de autor în Rhino. Întrebările cercetării, deciziile de metodă, verificarea adevărului din teren și fiecare confirmare pe foile reale sunt ale autorului; rapoartele de test din depozit (T1–T19) consemnează ce s-a încercat și cu ce cifre.

Regula pentru utilizator

Desenezi pe SCHITA, citești CONTEXT, iar QGIS îți răspunde.

Familie de straturi Rhino Cine o scrie Ce înseamnă
SCHITA::<CLASA>[::<SUBTIP>] dvs. singura familie care pleacă la push; al doilea nivel e clasa (SCHITA::CLADIRI → building), al treilea subtipul (SCHITA::CLADIRI::P+4 → subtype P+4, și RH dacă e regim de înălțime)
CONTEXT::<STRAT> QGIS → Rhino („Send context”) situația existentă (cadastru, clădiri, limite…), desenată blocată, înlocuită la fiecare trimitere, niciodată exportată
QGIS::<STRAT> QGIS → Rhino („Send to Rhino”) răspunsurile: ce a fost preluat / editat în QGIS; înlocuite la fiecare pull, niciodată exportate
PARCELE, CLADIRI … (simple) desenele vechi convenția simplă merge în continuare

Fiecare entitate trimisă poartă rhino_id (GUID-ul obiectului Rhino), class, subtype, RH, umplerile (fill_pattern, fill_color, fill_scale, fill_angle).

Convenția de georeferențiere

Modelul Rhino este în metri, iar coordonatele sunt cele ale CRS-ului proiectat (implicit Stereo 70, EPSG:3844): nu se mută originea, nu se aplică nicio transformare. Punctul din desen 695 500 / 633 200 este punctul de pe hartă 695 500 / 633 200. EarthAnchorPoint e ignorat (comanda avertizează dacă e setat); un document care nu e în metri primește un avertisment.

Instalare

Cu un clic

08-rhino\installer\dist\RhinoSyncSetup.exe copiază scripturile în Documents\rhino-sync, scrie aliasurile RhinoSync, RhinoSyncPush, RhinoSyncPull, RhinoSyncWatch în setările Rhino 8 și pune pluginul Rhino Sync în profilul QGIS, activat. Închideți Rhino și QGIS înainte (aliasurile se scriu în settings-Scheme__Default.xml, pe care Rhino îl rescrie la închidere).

Manual, în Rhino 8

  1. Copiați 08-rhino (sau măcar mapquilt_rhino.py, RhinoSync.py, MapQuiltPush.py, MapQuiltPull.py, MapQuiltWatch.py) undeva stabil, de ex. Documents\rhino-sync\.
  2. Options › Aliases, câte unul pe comandă (o singură pereche de ghilimele, calea e a dvs.):
RhinoSync        ! _-RunPythonScript "C:\Users\<dvs>\Documents\rhino-sync\RhinoSync.py"
RhinoSyncPush    ! _-RunPythonScript "C:\Users\<dvs>\Documents\rhino-sync\MapQuiltPush.py"
RhinoSyncPull    ! _-RunPythonScript "C:\Users\<dvs>\Documents\rhino-sync\MapQuiltPull.py"
RhinoSyncWatch   ! _-RunPythonScript "C:\Users\<dvs>\Documents\rhino-sync\MapQuiltWatch.py"

Scriptul rulează pe CPython 3 (implicit în Rhino 8); panoul cere Eto.Forms, deci CPython. Prima rulare pe un document nesalvat cere folderul.

Manual, în QGIS

Copiați 08-rhino/qgis-plugin/rhino_sync în %APPDATA%\QGIS\QGIS4\profiles\default\python\plugins\ și activați Rhino Sync din Plugins › Manage; dock-ul apare din meniul Vector sau din bara de unelte. Cu MapQuilt instalat, aceeași casetă e tabul Rhino sync al dock-ului MapQuilt.

Folderul comun

Implicit, folderul este rhino-sync lângă fișierul .3dm; Rhino îl ține minte în textul-utilizator al documentului, QGIS în setări. Un folder = un proiect.

<folder>/rhino_push.geojson    scris de Rhino, citit de QGIS
<folder>/qgis_pull.geojson     scris de QGIS, citit de Rhino (pe QGIS::*)
<folder>/qgis_context.geojson  scris de QGIS, citit de Rhino (pe CONTEXT::*, blocat)
<folder>/.alive-rhino, .alive-qgis   bătaia de inimă a fiecărei părți cât e pe Play (3 s)
<folder>/.mapquilt-sync.json   însoțitor (cine, când, ce, CRS, harta straturilor)
<folder>/sync-config.json      opțional: CRS, harta straturilor, numele părinților, stiluri
<folder>/rhino_sync.gpkg       creat de QGIS: oglinda schiței, un strat pe clasă (draft_*)
<folder>/project.gpkg          creat de QGIS la Promote: tabelele proiectului, cu rhino_id

Fișierele se scriu atomic (temporar + înlocuire, cu reîncercare o secundă dacă Windows răspunde „Access is denied” pentru că cealaltă parte citește).

Panoul Play

Același pe ambele părți.

Rhino: RhinoSync deschide o fereastră nemodală „Rhino sync”: Project folder (+ „…”), o lampă cu text, Play / Stop, Send now, Get now, Get context și ultimul mesaj. Play = modul live (push automat la 1,5 s după ultima modificare, pull și context automate, bătaie de inimă în .alive-rhino); lampa e verde cât QGIS e și el pe Play. A doua comandă aduce fereastra în față, nu deschide alta.

QGIS: dock-ul „Rhino sync” (sau tabul din MapQuilt) are aceleași elemente: Project folder, lampă, Play / Stop, Send to Rhino, Get now, plus Send context, Promote to project, Fields…, Send image. Play = supravegherea folderului (fiecare push din Rhino se încarcă singur) și bătaia de inimă în .alive-qgis; starea Play se ține minte între sesiuni.

Ciclul tipic

  1. În Rhino: RhinoSync › Play. În QGIS: Play.
  2. Desenați pe SCHITA::PARCELE sau SCHITA::CLADIRI::P+4; la o secundă-două după ce ridicați mâna, parcela apare în QGIS, în grupul Rhino › Draft from Rhino, peste planșă și satelit.
  3. Corectați în QGIS o față, Send to Rhino: o vedeți imediat pe QGIS::PARCELE, în culoarea simbolului QGIS.
  4. Send context trimite straturile selectate (cadastrul existent, clădirile) pe CONTEXT::<NUME STRAT>, blocate în Rhino.

Draft / Project în QGIS

Etichete, câmpuri, imagine

Convenții

Straturi și geometrii

Strat Rhino clasă geometrie
STRAZI street polilinie închisă
PARCELE parcel (include clădirea) polilinie închisă
CLADIRI building polilinie închisă
ANEXE annex polilinie închisă
AMENAJARI amenity polilinie închisă
CODURI indicator cerc (→ punct cu radius_m) sau text (→ punct cu code)
oricare aceeași clasă hașură → poligon cu goluri + umplere (fill_pattern, fill_color, fill_scale, fill_angle)

Numai contururi închise trec (o polilinie deschisă cu golul sub 1 m se închide automat și se raportează; peste 1 m se sare). Curbele care nu sunt polilinii (spline, polycurve cu arce) se eșantionează în 200 de vârfuri; cercurile NURBS se recunosc prin potrivire de cerc. Straturile imbricate merg după ultimul nume (Default::PARCELE = PARCELE). Un strat SCHITA::<nume nemapat> dă clasa <nume> cu minuscule (SCHITA::ZF_PROPUSA → zf_propusa).

Umpleri și culori, în ambele sensuri

sync-config.json

QGIS îl creează cu valorile implicite când alegeți folderul; îl puteți edita. Ambele părți îl citesc.

{"crs": "EPSG:3844",
 "layers": {"STRAZI": "street", "PARCELE": "parcel", "CLADIRI": "building", "ANEXE": "annex", "AMENAJARI": "amenity", "CODURI": "indicator"},
 "all_layers": false,
 "draft_parent_layer": "SCHITA", "context_parent_layer": "CONTEXT", "pull_parent_layer": "QGIS",
 "qgis_group": "Rhino",
 "style": {"parcel":   {"color": "#ffc800", "fill_pattern": "Solid",  "opacity": 0.16, "stroke_width": 0.5},
           "building": {"color": "#dc0000", "fill_pattern": "Hatch1", "opacity": 0.4,  "stroke_width": 0.5}},
 "color_by": "object",
 "label_height_m": 2,
 "nominal_scale": 1000}

CLI fără Rhino, verificare în loturi

mapquilt_rhino.py merge și fără Rhino, cu rhino3dm:

python 08-rhino\mapquilt_rhino.py push desen.3dm C:\tmp\sync\rhino_push.geojson [--all-layers] [--crs=EPSG:xxxx]
python 08-rhino\mapquilt_rhino.py pull qgis_pull.geojson out.3dm [--plain]

Pe etalonul Țicău (cadru_ticau.3dm) push dă exact numărătorile etalonului (3 străzi, 32 parcele, 47 clădiri, 27 anexe, 7 amenajări, 41 indicatori), cu vârfurile la sub 1 mm de etalon_ticau.geojson. pull desenează într-un .3dm nou; cu --plain pe straturile de bază, nu sub QGIS::, ca să poată fi re-exportat identic.

Limite

Literatură

Jurnalul versiunilor (changelog)

Din metadata.txt al pluginului, cea mai nouă versiune prima (în engleză).

Creat de Radu Andrei, Universitatea Tehnică „Gheorghe Asachi” din Iași, Facultatea de Arhitectură „G. M. Cantacuzino”, cu ajutorul lui Claude (Anthropic). Plugin GPL-2+, independent de MapQuilt. Manualul se generează din 06-plugin/help cu build_help.py.