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