Dieser Artikel beschreibt, wie man ein Python-Skript erstellt, das einen CIPM-Bericht, der aus dem Progress-Monitoring-Tool extrahiert wurde, transformiert in:
- BCF-Dateien zur Verwendung mit einem Revit-Modell oder einem anderen Tool
- formatierte Anmerkungen innerhalb eines Cintoo-Projekts, das an problematische Modellelemente gebunden ist, basierend auf Coverage Parameter
Permissions: users with roles including Manage Annotations permission can create annotations using this script.
Ein Beispiel für ein Skript befindet sich im Anhang. Alle zusätzlichen Skripte sind ebenfalls angehängt.
INHALTSVERZEICHNIS
- Voraussetzungen
- Eingabeinformationen
- Genehmigung
- Einschränkungen
- Ergebnisse
- CreateBCF.py-Skript erklärt
- CreateAnnotation.py-Skript erklärt
Voraussetzungen
Installieren Sie Python anhand der Anweisungen eines AI-Chatbots Ihrer Wahl. Fragen Sie nach einer Anleitung, um Python-Skripte über die Eingabeaufforderung auszuführen, wenn keine dedizierte Umgebung installiert ist.
In diesem Python-Skript werden die folgenden Bibliotheken verwendet, um den Workflow auszuführen.
Installieren Sie sie, bevor Sie das Skript verwenden:
- png - führen Sie den folgenden Befehl aus, um die Bibliothek zu verbinden
pip install pypng- requests - führen Sie den folgenden Befehl aus, um die Bibliothek zu verbinden
pip install requestsEingabeinformationen
Führen Sie das Skript createBCF.py in der Eingabeaufforderung aus. Alle anderen Skripte sind ergänzender Natur.
python createBCF.py
Nach der Ausführung wird das Skript den Benutzer auffordern, die folgenden Informationen bereitzustellen:
- Geben Sie den Pfad zum CIPM .csv-Bericht ein - vollständiger Pfad zum CIPM-Bericht im CSV-Format mit Erweiterung
Beispiel: C:\Projects\ProgressMonitoring\Walls_0.0500(Meters).csv - Geben Sie den Pfad ein, in dem BCF-Dateien erstellt werden sollen - vollständiger Pfad zum Erstellen und Speichern von BCF-Dateien lokal
Beispiel: C:\Projects\ProgressMonitoring\BCF - Geben Sie die minimale Abdeckung ein - vom Benutzer definierter Grenzwert, um alle Abdeckung -Werte, die strikt darunter liegen, in Prozent zu erfassen.
Beispiel: Um BCF/Annotations für Modellelemente mit weniger als 25% Scan-Abdeckung zu erstellen, Wert 25 eingeben - Geben Sie den Pfad zur Arbeitszone ein, in der Anmerkungen erstellt werden sollen - vollständige URL-Adresse zur Arbeitszone in einem Cintoo-Projekt.
Note: it's highly recommended to create a dedicated work zone for annotations created by this script as this would allow more control on the result and easier cleanup in case of need.
Beispiel: https://aec.cintoo.com/accounts/0a000a00-000a-000a-a0000aa0a00000a0/
projects/a0a0000a-aaa0000a-000a-aaaa00aaa000/workzones/aaaaaaaaaa0aaaaaaaa/data
Kopieren Sie diese URL direkt aus dem Browser.
Genehmigung
Sobald alle Parameter eingegeben sind, wird der Benutzer aufgefordert, die Nutzung eines Cintoo-Kontos auf einer angezeigten Browser-Seite zu autorisieren. Klicken Sie auf Zulassen , um fortzufahren.
Nach der Autorisierung erscheint die folgende Nachricht, die eine erfolgreiche Authentifizierung signalisiert. Diese Browser-Registerkarte kann sicher geschlossen werden.

Cintoo-tokens.json - Authentifizierungs-JSON, das vom Skript erstellt wurde, um die Verbindung herzustellen. Es wird empfohlen, bei jedem Abschluss eines Jobs vom Benutzer gelöscht zu werden.
Note: time spent generating BCF, and annotations is based on the coverage level value and the amount of model elements.
Einschränkungen
Warning: please be aware that maximum of 3000 annotations could be created with the script using APIs.
Ergebnisse
- BCF-Dateien am benutzerdefinierten Pfad lokal gespeichert, die in Revit verwendet werden können, um problematische Modellelemente zu erkennen und zu korrigieren.

- Cintoo Annotations im Projekt, das visuell mit Modellelementen verbunden ist, mit allen notwendigen Daten in der Beschreibung (bearbeitbar).

CreateBCF.py-Skript erklärt
Im Folgenden finden Sie das Skript mit Kommentaren zu jedem Teil.
Warning: if any adjustments to the code are needed to be made, it is possible, but Cintoo does not take any responsibility on the functionality of this code once modified.
import csv
import os
import zipfile
import png
from createAnnotation import createAnnotation
import login
from cintooUtils import parse_cintoo_workzone_context_url
import datetime
import uuid
def create_png(path):
width = 255
height = 255
img = []
for y in range(height):
row = ()
for x in range(width):
row = row + (x, max(0, 255 - x - y), y)
img.append(row)
with open(path, 'wb') as f:
w = png.Writer(width, height, greyscale=False)
w.write(f, img)Importieren aller notwendigen Bibliotheken und ergänzenden Skripte zur Unterstützung des Hauptskripts. Erstellen einer Vorlage png, die erforderlich ist, um eine BCF-Datei zu erstellen.
def create_bcfzip(input_csv,
output_dir,
min_coverage,
tenant=None,
account_id=None,
project_id=None,
workzone_id=None,
camera_state=None,
headers=None):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
report_name = input_csv.split("\\")[-1]
with open(input_csv, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
elements_to_be_created = [row for row in reader if float(row['Coverage by Scan Data (%)']) < min_coverage]
if len(elements_to_be_created) > 1000:
print("More than 1000 elements have a lower score than the minimum coverage score. Nur die obersten 1000 werden erstellt.")
elements_to_be_created = sorted(
elements_to_be_created,
key=lambda x: float(x['Coverage by Scan Data (%)']),
reverse=True
)[0:999]
for row in elements_to_be_created:
if (row.get('IfcGUID') or row.get('GlobalId')) and row.get('Coverage by Scan Data (%)'):
print(row)
try:
coverage = float(row['Coverage by Scan Data (%)'])
print(coverage)
except ValueError:
continue
guid = row.get('IfcGUID') or row.get('GlobalId')
element_name = row.get('Model Element Name', 'Unknown')
element_id = list(row.values())[0]
print(element_id)
create_bcf_file(guid, element_name, output_dir, coverage, report_name)
if all([tenant, account_id, project_id, workzone_id, camera_state]):
createAnnotation(tenant,
account_id,
project_id,
workzone_id,
f'Low Coverage Issue: {element_name}',
f"The element {element_name} has a coverage score of {coverage}, according to the report {report_name}",
{"x": 0, "y": 0, "z": 0},
camera_state,
headers,
modelElementId=element_id)Funktion, die BCF-Dateien erstellt, die mit jedem Element basierend auf dem vom Benutzer definierten Coverage -Parameter verknüpft sind.
Es analysiert alle notwendigen Daten aus vom Benutzer bereitgestellten Parametern wie Ausgabeverzeichnis, minimale Abdeckung, Mandant etc. und vorhandene GUIDs in den Zeilen des CIPM-Berichts, um sie basierend auf Abdeckungsvergleichen zu extrahieren.
Warning: it is not recommended to change the create_bcfzip function, only if there is a need to change the way the condition to choose the data works (for example, coverage<min_coverage). Bei Änderungen des Anmerkungstextes, der Beschreibung oder der Beschriftung - bitte die create_issue_xml-Funktion beachten.
def create_bcf_file(guid, element_name, output_dir, coverage, report_name):
bcf_dir = os.path.join(output_dir, guid)
os.makedirs(bcf_dir, exist_ok=True)
viewpoint_guid = str(uuid.uuid4())
comment_guid = str(uuid.uuid4())
topic_guid = str(uuid.uuid4())
project_guid = str(uuid.uuid4())
viewpoint_filename = os.path.join(bcf_dir, 'viewpoint.bcfv')
viewpoint_xml = create_viewpoint_xml(guid, viewpoint_guid)
with open(viewpoint_filename, 'w', encoding='utf-8') as f:
f.write(viewpoint_xml)
issue_filename = os.path.join(bcf_dir, 'markup.bcf')
issue_xml = create_issue_xml(element_name, coverage, report_name, viewpoint_guid, comment_guid, topic_guid)
with open(issue_filename, 'w', encoding='utf-8') as f:
f.write(issue_xml)
project_filename = os.path.join(output_dir, 'project.bcfp')
project_xml = create_project_xml(project_guid)
with open(project_filename, 'w', encoding='utf-8') as f:
f.write(project_xml)
version_filename = os.path.join(output_dir, 'bcf.version')
with open(version_filename, 'w', encoding='utf-8') as f:
f.write(create_bcf_version())
snapshot_filename = os.path.join(output_dir, 'snapshot.png')
create_png(snapshot_filename)
zip_filename = os.path.join(output_dir, f'{guid}.bcf')
with zipfile.ZipFile(zip_filename, 'w') as zf:
zf.write(viewpoint_filename, os.path.join(topic_guid, 'viewpoint.bcfv'))
zf.write(issue_filename, os.path.join(topic_guid, 'markup.bcf'))
zf.write(snapshot_filename, os.path.join(topic_guid, 'snapshot.png'))
zf.write(project_filename, os.path.join('project.bcfp'))
zf.write(version_filename, os.path.join('bcf.version'))
# Cleanup
os.remove(viewpoint_filename)
os.remove(issue_filename)
os.remove(project_filename)
os.remove(version_filename)
os.remove(snapshot_filename)
os.rmdir(bcf_dir)Erstellen individueller BCF-Dateien.
def create_viewpoint_xml(guid, viewpoint_guid):
return f"""<?xml version="1.0" encoding="UTF-8"?>
<VisualizationInfo Guid="{viewpoint_guid}">
<Components>
<ViewSetupHints SpacesVisible="false" SpaceBoundariesVisible="false" OpeningsVisible="false" />
<Selection>
<Component IfcGuid="{guid}" />
</Selection>
<Visibility DefaultVisibility="true" />
</Components>
<OrthogonalCamera>
<CameraViewPoint>
<X>129.93820633961636</X>
<Y>-124.61204504554462</Y>
<Z>104.47418360973809</Z>
</CameraViewPoint>
<CameraDirection>
<X>-0.58963662529065941</X>
<Y>0.5647967039409042</Y>
<Z>-0.57735026918962584</Z>
</CameraDirection>
<CameraUpVector>
<X>-0.41693605617897656</X>
<Y>0.39937157934842415</Y>
<Z>0.81649658092772603</Z>
</CameraUpVector>
<ViewToWorldScale>44.256083397559856</ViewToWorldScale>
</OrthogonalCamera>
</VisualizationInfo>"""Einrichten der Standardkamera-Position.
def create_issue_xml(element_name, coverage, report_name, viewpoint_guid, comment_guid, topic_guid):
return f"""<?xml version="1.0" encoding="UTF-8"?>
<Markup>
<Topic Guid="{topic_guid}" TopicType="Issue" TopicStatus="Active">
<Title>Geringes Abdeckungsproblem: {element_name}</Title>
<Priority>Normal</Priority>
<Index>1</Index>
<CreationDate>2025-03-05T09:22:02+00:00</CreationDate>
<CreationAuthor>youremail@domain.com</CreationAuthor>
<ModifiedDate>2025-03-05T09:22:03+00:00</ModifiedDate>
<ModifiedAuthor>youremail@domain.com</ModifiedAuthor>
<DueDate>2025-03-05T09:00:00+00:00</DueDate>
<AssignedTo>youremail@domain.com</AssignedTo>
<Description>Das Element {element_name} hat einen Abdeckungswert von {coverage}, gemäß dem Bericht {report_name}</Description>
</Topic>
<Comment Guid="{comment_guid}">
<Date>2025-03-04T09:53:03+00:00</Date>
<Author>youremail@domain.com</Author>
<Comment>Das Element {element_name} hat einen Abdeckungswert von {coverage}, gemäß dem Bericht {report_name}</Comment>
<Viewpoint Guid="{viewpoint_guid}" />
<ModifiedDate>2025-03-04T09:53:03+00:00</ModifiedDate>
<ModifiedAuthor>youremail@domain.com</ModifiedAuthor>
</Comment>
<Viewpoints Guid="{viewpoint_guid}">
<Viewpoint>viewpoint.bcfv</Viewpoint>
<Snapshot>snapshot.png</Snapshot>
</Viewpoints>
</Markup>
"""Erstellen von XMLs für Metadaten zu Problemen.
Benötigte Informationen über den Autor in den Feldern eingeben:
- CreationAuthor - Zeile 154
- ModifiedAuthor - Zeile 156
- AssignedTo - Zeile 158
- Author - Zeile 163
- ModifiedAuthor - Zeile 167
def create_project_xml(projectGuid):
return f"""<?xml version="1.0" encoding="UTF-8"?>
<ProjectExtension>
<Project ProjectId="{projectGuid}" />
<ExtensionSchema></ExtensionSchema>
</ProjectExtension>"""Erstellen einer Datei, die auf ein Projekt verweist.
def create_extensions_xsd():
return """<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<schema>
<redefine schemaLocation="markup.xsd">
<simpleType name="Priority">
<restriction base="Priority">
<enumeration value="Aucune"/>
<enumeration value="Faible"/>
<enumeration value="Moyenne"/>
<enumeration value="Haute"/>
<enumeration value="Critique"/>
</restriction>
</simpleType>
<simpleType name="TopicStatus">
<restriction base="TopicStatus">
<enumeration value="Assignée"/>
<enumeration value="En cours"/>
<enumeration value="Terminée"/>
<enumeration value="Bloquée"/>
<enumeration value="Validée"/>
<enumeration value="Archivée"/>
</restriction>
</simpleType>
<simpleType name="TopicLabel">
<restriction base="TopicLabel"/>
</simpleType>
</redefine>
</schema>
"""Beschreibung der BCF-Struktur (Kategorien).
def create_bcf_version():
return """<?xml version="1.0" encoding="UTF-8"?>
<Version VersionId="2.1" xsi:noNamespaceSchemaLocation="version.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DetailedVersion>2.1 KUBUS BV</DetailedVersion>
</Version>"""Definition der BCF-Version (2.1).
def main():
input_csv = input("Insert the path to the CIPM .csv report :")
output_dir = input("Insert the path where BCF files should be created :")
min_coverage = float(input("Insert the minimum coverage"))
context_url = input("Insert the path to the workzone where to create annotations (e.g: "
"https://aec.cintoo.com/accounts/0a000a00-000a-000a-a000-0aa0a00000a0/projects/a0a0000a-aaa0"
"-000a-000a-aaaa00aaa000/workzones/aaaaaaaaaa0aaaaaaaa/data ")
context_details = parse_cintoo_workzone_context_url(context_url)
tenant, account_id, project_id, workzone_id = context_details['tenant'], context_details['accountId'], \
context_details['projectId'], context_details['workzoneId']
# Authenticate and get access token
login.load_tokens()
token = login.get_token(tenant)
headers = {"Authorization": f"Bearer {token}"}
camera_state = {"type": "legacy",
"camerastate": {
"position": [0, 0, 1],
"rotationQuaternion": [0, 0, 0, 1],
"fov": 1.5708,
"ortho": False,
"overviewScale": 10,
"camType": "scan"},
}
create_bcfzip(input_csv,
output_dir,
min_coverage,
tenant,
account_id,
project_id,
workzone_id,
camera_state,
headers)
main()Definition des Hauptteils des Skripts, das dem Benutzer Eingabefragen stellt, Zugriffstoken abruft und sich gemäß Cintoo API Authentifizierung bei einem Cintoo-Konto authentifiziert, Standard-Kameraposition, die nach dem Import in Cintoo geändert wird, Ausgabe-BCFzip-Dateien erstellt und die Hauptfunktion ausführt.
CreateAnnotation.py-Skript erklärt
Nachfolgend finden Sie das Skript mit Kommentaren zu jedem Teil.
Warning: if any adjustments to the code are needed to be made it is possible but Cintoo does not take any responsibility on the functionality of this code once modified.
import login
import requests
from cintooUtils import parse_cintoo_workzone_context_url
TIMEOUT = 10Importieren aller notwendigen Bibliotheken und Daten aus ergänzenden Skripten zur Unterstützung des Hauptskripts. Festlegen eines Timeout-Parameters.
def getWorkzoneGuid(tenant, account_id, project_id, workzone_idv1, headers):
url = f"{tenant}/api/2/accounts/{account_id}/projects/{project_id}/workzones"
response = requests.get(url, headers=headers, timeout=TIMEOUT)
response.raise_for_status()
return [wz for wz in response.json() if wz["api1Id"] == workzone_idv1]Extrahieren der Arbeitszonen-ID aus Benutzereingabe.
def createAnnotation(tenant,
account_id,
project_id,
workzone_id,
title,
description,
position,
saved_view,
headers,
modelElementId=None,):
"""Erstellt ein Projekt im angegebenen Konto."""
workzone_guid = getWorkzoneGuid(tenant, account_id, project_id, workzone_id, headers)[0]['id'].split(':')[-1]
url = f"{tenant}/api/2/accounts/{account_id}/projects/{project_id}/annotations"
print(f"modelElementId : {modelElementId}")
if modelElementId:
body = {
'workzoneId': workzone_guid,
'annotationType': "Note",
'title': title,
'description': description,
'position': position,
'normal': {"x": 0, "y": 0, "z": 1},
'savedView': {**saved_view, 'modelElementId': modelElementId},
}
else:
body = {
'workzoneId': workzone_guid,
'annotationType': "Note",
'title': title,
'description': description,
'position': position,
'normal': {"x": 0, "y": 0, "z": 1},
'savedView': saved_view,
'elementId': "Toto"
}
print(url)
print(body)
response = requests.post(url, headers=headers, json=body, timeout=TIMEOUT)
print(response.text)
response.raise_for_status()
return response.json()Parsing von Cintoo-Standortinformationen aus Benutzereingaben, um jede Annotation einem entsprechenden Modellelement zuzuordnen.
def main(context_url):
context_details = parse_cintoo_workzone_context_url(context_url)
tenant, account_id, project_id, workzone_id = context_details['tenant'], context_details['accountId'], \
context_details['projectId'], context_details['workzoneId']
# Authenticate and get access token
login.load_tokens()
token = login.get_token(tenant)
headers = {"Authorization": f"Bearer {token}"}
camera_state = {"type": "legacy",
"camerastate": {
"position": [0, 0, 1],
"rotationQuaternion": [0, 0, 0, 1],
"fov": 1.5708,
"ortho": False,
"overviewScale": 10,
"camType": "scan"}}
print(workzone_id)
createAnnotation(tenant, account_id, project_id, workzone_id, "Test API", "Created with API",
{"x": 0, "y": 0, "z": 0}, camera_state, headers)Hauptfunktion mit Authentifizierungsprozess, Kameraeinstellung und Erstellen von Anmerkungen im Cintoo-Projekt.
War dieser Artikel hilfreich?
Das ist großartig!
Vielen Dank für das Feedback
Leider konnten wir nicht helfen
Vielen Dank für das Feedback
Feedback gesendet
Wir wissen Ihre Bemühungen zu schätzen und werden versuchen, den Artikel zu korrigieren