Hello Rick,
We have now released the app. Unfortunately, we have noticed that when we change the input values in Step 2, the output data does not change. What could be the reason for this? When we change the inputs, the Power Shell shows us that the input values have been changed but the output has not.
In this example, we have reduced the âEigenverbrauchâ from 25% to 5%:
âstep_2â: Munch({âModultypâ: 0, âEigenverbrauchâ: 25, âInvestitionskostenâ: 2, âInstandhaltungskostenâ: 0.02, âFoerderbeitragâ: 6, âStrompreisâ: 0.3, âEinspeiseverguetungâ: 0.12, âZeitraumâ: 25})})
2024-10-08 19:56:44.684 INFO : External analysis not yet finished, waiting 1 seconds to retryâŠ
2024-10-08 19:56:45.870 INFO : External analysis not yet finished, waiting 2 seconds to retryâŠ
2024-10-08 19:56:47.977 INFO : External analysis not yet finished, waiting 3 seconds to retryâŠ
2024-10-08 19:56:51.078 INFO : External analysis not yet finished, waiting 4 seconds to retryâŠ
2024-10-08 19:56:55.368 INFO : External analysis completed successfully, returning results
{âEinspeiseverguetungâ: â1920 CHFâ, âEinnahmeJahrâ: â6719 CHFâ, âStromkosteneinsparungâ: â4800 CHFâ, âEinnahmenâ: â167975 CHF / Einnahmen in X Jahrenâ, âUnterhaltskostenâ: â2894 CHFâ, âKostenproJahrâ: â2894 CHFâ, âInvestitionskostenâ: â144710 CHFâ, âKostenGesamtâ: â72355 CHFâ, âRenditeâ: â5 %â, âAmortisationsdauerâ: â20 Jahreâ, âProduktionFassadeâ: â31997 kWh/aâ}
2024-10-08 19:56:55.551 INFO : Job (uid: 1022775) completed in 20826ms
, âstep_2â: Munch({âModultypâ: 0, âEigenverbrauchâ: 5, âInvestitionskostenâ: 300, âInstandhaltungskostenâ: 0.02, âFoerderbeitragâ: 6, âStrompreisâ: 0.3, âEinspeiseverguetungâ: 0.12, âZeitraumâ: 25})})
2024-10-08 19:58:44.373 INFO : External analysis not yet finished, waiting 1 seconds to retryâŠ
2024-10-08 19:58:45.566 INFO : External analysis not yet finished, waiting 2 seconds to retryâŠ
2024-10-08 19:58:47.751 INFO : External analysis not yet finished, waiting 3 seconds to retryâŠ
2024-10-08 19:58:50.848 INFO : External analysis not yet finished, waiting 4 seconds to retryâŠ
2024-10-08 19:58:55.153 INFO : External analysis completed successfully, returning results
{âEinspeiseverguetungâ: â1920 CHFâ, âEinnahmeJahrâ: â6719 CHFâ, âStromkosteneinsparungâ: â4800 CHFâ, âEinnahmenâ: â167975 CHF / Einnahmen in X Jahrenâ, âUnterhaltskostenâ: â2894 CHFâ, âKostenproJahrâ: â2894 CHFâ, âInvestitionskostenâ: â144710 CHFâ, âKostenGesamtâ: â72355 CHFâ, âRenditeâ: â5 %â, âAmortisationsdauerâ: â20 Jahreâ, âProduktionFassadeâ: â31997 kWh/aâ}
2024-10-08 19:58:55.342 INFO : Job (uid: 1022781) completed in 20744ms
this is our app.py code:
from io import BytesIO
import json
import rhino3dm
from viktor import ViktorController, File
from viktor.parametrization import NumberField, FileField, Text, ViktorParametrization, Step, OptionField, \
OptionListElement, GeometrySelectField, GeometryMultiSelectField
from viktor.external.generic import GenericAnalysis
from viktor.views import GeometryAndDataView, GeometryAndDataResult, DataGroup, DataItem, GeometryView, GeometryResult
class Parametrization(ViktorParametrization):
step_1 = Step('Modell_Modultyp', views='choose_geom')
step_1.intro = Text(
"## PV Machbarkeitsanalyse [PARAMETRICZONE](https://www.parametriczone.ch/) \n Dieses Script berechnet die ersten Kennzahlen welche fĂŒr die Realisierung einer PV-Anlage an einer Fassade benötigt werden. Dabei stĂŒtzt sich die Berechnung auf angenommene Standardwerte. Im zweiten Tab können die Input Werte beliebig verĂ€ndert werden, zu jedem Wert ist fĂŒr die Nachvollziehbarkeit eine Beschreibung hinterlegt. \n\n In naher Zukunft wird eine neue App verfĂŒgbar sein, die eine prĂ€zisere Berechnung der Leistungswerte von PV-Fassaden ermöglicht. Sie wird das optimal geeignete PV-Modul fĂŒr jede Fassade identifizieren, die Anordnung der Module automatisch planen und optimieren sowie die ideale Stringverschaltung bestimmen. Dadurch wird der Einsatz kostenintensiver und störanfĂ€lliger Leistungsoptimierer ĂŒberflĂŒssig, was zu einer effizienteren und zuverlĂ€ssigeren Nutzung von PV-Fassaden fĂŒhrt. \n\n\n Laden sie Ihre FassadenflĂ€che und Kontextgeometrien als 3dm Datei hoch. Wichtig ist, dass die Nordrichtung in der hochgeladenen Datei richtig eingestellt ist und alle Geometien mĂŒssen einen Namen enthalten, damit sie vom Script erkannt werden können. Falls sie Probleme mit ihrem hochgeladenen Modell haben melden sie sich hier: Mail: pvplanningsyst@gmail.com / [Vorlage Modell](https://www.parametriczone.ch/) \n\n\n\n Geben sie die vorgegebenen Parameter an:")
# Input fields
step_1.selected_geometry = GeometrySelectField("WÀhle die zu berechnende FassadenflÀche aus")
step_1.selected_geometries = GeometryMultiSelectField("WĂ€hle die Umgebungsgeometrie aus")
step_1.Modell = FileField('Modell', file_types=['.3dm'], max_size=100_000_000)
step_2 = Step('Angaben', views='run_grasshopper')
step_2.intro = Text(
"## Angaben der Inputparameter \n Zum Nachvollziehen werden die Inputparameter untenstehend beschrieben: \n\n Modultyp: 4 Standard-Modultypen sind vorgegeben \n\n\n Eigenverbrauch: Liegt durchschnittlich bei ca. 20 bis 35 Prozent \n\n\n\n Investitionskosten: Durchschnittlich 380 CHF/m2 (inkl. Installation) unter folgendem Link finden sie Richtwerte der Schweiz:[Investitionskosten Richtwerte](https://www.photovoltaikmeister.ch/arten/photovoltaik-fassade/) \n\n\n\n\n Instandhaltungskosten: Erfahrungswert 2 Prozent der Investitionskosten \n\n\n\n\n\n Foerderbeitrag: [FörderbeitrĂ€ge Beschrieb](https://www.swissolar.ch/de/wissen/wirtschaftlichkeit/foerderung/pv-foerderung) [ZĂŒrich](https://www.stadt-zuerich.ch/energie/de/index/foerderung/alle-foerderprogramme/photovoltaik.html) [Luzern](https://www.stadtluzern.ch/dienstleistungeninformation/6253) [Zug]( (in Prozent der Investitionskosten (falls die Investitionskosten in CHF angegeben sind muss der Wert berechnet werden und als Prozent Angabe eingegeben werden)) \n\n\n\n\n\n\n Strompreis: [Strompreise pro Gemeinde](https://www.nzz.ch/wirtschaft/die-strompreise-sinken-wieder-in-welchen-gemeinden-die-tarife-naechstes-jahr-besonders-guenstig-sind-und-wo-sie-entgegen-dem-nationalen-trend-steigen-ld.1846692) (in CHF/kwh) \n\n\n\n\n\n\n\n EinspeisevergĂŒtung: [EinspeisevergĂŒtungĂŒbersicht Regionen](https://www.vese.ch/pvtarif/) (in CHF/kwh) \n\n\n\n\n\n\n\n\n Zeitraum: Zeitraum der Berechnung (Durchschnittliche Lebensdauer PV-Anlage = 20-25 Jahre)"
)
step_2.Modultyp = OptionField('Modultyp', options=[OptionListElement('0', 'L 1300x875'),
OptionListElement('1', 'Q 1300x720'),
OptionListElement('2', 'M 985x875'),
OptionListElement('3', 'S 985x720')], default=0)
step_2.Eigenverbrauch = NumberField("Eigenverbrauch in % des Gesamtertrages", default=25)
step_2.Investitionskosten = NumberField( "Investitionskosten(Fr./m2)", default=380 )
step_2.Instandhaltungskosten = NumberField( "Instandhaltungskosten in % der Investitionskosten", default=0.02 )
step_2.Foerderbeitrag = NumberField("Förderbeitrag %", default=6)
step_2.Strompreis = NumberField("Strompreis(Fr./kWh)", default=0.3)
step_2.Einspeiseverguetung = NumberField("Einspeiseverguetung(Fr./kWh)", default=0.12)
step_2.Zeitraum = NumberField("Zeitraum(Jahre)", default=25)
class Controller(ViktorController):
label = 'My Entity Type'
parametrization = Parametrization(width=30)
@GeometryView("Geometry", duration_guess=0, x_axis_to_right=True)
def choose_geom(self, params, **kwargs):
return GeometryResult(geometry=params.step_1.Modell.file, geometry_type="3dm")
@GeometryAndDataView("Geometry", duration_guess=0, update_label='Run Grasshopper')
def run_grasshopper(self, params, **kwargs):
rhino_upload = params.step_1['Modell'].file
# Read the rhino file
gh_file = rhino3dm.File3dm.FromByteArray(rhino_upload.getvalue_binary())
encoded_facade = []
encoded_context = []
# Durchlaufen aller Objekte und Kodieren der zum gewĂŒnschten Layer gehörenden
for obj in gh_file.Objects:
if params.step_1.selected_geometry == obj.Attributes.Name:
geom = obj.Geometry
# Kodieren des Geometrieobjekts
encoded_data = json.dumps(geom.Encode())
encoded_facade.append(encoded_data)
for obj in gh_file.Objects:
if obj.Attributes.Name in params.step_1.selected_geometries:
#if params.step_1.selected_geometries == obj.Attributes.Name:
geom = obj.Geometry
# Kodieren des Geometrieobjekts
encoded_data2 = json.dumps(geom.Encode())
encoded_context.append(encoded_data2)
# Erstellen einer JSON-Datei aus den Eingabeparametern
# new dictinary
params.step_1.pop('Modell')
params.step_1['encoded_geometry'] = encoded_facade
params.step_1['encoded_geometry2'] = encoded_context
input_json = json.dumps(params.step_1) # Konvertieren aller Parameter inklusive der Geometrie in JSON
print(params)
# Erstellen der Eingabedateien fĂŒr die Analyse
files = [
('input.json', BytesIO(bytes(input_json, 'utf8'))),
]
# Run the Grasshopper analysis and obtain the output files
generic_analysis = GenericAnalysis(files=files, executable_key="run_grasshopper", output_filenames=[
"geometry.3dm", "output.json"
])
generic_analysis.execute(timeout=120)
params.step_1.rhino_3dm_file = generic_analysis.get_output_file("geometry.3dm", as_file=True)
params.step_2.output_values: File = generic_analysis.get_output_file("output.json", as_file=True)
# Create a DataGroup object to display output data
params.step_2.output_dict = json.loads(params.step_2.output_values.getvalue())
print(params.step_2.output_dict)
params.step_2.data_group = DataGroup(
*[DataItem(key.replace("_", " "), val) for key, val in params.step_2.output_dict.items()]
)
return GeometryAndDataResult(geometry=params.step_1.rhino_3dm_file, geometry_type="3dm", data=params.step_2.data_group)
and this is our run grasshopper.py script:
# Pip install required packages
import os
import json
import compute_rhino3d.Grasshopper as gh
import compute_rhino3d.Util
import rhino3dm
# Set the compute_rhino3d.Util.url, default URL is http://localhost:6500/
compute_rhino3d.Util.url = 'http://localhost:6500/'
# Define path to local working directory
workdir = os.getcwd() + '\\'
# Read input parameters from JSON file
with open(workdir + 'input.json') as f:
input_params = json.load(f)
# Create the input DataTree
input_trees = []
for key, value in input_params.items():
tree = gh.DataTree(key)
if isinstance(value,list) :
tree.Append([{0}], value)
else:
tree.Append([{0}], [str(value)])
input_trees.append(tree)
# Evaluate the Grasshopper definition
output = gh.EvaluateDefinition(
workdir + 'sample_box_grasshopper.gh',
input_trees
)
def get_value_from_tree(datatree: dict, param_name: str):
"""Get first value in datatree that matches given param_name"""
for val in datatree['values']:
if val["ParamName"] == param_name:
return val['InnerTree']['{0}'][0]['data']
# Create a new rhino3dm file and save resulting geometry to file
file = rhino3dm.File3dm()
output_geometry = get_value_from_tree(output, "Geometry")
obj = rhino3dm.CommonObject.Decode(json.loads(output_geometry))
file.Objects.AddMesh(obj)
# Save Rhino file to working directory
file.Write(workdir + 'geometry.3dm', 7)
units = {
"Einspeiseverguetung": "CHF",
"EinnahmeJahr": "CHF",
"Stromkosteneinsparung": "CHF",
"Einnahmen": "CHF / Einnahmen in X Jahren",
"Unterhaltskosten": "CHF",
"KostenproJahr": "CHF",
"Investitionskosten": "CHF",
"KostenGesamt": "CHF",
"Rendite": "%",
"Amortisationsdauer": "Jahre",
"ProduktionFassade": "kWh/a"
}
# Parsing und Einheiten hinzufĂŒgen
output_values = {}
for key in ["Einspeiseverguetung", "EinnahmeJahr", "Stromkosteneinsparung", "Einnahmen", "Unterhaltskosten", "KostenproJahr", "Investitionskosten", "KostenGesamt", "Rendite", "Amortisationsdauer", "ProduktionFassade"]:
val = get_value_from_tree(output, key)
val = val.replace("\"", "")
# Einheit hinzufĂŒgen
if key in units:
output_values[key] = f"{val} {units[key]}"
else:
output_values[key] = val
# Speichern der JSON-Datei im Arbeitsverzeichnis
with open(workdir + 'output.json', 'w') as f:
json.dump(output_values, f, ensure_ascii=False, indent=4)
thanks alot
cheers Samuel & Marco