Cet article décrit comment créer un script Python qui permet de transformer un rapport CIPM extrait de l'outil de Suivi de progression en :
- Fichiers BCF à utiliser avec un modèle Revit ou tout autre outil
- Formaté Annotations à l'intérieur d'un projet Cintoo lié à des éléments de modèle problématiques basés sur le paramètre Couverture .
Autorisations : les utilisateurs ayant des rôles incluant l'autorisation Gérer les annotations peuvent créer des annotations avec ce script.
Un exemple de script est fourni en pièce jointe. Tous les scripts supplémentaires sont également joints.
TABLE DES MATIÈRES
- Prérequis
- Informations d'entrée
- Autorisation
- Limitations
- Résultats
- CreateBCF.py Script Explained
- CreateAnnotation.py Script Explained
Prérequis
Installez Python en suivant les instructions de l'outil de chatbot AI de votre choix. Demandez une instruction pour exécuter des scripts Python depuis l'invite de commande si aucun environnement dédié n'est installé.
Dans ce script Python, les bibliothèques suivantes sont utilisées pour exécuter le flux de travail.
Installez-les avant d'utiliser le script :
- png - exécutez la commande suivante pour connecter la bibliothèque
pip install pypng
- requests - exécutez la commande suivante pour connecter la bibliothèque
pip install requests
Informations d'entrée
Exécutez le script createBCF.py dans l'invite de commande. Tous les autres scripts sont des suppléments.
python createBCF.py
Une fois exécuté, le script invite l'utilisateur à fournir les informations suivantes :
- Insérez le chemin vers le rapport CIPM .csv - chemin complet vers le rapport CIPM au format CSV avec extension
Exemple : C:\Projects\ProgressMonitoring\Walls_0.0500(Meters).csv - Insérez le chemin où les fichiers BCF doivent être créés - chemin complet pour créer et stocker localement les fichiers BCF
Exemple : C:\Projects\ProgressMonitoring\BCF - Insérez la couverture minimale - limite définie par l'utilisateur pour prendre toutes les valeurs de Couverture strictement en dessous en pourcentage.
Exemple : pour créer des BCF/Annotations pour les éléments de modèle avec moins de 25 % de couverture de balayage, insérez la valeur 25 - Insérez le chemin vers la zone de travail où créer les annotations - adresse URL complète vers la zone de travail dans le projet Cintoo.
Remarque : il est fortement recommandé de créer une zone de travail dédiée pour les annotations créées par ce script, car cela permettrait un meilleur contrôle des résultats et un nettoyage plus facile si nécessaire.
Exemple : https://aec.cintoo.com/accounts/0a000a00-000a-000a-a0000aa0a00000a0/
projects/a0a0000a-aaa0000a-000a-aaaa00aaa000/workzones/aaaaaaaaaa0aaaaaaaa/data
Copiez cette URL directement depuis le navigateur.
Autorisation
Une fois tous les paramètres insérés, l'utilisateur est invité à autoriser l'utilisation d'un compte Cintoo dans une page du navigateur qui s'est ouverte. Cliquez sur Autoriser pour continuer.
Une fois l'autorisation accordée, le message suivant apparaîtra pour signaler une authentification réussie. Cette onglet de navigateur pourrait être fermé en toute sécurité.
Cintoo-tokens.json - authentification JSON créé par le script pour établir la connexion. Recommandé à supprimer chaque fois qu'un utilisateur termine le travail.
Remarque : le temps passé à générer des BCF et des annotations dépend de la valeur du niveau de couverture et du nombre d'éléments de modèle.
Limitations
Avertissement : sachez que le script peut créer un maximum de 3000 annotations via les API.
Résultats
- Les fichiers BCF enregistrés localement à un chemin défini par l'utilisateur peuvent être utilisés dans Revit (par exemple) pour détecter et corriger les éléments de modèle problématiques
- Cintoo Annotations dans le projet visuellement lié aux éléments de modèle avec toutes les données nécessaires dans la description (modifiable).
CreateBCF.py Script Explained
Vous trouverez ci-dessous le script avec des commentaires sur chaque partie.
Avertissement : si des ajustements du code doivent être effectués, c'est possible, mais Cintoo n'assume aucune responsabilité sur la fonctionnalité de ce code une fois modifié.
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)
Importation de toutes les bibliothèques nécessaires et des scripts supplémentaires pour prendre en charge le script principal. Création d'un modèle png nécessaire pour créer un fichier BCF.
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("Plus de 1000 éléments ont une note inférieure au score de couverture minimal. Seuls les 1000 premiers seront créés.") 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'Problème de faible couverture : {element_name}', f"L'élément {element_name} a un score de couverture de {coverage}, selon le rapport {report_name}", {"x": 0, "y": 0, "z": 0}, camera_state, headers, modelElementId=element_id)
Fonction qui crée des fichiers BCF liés à chaque élément pris en fonction du paramètre Couverture défini par l'utilisateur.
Il analyse toutes les données nécessaires à partir des paramètres fournis par l'utilisateur, comme le répertoire de sortie, la couverture minimale, le locataire, etc., et les GUID existants dans les lignes de rapport CIPM pour les extraire en fonction de la comparaison des couvertures.
Avertissement : il n'est pas recommandé de changer la fonction create_bcfzip, sauf s'il est nécessaire de modifier la façon dont la condition pour choisir les données fonctionne (par exemple, couverture<min_couverture). Pour les changements de texte d'annotation, de description, d'étiquetage - veuillez vous référer à la fonction create_issue_xml.
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'))
# Nettoyage
os.remove(viewpoint_filename)
os.remove(issue_filename)
os.remove(project_filename)
os.remove(version_filename)
os.remove(snapshot_filename)
os.rmdir(bcf_dir)
Création de fichiers BCF individuels.
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>"""
Mise en place de la position par défaut de la caméra.
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>Problème de faible couverture : {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>L'élément {element_name} a un score de couverture de {coverage}, selon le rapport {report_name}</Description> </Topic> <Comment Guid="{comment_guid}"> <Date>2025-03-04T09:53:03+00:00</Date> <Author>youremail@domain.com</Author> <Comment>L'élément {element_name} a un score de couverture de {coverage}, selon le rapport {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> """
Création de fichiers XML pour les métadonnées des problèmes.
Entrez les informations nécessaires sur l'auteur dans les champs :
- CreationAuthor - ligne 154
- ModifiedAuthor - ligne 156
- AssignedTo - ligne 158
- Auteur - ligne 163
- ModifiedAuthor - ligne 167
def create_project_xml(projectGuid): return f"""<?xml version="1.0" encoding="UTF-8"?> <ProjectExtension> <Project ProjectId="{projectGuid}" /> <ExtensionSchema></ExtensionSchema> </ProjectExtension>"""
Création d'un fichier référenciant un projet.
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> """
Description de la structure BCF (catégories).
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>"""
Définition de la version BCF (2.1).
def main():
input_csv = input("Insérez le chemin vers le rapport CIPM en .csv :")
output_dir = input("Insérez le chemin où les fichiers BCF doivent être créés :")
min_coverage = float(input("Insérez la couverture minimale"))
context_url = input("Insérez le chemin vers la zone de travail où créer des annotations (par ex : "
"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']
# Authentification et obtention du jeton d'accès
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()
Définition de la partie principale du script posant les questions d'entrée à l'utilisateur, obtenant le jeton d'accès et authentifiant avec le compte Cintoo selon l'authentification API de Cintoo, la position par défaut de la caméra qui est modifiée une fois importée dans Cintoo, créant les fichiers BCFzip de sortie et exécutant la fonction principale.
CreateAnnotation.py Script Explained
Vous trouverez ci-dessous le script avec des commentaires sur chaque partie.
Attention : si des ajustements au code doivent être effectués, c'est possible mais Cintoo n'assume aucune responsabilité quant à la fonctionnalité de ce code une fois modifié.
import login
import requests
from cintooUtils import parse_cintoo_workzone_context_url
TIMEOUT = 10
Importation de toutes les bibliothèques nécessaires et des données à partir de scripts supplémentaires pour soutenir le script principal. Définition d'un paramètre de délai d'attente.
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]
Extraction de l'ID de la zone de travail à partir de l'entrée utilisateur.
def createAnnotation(tenant,
account_id,
project_id,
workzone_id,
title,
description,
position,
saved_view,
headers,
modelElementId=None,):
"""Crée un projet dans le compte spécifié."""
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()
Analyse des informations de localisation Cintoo à partir de l'entrée utilisateur, en attachant chaque annotation à un élément de modèle correspondant.
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']
# Authentification et obtention du jeton d'accès
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", "Créé avec l'API",
{"x": 0, "y": 0, "z": 0}, camera_state, headers)
Fonction principale avec processus d'authentification, configuration de la position de la caméra et création d'annotations dans le projet Cintoo.
Cet article a-t-il été utile ?
C'est super !
Merci pour votre commentaire
Désolé ! Nous n'avons pas pu vous être utile
Merci pour votre commentaire
Commentaires envoyés
Nous apprécions vos efforts et nous allons corriger l'article