1 janv. 2022

[Dynamo += Python] Nomenclature vers DataFrame (Pandas)

 




Vous souhaitez plus de filtre et de contrôle sur vos nomenclatures pour analyser vos données ? Suivez le Panda 😀 🐼

if this article is not in your language, use the Google Translate widget (bottom of page for Mobile version)

Bien que les nomenclatures puissent être exportées en au format txt/csv, pour ensuite êtres importés dans Pandas avec Python3 (Google Collab est parfait pour cela, la majorité des librairies Data Science sont préinstallées), voici une alternative sans export avec Dynamo et le moteur CPython3.

Pour exemple, on prendra des nomenclatures d'espaces avec 2 valeurs de puissances surfaciques d'éclairage :

  • Les puissances surfaciques Pecl (W/m²) résultants des luminaires de la maquette.
  • Les puissances surfaciques Pecl (W/m²) définit par l'étude thermique et renseignés au préalable sous Revit par type d'espace.

Le but dans l'exemple ci-dessous est d'obtenir uniquement pour 2 niveaux des statistiques sommaires sur les types d'espace dont les puissances surfaciques d'éclairage dépasseraient les valeurs de la RT (ici on ne tiendra pas compte des coefficients C1 et C2).

J'utilise ici un Wrapper permettant d'envelopper un objet DataFrame dans une classe Python avec rajout de méthodes personnalisées, cela permet également de transférer facilement une instance de classe d'un nœud Python a un autre.

La Classe Python 

import clr
import sys
import re
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
my_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments)
reDir = System.IO.DirectoryInfo(re.__file__)
path_py3_lib = reDir.Parent.Parent.FullName
sys.path.append(path_py3_lib + r'\Lib\site-packages')
import pandas as pd
import numpy as np
from typing import Union
import webbrowser
import codecs
from bs4 import BeautifulSoup as bs
class WrapDFrame():
dictUnitValue = {}
def __init__(self, data: Union[DB.ViewSchedule , pd.core.frame.DataFrame]):
self.lstError = []
if isinstance(data, pd.DataFrame):
self._df = data
elif isinstance(data, pd.Series):
self._df = data.to_frame()
else:
self._df = self.__getDF_From_View(data)
#
def __getDF_From_View(self, data):
"""
convert Schedule to DataFrame (private method)
"""
viewschedule = data
outvalue = []
if viewschedule.ViewType == ViewType.PanelSchedule:
tabledata = viewschedule.GetSectionData(SectionType.Body)
else:
tabledata = viewschedule.GetTableData().GetSectionData(SectionType.Body)
#
paraSchNames = []
nbrCol = tabledata.NumberOfColumns
nbrRow = tabledata.NumberOfRows
for r in range(nbrRow):
if r == 0:
for c in range(nbrCol):
paraSchNames.append(viewschedule.GetCellText(SectionType.Body, r, c))
else:
temp = []
unit_header = []
#get values by Rows
for c in range(nbrCol):
unitType = tabledata.GetCellSpec(r,c)
is_unitType = True if len(unitType.TypeId) > 0 else False
#
if tabledata.GetCellType(r,c) == CellType.Text or tabledata.GetCellType(r,c) == CellType.ParameterText:
valueCell = tabledata.GetCellText(r,c)
valueCell, unitstr = self.__to_Float(valueCell) if is_unitType else (valueCell, "")
temp.append(valueCell)
unit_header.append(unitstr)
#
elif tabledata.GetCellType(r,c) == CellType.Parameter:
try:
valueCell = viewschedule.GetParamValue(SectionType.Body, r , c )
valueCell, unitstr = self.__to_Float(valueCell) if is_unitType else (valueCell, "")
temp.append(valueCell)
unit_header.append(unitstr)
except:
valueCell = viewschedule.GetCellText(SectionType.Body, r , c )
valueCell, unitstr = self.__to_Float(valueCell) if is_unitType else (valueCell, "")
temp.append(valueCell)
unit_header.append(unitstr)
#
elif tabledata.GetCellType(r,c) == CellType.CalculatedValue:
valueCell = tabledata.GetCellCalculatedValue(r,c)
valueCell, unitstr = self.__to_Float(valueCell) if is_unitType else (valueCell, "")
temp.append(valueCell)
unit_header.append(unitstr)
if len(temp) >= 1:
# replace empty value by NaN
temp = [x if x != "" else np.nan for x in temp]
outvalue.append(temp)
df = pd.DataFrame(data = outvalue, columns= paraSchNames)
for nameCol, nameUnit in zip(df.columns, unit_header):
self.__class__.dictUnitValue[nameCol] = nameUnit
# remove blank line
df = df.drop(0, axis = 0)
return df
@property
def UnwrapDFrame(self):
"""
return the unwrap Panda DataFrame
"""
return self._df
def Append(self, *args, **kwargs):
"""
method to call the 'append' DataFrame method to concat 2 DataFrames
"""
if isinstance(args[0], WrapDFrame):
# uwrap fist item
args = list(args)
args[0] = args[0].UnwrapDFrame
args = tuple(args)
new_df = self.UnwrapDFrame.append(args, kwargs)
return WrapDFrame(new_df)
def ToHTML(self):
"""
convert DataFrame to Html and open it
"""
filename = my_path + '\\htmlDataFrame.html'
htmlDf = """\
<html>
<head>
<style>
table, th, td {{font-size:10pt; border:1px solid black; border-collapse:collapse; text-align:left;}}
th, td {{padding: 5px;}}
tr:nth-child(even) {{background: #E0E0E0;}}
tr:hover {{background: silver; cursor: pointer;}}
</style>
</head>
<body>
{0}
</body>
</html>
""".format(self.UnwrapDFrame.to_html())
#
# add unit in tooltip with bs4
soup = bs(htmlDf, 'html.parser')
tables = soup.find_all('table')
for table in tables:
rows = table.find_all('tr')
row = rows[0]
cols = row.find_all('th')
for ele in cols:
if ele.string is not None:
unit = self.__class__.dictUnitValue.get(ele.string.strip())
if unit is not None:
print("pass")
try:
ele["title"] = unit
except:
import traceback
self.lstError.append([ele.string, traceback.format_exc()])
htmlDf = soup.prettify()
#
#with open(filename, 'w') as f:
with codecs.open(filename, "w", encoding="utf-8") as f:
f.write( htmlDf)
webbrowser.open_new_tab(filename)
def __to_Float(self, x):
"""
convert value cell to float and get suffix unit
"""
sgA = re.match(r'([+-]?\d*.?\d+)\s(.+)', x)
sgB = re.match(r'([+-]?\d*.?\d+)', x)
if sgA is not None:
return float(sgA.group(1).replace(',','.')), sgA.group(2)
elif sgB is not None:
return float(sgB.group(1).replace(',','.')), ""
else:
return x, ""
OUT = WrapDFrame
  • Exemple en concaténant 2 Nomenclatures (1 nomenclature pour chaque niveau)

import clr
import sys
import re
import System

WrapDFrame = IN[0] # import class

viewschA = UnwrapElement(IN[1])
viewschB = UnwrapElement(IN[2])

# create DataFrame 1
objdfA = WrapDFrame(viewschA)
# create DataFrame 2
objdfB = WrapDFrame(viewschB)
# concat objects with same 'append' method of Pandas.DataFrame
objdfC = objdfA.Append(objdfB, ignore_index = False, sort = False)
# remove NaN values
dfC = objdfC.UnwrapDFrame.dropna()
# change Index with space Number
dfC = dfC.set_index("Numéro")
# add column 
dfC["DifferenceRT/Réelle"] = dfC.iloc[:, -1] - dfC.iloc[:, -2]
# Create an new instance of WrapDFrame with describe by Name of Space
objdfD = WrapDFrame(dfC.groupby(["Type d'espace"]).describe().sort_values(by=('DifferenceRT/Réelle', 'min')))
# view in Html
objdfD.ToHTML()
OUT = objdfD

Note :
La méthode 'Append' est une de méthode de remplacement (Overriding Method) qui appelle la méthode d'origine  'append' d'un DataFrame (avec les mêmes arguments), 'concatenate' pourrait par ailleurs être utilisée.

  • Exemple en utilisant un filtre 'OU' sur le DataFrame issue d'une Nomenclature
Pour ceux qui ont toujours voulu mettre un filtre 'OU' dans un filtre de Nomenclature ....

import clr
import sys
import re
import System
           
WrapDFrame = IN[0] # import class          

viewschAllLvl = UnwrapElement(IN[1])

# create DataFrame 1
objdfA = WrapDFrame(viewschAllLvl)
# remove nan values
dfC = objdfA.UnwrapDFrame.dropna()
# change Index with space Number
dfC = dfC.set_index("Numéro")
# Get only Level 1 and Level 2
dfC = dfC[(dfC["Niveau"] == "Level 1") | (dfC["Niveau"] == "Level 2")]
# add column 
dfC["DifferenceRT/Réelle"] = dfC.iloc[:, -1] - dfC.iloc[:, -2]
# Create an new instance of WrapDFrame with describe by Name of Space
objdfD = WrapDFrame(dfC.groupby(["Type d'espace"]).describe().sort_values(by=('DifferenceRT/Réelle', 'min')))
# view in Html
objdfD.ToHTML()
OUT = objdfD

Résultat



À noter que pour ce simple exemple un résultat similaire peut être obtenu via les propriétés de la nomenclature (sauf pour le tri)



Ressources :

Aperçu en Vidéo

Land Of Pirates by Alexander Nakarada
Link: https://filmmusic.io/song/4961-land-of-pirates
License: https://filmmusic.io/standard-license
Circus Monkey by Alexander Nakarada
Link: https://filmmusic.io/song/8423-circus-monkey
License: https://filmmusic.io/standard-license



Dans un prochain article, on verra quelques exemples avec Seaborn

🌟🌟🌟 Bonne Année et Meilleurs Vœux à tous 🌟🌟🌟

Related Posts:

0 commentaires:

Enregistrer un commentaire