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
- Exemple en utilisant un filtre 'OU' sur le DataFrame issue d'une 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)
0 commentaires:
Enregistrer un commentaire