From a8f04731e688f9b61290fc057d3b0a81d551eef2 Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Wed, 24 Apr 2024 15:30:05 +0200 Subject: [PATCH 1/7] #15 request: cast type, add range. UI: clicker --- src/aixd_grasshopper/api.py | 12 +++ .../components/aixd_DataObjectsNames/code.py | 33 ++++--- .../components/aixd_Generator/code.py | 89 +++++++++++++------ .../components/aixd_Generator/metadata.json | 31 +++++-- src/aixd_grasshopper/controller.py | 29 +++++- src/aixd_grasshopper/gh_ui.py | 5 ++ 6 files changed, 150 insertions(+), 49 deletions(-) diff --git a/src/aixd_grasshopper/api.py b/src/aixd_grasshopper/api.py index dfdf90c..91e9acb 100644 --- a/src/aixd_grasshopper/api.py +++ b/src/aixd_grasshopper/api.py @@ -164,6 +164,18 @@ def get_dataobject_names_from_block(): return response +@app.route("/get_dataobject_types", methods=["POST"]) +def get_dataobject_types(): + data = request.data + data = json.loads(data) + session_id = data["session_id"] + sc = SessionController.create(session_id) + + result = sc.get_dataobject_types() + response = json.dumps(result, cls=DataEncoder) + return response + + @app.route("/plot_distrib_attributes", methods=["POST"]) def plot_distrib_attributes(): data = request.data diff --git a/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py b/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py index 1dfd8bc..a3c90b1 100644 --- a/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py +++ b/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py @@ -1,4 +1,7 @@ -# this code has been inspired by a forum post by "chanley" (2018.11.02) https://discourse.mcneel.com/t/can-i-instantiate-specific-component-on-the-canvas-with-a-script-python/74204/8 +# flake8: noqa + +# this code has been inspired by a forum post by "chanley" (2018.11.02) +# https://discourse.mcneel.com/t/can-i-instantiate-specific-component-on-the-canvas-with-a-script-python/74204/8 import Grasshopper import System.Drawing as sd @@ -9,21 +12,22 @@ comp = ghenv.Component ghdoc = comp.OnPingDocument() + def make_Panel(NickName, UserText, Pivot, Bounds): try: Panel = Grasshopper.Kernel.Special.GH_Panel() Panel.NickName = NickName Panel.UserText = UserText Panel.Properties.Colour = sd.Color.White - #Panel.Properties.Font = sd.Font("Trebuchet MS", 10) + # Panel.Properties.Font = sd.Font("Trebuchet MS", 10) Panel.Properties.Multiline = False Panel.Properties.DrawIndices = False Panel.Properties.DrawPaths = False - ghdoc.AddObject(Panel,False,ghdoc.ObjectCount+1) + ghdoc.AddObject(Panel, False, ghdoc.ObjectCount + 1) Panel.Attributes.Pivot = Pivot Panel.Attributes.Bounds = Bounds - except Exception, ex: - ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning,str(ex)) + except Exception(ex): + ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning, str(ex)) x = ghenv.Component.Attributes.DocObject.Attributes.Bounds.Right @@ -36,20 +40,21 @@ def make_Panel(NickName, UserText, Pivot, Bounds): errors = "" if not datablock: - datablock = [ "design_parameters","performance_attributes","inputML","outputML"] #all datablock names + datablock = ["design_parameters", "performance_attributes", "inputML", "outputML"] # all datablock names if get_names: - + for i, datablock_nickname in enumerate(datablock): panel_title = datablock_nickname - response = get_dataobject_names_from_block(session_id(),datablock_nickname) - text_items = response['names'] + response = get_dataobject_names_from_block(session_id(), datablock_nickname) + text_items = response["names"] if text_items: - text_str= "\n".join(text_items) - pt = sd.PointF(x + gap + i*gap, y + i*gap) - rect = sd.RectangleF(0,0,w,h) + text_str = "\n".join(text_items) + pt = sd.PointF(x + gap + i * gap, y + i * gap) + rect = sd.RectangleF(0, 0, w, h) make_Panel(panel_title, text_str, pt, rect) else: - errors+=response['msg']+"\n" + errors += response["msg"] + "\n" -if errors: ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning,str(errors)) +if errors: + ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning, str(errors)) diff --git a/src/aixd_grasshopper/components/aixd_Generator/code.py b/src/aixd_grasshopper/components/aixd_Generator/code.py index d090bfd..e3cc966 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/code.py +++ b/src/aixd_grasshopper/components/aixd_Generator/code.py @@ -1,28 +1,30 @@ # flake8: noqa -# reformat request data -# original input should be a list of strings -# each string should have a form: attribute_name:value +from aixd_grasshopper.gh_ui import request_designs, get_dataobject_types +from aixd_grasshopper.gh_ui_helper import session_id, component_id +from scriptcontext import sticky as st + +cid = component_id(session_id(), ghenv.Component, "run_generation") +item = component_id(session_id(), ghenv.Component, "pick_sample") + +""" +requested_values: a multiline string with variable_name:values tuples. + +""" if not n_designs or n_designs < 1: n_designs = 1 -request_ok = True -try: - request_dict = {} - for rv in requested_values: - rv = rv.strip() - k, v = rv.split(":") - v = float(v) - request_dict[k] = v - print(request_dict) - request_ok = True -except: - request_ok = False - # raise ValueError("Request could not be processed") - -# ------------------------------------------------------------------------------- +def recast_type(value, typename): + if typename == "real": + return float(value) + if typename == "integer": + return int(value) + if typename == "categorical": + return str(value) + if typename == "bool": + return bool(value) class wrapper: @@ -33,23 +35,56 @@ def __repr__(self): return "Generated design" -# ------------------------------------------------------------------------------- +def reformat_request(request_string): + types = get_dataobject_types(session_id())["dataobject_types"] + request_dict = {} + for rv in request_string: + rv = rv.strip() + + # split into name:value(s) + k, v = rv.split(":") + + if k not in types.keys(): + raise ValueError( + "'{0}' is not a valid variable name. There is not variable with this name in the dataset.".format(k) + ) + + # check if a list or a single value + if v[0] == "[" and v[-1] == "]": + v = v[1:-1] + v = v.split(",") + v = [recast_type(vi, types[k]) for vi in v] + else: + v = recast_type(v, types[k]) + request_dict[k] = v + return request_dict -from scriptcontext import sticky as st -from aixd_grasshopper.gh_ui import request_designs -from aixd_grasshopper.gh_ui_helper import component_id -from aixd_grasshopper.gh_ui_helper import session_id +# ------------------------------------------------------------------------------- -cid = component_id(session_id(), ghenv.Component, "Generator") +if clear and cid in st.keys(): + del st[cid] + st[item] = None + ghenv.Component.Message = "no samples" -if run and request_ok: +if generate and requested_values: + + request_dict = reformat_request(requested_values) + st[item] = 0 ghenv.Component.Message = "Running" st[cid] = request_designs(session_id(), request_dict, n_designs) ghenv.Component.Message = "Finished" +if pick_previous: + st[item] -= 1 +if pick_next: + st[item] += 1 + if cid in st.keys(): - predictions = [wrapper(x) for x in result] - ghenv.Component.Message = "Ready" + samples = st[cid]["generated"] + n = len(samples) + i = st[item] % n + sample = wrapper(samples[i]) + ghenv.Component.Message = "sample {}/{}".format(i + 1, n) diff --git a/src/aixd_grasshopper/components/aixd_Generator/metadata.json b/src/aixd_grasshopper/components/aixd_Generator/metadata.json index fb877cf..858b290 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/metadata.json +++ b/src/aixd_grasshopper/components/aixd_Generator/metadata.json @@ -13,7 +13,7 @@ "name": "requested_values", "description": "List of requested values, each formatted as a string with the following format: 'variable_name:value'.", "typeHintID": "str", - "scriptParamAccess": 0 + "scriptParamAccess": 1 }, { "name": "n_designs", @@ -22,18 +22,35 @@ "scriptParamAccess": 0 }, { - "name": "run", + "name": "generate", "description": "Set to True to start the generation process.", - "typeHintID": "none", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "clear", + "description": "Forget the previously generated designs.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "pick_previous", + "description": "Iterate backward through the list of generated designs, instantiate the previous sample.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "pick_next", + "description": "Iterate forward through the list of generated designs, instantiate the next sample.", + "typeHintID": "bool", "scriptParamAccess": 0 } ], - "outputParameters": [ { - "name": "predicions", - "description": "List of generated designs." + "name": "sample", + "description": "Selected sample to instantiate in the parametric model." } ] } -} +} \ No newline at end of file diff --git a/src/aixd_grasshopper/controller.py b/src/aixd_grasshopper/controller.py index 9ed0ad4..7d159e2 100644 --- a/src/aixd_grasshopper/controller.py +++ b/src/aixd_grasshopper/controller.py @@ -11,7 +11,7 @@ import torch from aixd.data.data_blocks import DesignParameters from aixd.data.data_blocks import PerformanceAttributes -from aixd.data.data_objects import DataInt +from aixd.data.data_objects import DataInt, DataBool, DataCategorical, DataReal from aixd.data.dataset import Dataset from aixd.data.utils_data import reformat_dataframe_to_dataframeflat from aixd.data.utils_data import reformat_dataframeflat_to_dict @@ -229,6 +229,33 @@ def get_dataobject_names_from_block(self, datablock_nickname): return {"msg": "", "names": self.datamodule.output_ml_dblock.names_list} return {"msg": f"Wrong block nickname: {datablock_nickname}.", "names": []} + def get_dataobject_types(self): + """ + Returns names of the data types of the dataobjects in the dataset. + """ + if not self.dataset: + error = "Dataset is not loaded." + raise ValueError(error) + + all_dataobjects = self.dataset.data_objects + # cannot use this because boolean are declared as categorical + # dataobject_types = {d.name: d.type for d in all_dataobjects} + + dataobject_types = {} + for d in all_dataobjects: + if isinstance(d, DataInt): + dataobject_types[d.name] = "integer" + elif isinstance(d, DataReal): + dataobject_types[d.name] = "real" + elif isinstance(d, DataCategorical): + dataobject_types[d.name] = "categorical" + elif isinstance(d, DataBool): + dataobject_types[d.name] = "boolean" + else: + dataobject_types[d.name] = "unsupported" + + return {"msg": "", "dataobject_types": dataobject_types} + def get_design_parameters(self): # TODO: rename if not self.dataset: diff --git a/src/aixd_grasshopper/gh_ui.py b/src/aixd_grasshopper/gh_ui.py index 5d6c96d..e1c7444 100644 --- a/src/aixd_grasshopper/gh_ui.py +++ b/src/aixd_grasshopper/gh_ui.py @@ -40,6 +40,11 @@ def get_dataobject_names_from_block(session_id, datablock_nickname): return http_post_request(action="get_dataobject_names_from_block", data=data) +def get_dataobject_types(session_id): + data = {"session_id": session_id} + return http_post_request(action="get_dataobject_types", data=data) + + def get_one_sample(session_id, i): data = {"session_id": session_id, "item": i} return http_post_request(action="get_one_sample", data=data) From 7ff48690ff719bc150ba60120885281632af881f Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Wed, 1 May 2024 11:01:43 +0200 Subject: [PATCH 2/7] rebasing --- .../components/aixd_DatasetOneSample/code.py | 114 ++---------------- .../aixd_DatasetOneSample/metadata.json | 4 +- 2 files changed, 13 insertions(+), 105 deletions(-) diff --git a/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py b/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py index b106f84..522a2ff 100644 --- a/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py +++ b/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py @@ -1,117 +1,25 @@ # flake8: noqa -import Grasshopper +from scriptcontext import sticky as st from aixd_grasshopper.gh_ui import get_one_sample from aixd_grasshopper.gh_ui_helper import component_id +from aixd_grasshopper.gh_ui_helper import instantiate_sample, sample_summary from aixd_grasshopper.gh_ui_helper import session_id +cid = component_id(session_id(), ghenv.Component, "GetOneSample") + + if not item: item = -1 if get: - sample = get_one_sample(session_id(), item) - - -# TODO - clean up: - - -# ------------------------------------------------------------------------------- -def find_component_by_nickname(ghdoc, component_nickname): - found = [] - # all_objects = ghdoc.Objects - all_objects = ghenv.Component.OnPingDocument().Objects - for obj in all_objects: - - if obj.Attributes.PathName == component_nickname: - # if obj.NickName == component_nickname: - found.append(obj) - - if not found: - print("No ghcomponent found with a nickname {}.".format(component_nickname)) - return - if len(found) > 1: - print("{len(found)} ghcomponents found with the nickname {} - will return None.".format(component_nickname)) - return - return found[0] - - -def set_value(component, val): - component.Script_ClearPersistentData() - component.AddPersistentData(val) - component.ExpireSolution(True) - - -def set_values(component, vals): - """ - Data type of vals must match the type of the component. - See TYPES list. - """ - ghtype = TYPES[component.TypeName] - - component.Script_ClearPersistentData() - if not isinstance(vals, list): - vals = [vals] - for v in vals: - component.PersistentData.Append(ghtype(v)) - component.ExpireSolution(True) - - -TYPES = { - "Arc": Grasshopper.Kernel.Types.GH_Arc, - "Boolean": Grasshopper.Kernel.Types.GH_Boolean, - "Box": Grasshopper.Kernel.Types.GH_Box, - "Brep": Grasshopper.Kernel.Types.GH_Brep, - "Circle": Grasshopper.Kernel.Types.GH_Circle, - "ComplexNumber": Grasshopper.Kernel.Types.GH_ComplexNumber, - "Curve": Grasshopper.Kernel.Types.GH_Curve, - "Guid": Grasshopper.Kernel.Types.GH_Guid, - "Integer": Grasshopper.Kernel.Types.GH_Integer, - "Interval": Grasshopper.Kernel.Types.GH_Interval, - "Interval2D": Grasshopper.Kernel.Types.GH_Interval2D, - "Line": Grasshopper.Kernel.Types.GH_Line, - "Mesh": Grasshopper.Kernel.Types.GH_Mesh, - "Number": Grasshopper.Kernel.Types.GH_Number, - "Plane": Grasshopper.Kernel.Types.GH_Plane, - "Point": Grasshopper.Kernel.Types.GH_Point, - "Rectangle": Grasshopper.Kernel.Types.GH_Rectangle, - "String": Grasshopper.Kernel.Types.GH_String, - "SubD": Grasshopper.Kernel.Types.GH_SubD, - "Surface": Grasshopper.Kernel.Types.GH_Surface, - "Vector": Grasshopper.Kernel.Types.GH_Vector, - "Text": Grasshopper.Kernel.Types.GH_String, -} - - -# ------------------------------------------------------------------------------- - - -if sample: - - for dp_name, dp_vals in sample["design_parameters"].items(): - # print dp_name, dp_vals - component_name = "GENERATED_{}".format(dp_name) - component = find_component_by_nickname(ghdoc, component_name) - print("{}: {}".format(component_name, dp_vals)) - - if not dp_vals: - print("empty values for {}".format(comp)) - else: - set_values(component, dp_vals) - + st[cid] = get_one_sample(session_id(), item) -# ------------------------------------------------------------------------------- -if sample: - txt = "" - txt += "Design Parameters:\n\n" - for name, values in sample["design_parameters"].items(): - txt += "{}: {}\n".format(name, values) - txt += "\n" - txt += "Performance Attributes:\n\n" - for name, values in sample["performance_attributes"].items(): - txt += "{}: {}\n".format(name, values) +ghdoc = ghenv.Component.OnPingDocument() - sample = txt - trigger = False +if cid in st.keys(): + sample = st[cid] + instantiate_sample(ghdoc, sample) -# sample = None + sample_summary = sample_summary(sample) diff --git a/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json b/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json index 7681d8c..47a62c7 100644 --- a/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json +++ b/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json @@ -1,6 +1,6 @@ { "name": "DatasetOneSample", - "nickname": "OneSample", + "nickname": "DatasetOneSample", "category": "AIXD", "subcategory": "2 Dataset", "description": "Retrieves one sample from the dataset (at a given or random index) and instantiates it in the parametric model.", @@ -25,7 +25,7 @@ "outputParameters": [ { - "name": "sample", + "name": "sample_summary", "description": "Summary of the retrieved sample." } ] From 2bfd0efd0221f735b6961b9e95a03f31ae9af264 Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Wed, 1 May 2024 11:02:35 +0200 Subject: [PATCH 3/7] rebasing --- .../components/aixd_Generator/code.py | 19 ++++++++++--- .../components/aixd_Generator/metadata.json | 6 ++++- src/aixd_grasshopper/gh_ui_helper.py | 27 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/aixd_grasshopper/components/aixd_Generator/code.py b/src/aixd_grasshopper/components/aixd_Generator/code.py index e3cc966..676bcdb 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/code.py +++ b/src/aixd_grasshopper/components/aixd_Generator/code.py @@ -1,9 +1,14 @@ # flake8: noqa -from aixd_grasshopper.gh_ui import request_designs, get_dataobject_types -from aixd_grasshopper.gh_ui_helper import session_id, component_id from scriptcontext import sticky as st +from aixd_grasshopper.gh_ui import get_dataobject_types +from aixd_grasshopper.gh_ui import request_designs +from aixd_grasshopper.gh_ui_helper import component_id +from aixd_grasshopper.gh_ui_helper import instantiate_sample +from aixd_grasshopper.gh_ui_helper import sample_summary +from aixd_grasshopper.gh_ui_helper import session_id + cid = component_id(session_id(), ghenv.Component, "run_generation") item = component_id(session_id(), ghenv.Component, "pick_sample") @@ -86,5 +91,13 @@ def reformat_request(request_string): samples = st[cid]["generated"] n = len(samples) i = st[item] % n - sample = wrapper(samples[i]) + ghenv.Component.Message = "sample {}/{}".format(i + 1, n) + + sample_dict = samples[i] + ghdoc = ghenv.Component.OnPingDocument() + instantiate_sample(ghdoc, sample_dict) # sends design parameter values to the parametric model + + # --- output --- + sample_txt = sample_summary(sample_dict) + sample = wrapper(samples[i]) diff --git a/src/aixd_grasshopper/components/aixd_Generator/metadata.json b/src/aixd_grasshopper/components/aixd_Generator/metadata.json index 858b290..f6a5868 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/metadata.json +++ b/src/aixd_grasshopper/components/aixd_Generator/metadata.json @@ -49,7 +49,11 @@ "outputParameters": [ { "name": "sample", - "description": "Selected sample to instantiate in the parametric model." + "description": "Selected sample." + }, + { + "name": "sample_txt", + "description": "Summary of the selected sample." } ] } diff --git a/src/aixd_grasshopper/gh_ui_helper.py b/src/aixd_grasshopper/gh_ui_helper.py index 52a7f0f..4ec4c94 100644 --- a/src/aixd_grasshopper/gh_ui_helper.py +++ b/src/aixd_grasshopper/gh_ui_helper.py @@ -169,6 +169,33 @@ def ghparam_get_values(component, compute=False): return [x.Value for x in component.VolatileData[0]] +def sample_summary(sample_dict): + txt = "" + txt += "Design Parameters:\n\n" + for name, values in sample_dict["design_parameters"].items(): + txt += "{}: {}\n".format(name, values) + txt += "\n" + txt += "Performance Attributes:\n\n" + for name, values in sample_dict["performance_attributes"].items(): + txt += "{}: {}\n".format(name, values) + return txt + + +def instantiate_sample(ghdoc, sample_dict): + """ + Apply design parameter values to the parametric model. + """ + + for dp_name, dp_vals in sample_dict["design_parameters"].items(): + component_name = "GENERATED_{}".format(dp_name) + component = find_component_by_nickname(ghdoc, component_name) + + if not dp_vals: + print("No values for {}!".format(dp_name)) + else: + ghparam_set_values(component, dp_vals) + + def convert_str_to_bitmap(base64_imgstr, scale=1.0): """Get image from string and rescale.""" From a297bc73e37eb63a2d437d09267809e573685cbd Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Thu, 2 May 2024 21:27:36 +0200 Subject: [PATCH 4/7] #15 generator comp. cleanup --- .../components/aixd_Generator/code.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/aixd_grasshopper/components/aixd_Generator/code.py b/src/aixd_grasshopper/components/aixd_Generator/code.py index 676bcdb..6651811 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/code.py +++ b/src/aixd_grasshopper/components/aixd_Generator/code.py @@ -6,7 +6,7 @@ from aixd_grasshopper.gh_ui import request_designs from aixd_grasshopper.gh_ui_helper import component_id from aixd_grasshopper.gh_ui_helper import instantiate_sample -from aixd_grasshopper.gh_ui_helper import sample_summary +from aixd_grasshopper.gh_ui_helper import sample_summary as sample_summary_f from aixd_grasshopper.gh_ui_helper import session_id cid = component_id(session_id(), ghenv.Component, "run_generation") @@ -14,7 +14,6 @@ """ requested_values: a multiline string with variable_name:values tuples. - """ if not n_designs or n_designs < 1: @@ -32,14 +31,6 @@ def recast_type(value, typename): return bool(value) -class wrapper: - def __init__(self, dict): - self.dict = dict - - def __repr__(self): - return "Generated design" - - def reformat_request(request_string): types = get_dataobject_types(session_id())["dataobject_types"] request_dict = {} @@ -96,8 +87,7 @@ def reformat_request(request_string): sample_dict = samples[i] ghdoc = ghenv.Component.OnPingDocument() - instantiate_sample(ghdoc, sample_dict) # sends design parameter values to the parametric model + instantiate_sample(ghdoc, sample_dict) # sends design parameter values to the parametric model # --- output --- - sample_txt = sample_summary(sample_dict) - sample = wrapper(samples[i]) + sample_summary = "Predicted/Generated:\n--------------------\n\n" + sample_summary_f(sample_dict) From 70851259b8426055c447d5b21481c89363f75f40 Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Thu, 2 May 2024 21:39:20 +0200 Subject: [PATCH 5/7] #15 move request formatting to helper module --- .../components/aixd_Generator/code.py | 43 ++----------------- .../components/aixd_Generator/metadata.json | 6 +-- src/aixd_grasshopper/gh_ui_helper.py | 41 ++++++++++++++++++ 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/aixd_grasshopper/components/aixd_Generator/code.py b/src/aixd_grasshopper/components/aixd_Generator/code.py index 6651811..ba904a3 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/code.py +++ b/src/aixd_grasshopper/components/aixd_Generator/code.py @@ -8,6 +8,7 @@ from aixd_grasshopper.gh_ui_helper import instantiate_sample from aixd_grasshopper.gh_ui_helper import sample_summary as sample_summary_f from aixd_grasshopper.gh_ui_helper import session_id +from aixd_grasshopper.gh_ui_helper import reformat_request cid = component_id(session_id(), ghenv.Component, "run_generation") item = component_id(session_id(), ghenv.Component, "pick_sample") @@ -19,44 +20,6 @@ if not n_designs or n_designs < 1: n_designs = 1 - -def recast_type(value, typename): - if typename == "real": - return float(value) - if typename == "integer": - return int(value) - if typename == "categorical": - return str(value) - if typename == "bool": - return bool(value) - - -def reformat_request(request_string): - types = get_dataobject_types(session_id())["dataobject_types"] - request_dict = {} - - for rv in request_string: - rv = rv.strip() - - # split into name:value(s) - k, v = rv.split(":") - - if k not in types.keys(): - raise ValueError( - "'{0}' is not a valid variable name. There is not variable with this name in the dataset.".format(k) - ) - - # check if a list or a single value - if v[0] == "[" and v[-1] == "]": - v = v[1:-1] - v = v.split(",") - v = [recast_type(vi, types[k]) for vi in v] - else: - v = recast_type(v, types[k]) - request_dict[k] = v - return request_dict - - # ------------------------------------------------------------------------------- @@ -67,7 +30,9 @@ def reformat_request(request_string): if generate and requested_values: - request_dict = reformat_request(requested_values) + variable_types = get_dataobject_types(session_id())["dataobject_types"] + + request_dict = reformat_request(requested_values, variable_types) st[item] = 0 ghenv.Component.Message = "Running" st[cid] = request_designs(session_id(), request_dict, n_designs) diff --git a/src/aixd_grasshopper/components/aixd_Generator/metadata.json b/src/aixd_grasshopper/components/aixd_Generator/metadata.json index f6a5868..baebdba 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/metadata.json +++ b/src/aixd_grasshopper/components/aixd_Generator/metadata.json @@ -48,12 +48,8 @@ ], "outputParameters": [ { - "name": "sample", + "name": "sample_summary", "description": "Selected sample." - }, - { - "name": "sample_txt", - "description": "Summary of the selected sample." } ] } diff --git a/src/aixd_grasshopper/gh_ui_helper.py b/src/aixd_grasshopper/gh_ui_helper.py index 4ec4c94..7e5eaad 100644 --- a/src/aixd_grasshopper/gh_ui_helper.py +++ b/src/aixd_grasshopper/gh_ui_helper.py @@ -208,3 +208,44 @@ def convert_str_to_bitmap(base64_imgstr, scale=1.0): size = Size(bitmap.Width * scale, bitmap.Height * scale) bitmap = Bitmap(bitmap, size) return bitmap + + +def recast_type(value, typename): + if typename == "real": + return float(value) + if typename == "integer": + return int(value) + if typename == "categorical": + return str(value) + if typename == "bool": + return bool(value) + + +def reformat_request(request_string, variable_types): + """ + Reformats the request string into a dictionary with the correct types. + variable_types: dictionary with variable names as keys and types ('real', 'int', 'categorical', 'bool') as values, + used to restore the data type. + """ + request_dict = {} + + for rv in request_string: + rv = rv.strip() + + # split into name:value(s) + k, v = rv.split(":") + + if k not in variable_types.keys(): + raise ValueError( + "'{0}' is not a valid variable name. There is not variable with this name in the dataset.".format(k) + ) + + # check if a list or a single value + if v[0] == "[" and v[-1] == "]": + v = v[1:-1] + v = v.split(",") + v = [recast_type(vi, variable_types[k]) for vi in v] + else: + v = recast_type(v, variable_types[k]) + request_dict[k] = v + return request_dict \ No newline at end of file From af8ace497a6db83e2460bf640770cadcdff5bde2 Mon Sep 17 00:00:00 2001 From: Aleksandra Apolinarska Date: Thu, 2 May 2024 22:18:56 +0200 Subject: [PATCH 6/7] #15 add plot contours request (generation_scatter) --- .../icons/aixd_PlotContoursRequest.png | Bin 0 -> 1606 bytes docs/documentation.rst | 32 +++++++++++-- src/aixd_grasshopper/api.py | 16 +++++++ .../aixd_PlotContours/metadata.json | 2 +- .../aixd_PlotContoursRequest/code.py | 26 +++++++++++ .../aixd_PlotContoursRequest/icon.png | Bin 0 -> 1606 bytes .../aixd_PlotContoursRequest/metadata.json | 44 ++++++++++++++++++ src/aixd_grasshopper/controller.py | 24 ++++++++-- src/aixd_grasshopper/gh_ui.py | 12 ++++- 9 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 docs/_images/icons/aixd_PlotContoursRequest.png create mode 100644 src/aixd_grasshopper/components/aixd_PlotContoursRequest/code.py create mode 100644 src/aixd_grasshopper/components/aixd_PlotContoursRequest/icon.png create mode 100644 src/aixd_grasshopper/components/aixd_PlotContoursRequest/metadata.json diff --git a/docs/_images/icons/aixd_PlotContoursRequest.png b/docs/_images/icons/aixd_PlotContoursRequest.png new file mode 100644 index 0000000000000000000000000000000000000000..2985924972684737ade9450e595ce945569c7b84 GIT binary patch literal 1606 zcmV-M2D$l(P)6N z_~cxiOMt{c5agOmfCM?@r{tbnE7uIrx}By>=0$1hw{}v}bJ{>8NC799cyi zAxn$`F+k-J0+uc!MvyJ?y^oEV9DvcLYrtF?u%m+Om1 z26g<;?)`6V4!2hW@}1_bzx-n9XZQX;f(jhYz)21d_2}N8t`v_Rq`IFK(I79a02g_0 zsu*VNZrNSkU^gA1{r7a#8jZn&{iD0~T>!ZE#t0-Ig8$pSHa%RQ#9~xvq5_x*nFNS< z_tjTF@l;+8K$#zQzUY40nb7q^c!Q}6g{>ms{g<}<{qL6O(SsHcdGOu~5d~oIM8L$$ zmsftPl{p-hXM_-OoCb=V7-VymR172QGXA zktbmiqij|!R;J6swMf}aK;(9Fb?r{`*1AE>AI*Gw^PE6jh(b<}wZHpW2Z~&fT-dn- zSGp=)dpm9Fo+jlWOAAlXC&0ucPv@iA$L(ll(0Bc}LS_Xd)DPclzWX9l5sg(eLIH4p z{k$XjkbwLd_;A4ftNYjC_^1VC3FtkeX9nf~qYUbkQ`fyb@R_}tzWpf7qJUbicaASb zv)Q<=Mu}v6bQsI?XAv@uLf(0Ady<}feND;|fvVyXaD^<@J|)PQ!|`C2@CQ-&&PCIw zD~%ib7tR+NCEVxrf7P)p3M3-h+iB9%$KL?*6vlx7$}(o}Yf7L5RK4o_i3%j#u#;Pf zicT~teY>@~10d14ZYqTu%hc!7HI@Hy8_Vjw#3pJlT5_sVa=#sta0?t^u&YMAU3RazkGjXLuz{u7-gGBFR39ug`w#DLT$+k7?+=RkSmU51o+OsEZuL&9X16t>x;~venyuR+ z)@VLmdF%1DsmNk9rlaag~{kNE4fK5b%UrolePW#uQax-LNk|I|MS}mTQ!4LX|YPN z(TcUHBJpU96Jv>uCNfQzMMfyr%!vX?K6z&JtSphz6jJ4l@y;4oMp0SD(TGfqT%?2(1gWD)9RX?|U27FrChXqbC&yQF z$DxT=M{~Xr4S%%K-cGF!<22mvAr&G>h(b!S)1*pmyfWm)T1Q3;#Ryg7u1El&7Ml0X zcAamw>s!0MQIb2>Q;G4jbsOY6ekcf@HEsz+RZ&HpLJpXUG>I{e!(9}G7eUy0@N(xw9(u^qW}N^07*qoM6N<$ Ef6N z_~cxiOMt{c5agOmfCM?@r{tbnE7uIrx}By>=0$1hw{}v}bJ{>8NC799cyi zAxn$`F+k-J0+uc!MvyJ?y^oEV9DvcLYrtF?u%m+Om1 z26g<;?)`6V4!2hW@}1_bzx-n9XZQX;f(jhYz)21d_2}N8t`v_Rq`IFK(I79a02g_0 zsu*VNZrNSkU^gA1{r7a#8jZn&{iD0~T>!ZE#t0-Ig8$pSHa%RQ#9~xvq5_x*nFNS< z_tjTF@l;+8K$#zQzUY40nb7q^c!Q}6g{>ms{g<}<{qL6O(SsHcdGOu~5d~oIM8L$$ zmsftPl{p-hXM_-OoCb=V7-VymR172QGXA zktbmiqij|!R;J6swMf}aK;(9Fb?r{`*1AE>AI*Gw^PE6jh(b<}wZHpW2Z~&fT-dn- zSGp=)dpm9Fo+jlWOAAlXC&0ucPv@iA$L(ll(0Bc}LS_Xd)DPclzWX9l5sg(eLIH4p z{k$XjkbwLd_;A4ftNYjC_^1VC3FtkeX9nf~qYUbkQ`fyb@R_}tzWpf7qJUbicaASb zv)Q<=Mu}v6bQsI?XAv@uLf(0Ady<}feND;|fvVyXaD^<@J|)PQ!|`C2@CQ-&&PCIw zD~%ib7tR+NCEVxrf7P)p3M3-h+iB9%$KL?*6vlx7$}(o}Yf7L5RK4o_i3%j#u#;Pf zicT~teY>@~10d14ZYqTu%hc!7HI@Hy8_Vjw#3pJlT5_sVa=#sta0?t^u&YMAU3RazkGjXLuz{u7-gGBFR39ug`w#DLT$+k7?+=RkSmU51o+OsEZuL&9X16t>x;~venyuR+ z)@VLmdF%1DsmNk9rlaag~{kNE4fK5b%UrolePW#uQax-LNk|I|MS}mTQ!4LX|YPN z(TcUHBJpU96Jv>uCNfQzMMfyr%!vX?K6z&JtSphz6jJ4l@y;4oMp0SD(TGfqT%?2(1gWD)9RX?|U27FrChXqbC&yQF z$DxT=M{~Xr4S%%K-cGF!<22mvAr&G>h(b!S)1*pmyfWm)T1Q3;#Ryg7u1El&7Ml0X zcAamw>s!0MQIb2>Q;G4jbsOY6ekcf@HEsz+RZ&HpLJpXUG>I{e!(9}G7eUy0@N(xw9(u^qW}N^07*qoM6N<$ Ef Date: Thu, 2 May 2024 22:22:19 +0200 Subject: [PATCH 7/7] #15 remove GenSampleEval, GenSelect --- docs/_images/icons/aixd_GenSampleEval.png | Bin 1524 -> 0 bytes docs/_images/icons/aixd_GenSelect.png | Bin 1551 -> 0 bytes docs/documentation.rst | 40 ------- .../components/aixd_GenSampleEval/code.py | 40 ------- .../components/aixd_GenSampleEval/icon.png | Bin 1524 -> 0 bytes .../aixd_GenSampleEval/metadata.json | 39 ------- .../components/aixd_GenSelect/code.py | 106 ------------------ .../components/aixd_GenSelect/icon.png | Bin 1551 -> 0 bytes .../components/aixd_GenSelect/metadata.json | 37 ------ 9 files changed, 262 deletions(-) delete mode 100644 docs/_images/icons/aixd_GenSampleEval.png delete mode 100644 docs/_images/icons/aixd_GenSelect.png delete mode 100644 src/aixd_grasshopper/components/aixd_GenSampleEval/code.py delete mode 100644 src/aixd_grasshopper/components/aixd_GenSampleEval/icon.png delete mode 100644 src/aixd_grasshopper/components/aixd_GenSampleEval/metadata.json delete mode 100644 src/aixd_grasshopper/components/aixd_GenSelect/code.py delete mode 100644 src/aixd_grasshopper/components/aixd_GenSelect/icon.png delete mode 100644 src/aixd_grasshopper/components/aixd_GenSelect/metadata.json diff --git a/docs/_images/icons/aixd_GenSampleEval.png b/docs/_images/icons/aixd_GenSampleEval.png deleted file mode 100644 index 668b839ecd368e809ea62e872716b67fea511975..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1524 zcmVOXZ8)%@l5o6I7 zh?1DlNHi2GK{PRqMr%~iFa8T^jRoT;qc!1!UxX%-ni&2hra_|7O1pFsDYl_3YfJY} zyWQ^2?7VmHJ?EG^v(rqsoXO3-bKiTv-#O?0-Z>8k$0i;J00#gD01!GBLBXSHiv*fB zg!xLr%@r4m=FqtXzjpkh+%Pe3SI;yneGT7l1J{i}KY-RiA;%6LB|N(XOiSQh0=hg@ zi4={AN+ybNflcOxY+y$$8H(npkOQ?>nWJXJ_}O&6nPs&l`)b>FX&!@Zs-MClIg%v~})v-hEgkM7!X_)kyV{kP|y z{nnRl6oabvoyNPXHVtfewb#S#tL}WIyS^dr0nZJI%*ZW=q;IU&Xc zsgwr98x?PbgJlV%8WA*Ndj@te#-tY8#q|F(TkBB@8z5Zslhn`;(*ejoUdyR4w!$D9)S=-fOIet2{&QYSYOUy)Z_F9WB8*98dO=|Po z>bEQasIxaLo-b;#q)VsXs#yt;`xYA&@XD!_Vo2{1LR%usB0xX|1n#7R0*h*x3X=}i zn%n87hA26^e&L+2hnAgKoZ|{GHN|5hC{!p_RJyW^Up8vP16Y&%5J>eL1X!u%RUJDQ znk>fJ56_>`!O+yv*u)%!<}{k*c>~oL1YE?c%6Z#pY2T7|8jIzWQFU3t%u%Y<#a0Lz zB?7F)!faaQnl(~4eO8@Cpg97I2xzeV>R)Asz@+1G25gEXw``IY)~|N+!CS5OFND(P zYKIpu*i1t9wJk#ztZ-#=a&|f+a8vUwXAsB`eqaRceFexYx^7D7$W5eZl)I zmj05KMTPBPXl7P9HzHL6t^VxZk<+A-O)r!qiQuKi>`fm?fM8N5+PtR!JyDe(?O1o3 zqm9h1inGCInfG~CvaN8)NTrIPHSAQx3Ib#Y?!R#~;e77d6T>N0FPxmcq9<$_uD_+K z`rG^0kE$Yz#6BVkhRGWn$#PWZ0$Zb;BeWZLn*;zVOU=9eG4RJ-8xE&K{NyulT-!^q zw4c&n1yCUpM%6$l36t2cS{no#-~aeM08q!y=vyLK{u#4;PS#sBJAGV%Rk2jXLKT}L a)c8NRxGL?xqM26!0000|n5(G_4&se)fCw z=FQK&=NxojUR)PaaipV}IdkXx&d)vP4z}OD`vU-o0KfnM0ss&I6F}Vns175QPpA~f z1*#`2sU~}+oBAl{iMu_F-`_KEub*&tm}14Lcy5A1f)DX1;Mp-Sjlq$E5fM_vs7;8o zhU~nFn&+&I7t?q)W#-=dz4t1_fe=FJ#8h6a?^g{~O~8vC;{}CB5Bow0njm7^VDzLl z_DjupVsZW5Z{JzHHT}btmEa64EiLhxQwy836Ej!3rO&g4i%# z$^l$JMHLQ)0%PwRh;Z1DnT|~S(%#lLzc&V-pPz>tH*SDF{0@Tc!gntuTSs=jl}@)> zNvF|>CZd=k+V>s~XGM{E?t7f#YNcI*w~LGyj%PC+GOi9 z?a8FmOl%q(iVOkb{u(;KguxyoLW__-pC-pwR<4;x3(ucF50@`rhX0n8->gvk#L=1f zP!ijQHDn2qK4u6t(D8k!fkYuqgp%O@V125X*cjWiWFkXk0zuf080rulR|lXzC@FZO z;A2#O-}(EmEiK9a17BKNl5)4$1w;h|IpFYqiYinng9=CqpalXHIm_n?>1!tvvaDO;Cvb?bp|?J@>Em_2$*9 zS0U)dix*+x%tB?s+^PEI2X0VxT~-#psJ(b5l~GHLMh{N^dHofmg29@CaECxoM25OQ zd;1$FXO1^uclYaaPd^>CTdiYrb8}#ffz8cLD2wvJ{jDuNLalf1ti4yfeE7w8vb%dw zc1tUcV->XkXl%+=-w0DAUQ=cL@TWidY;KK~tE+RvsY%;PlDZlWIRM|jeS1Vu*EJAe zwew&{>Z~}r{oyue=D|$aE6vO8uWWz{17I97WZU&1Z?L28?|(N_a;??cKaTeZf%}>3 z`da#Ae4b_D14xnthzPv*5E0e=UMA$E?00**ro|Di*!%@2Q)CmLYdw6D8#8KpO7Gv!~|DLv8t~Y&-2?*_}Hw20U5MxA;okQ^sikctJlChZBhDcCH z{RK$mFzm@FE}7X&vpFoBizyIb>Pz#&(I`(zqwNngvIw+C5Y8PWPy?#K{7C{8Frc88 z)CP*gvI)I)aw_V(+G^`i=BheTJ3HL5vAFpAi^q1XaARw$yB!lm_xnX;5W;BLALoxo zb&!!=0b+}hD(DN-hd%O*^7`4Sow|;FRrjBo%;gJ2Xvzc6#?j;P98U!B`vZ#{F-h^(vQxUQ49V%uOBlS&goQ&de6pb@6_ zJR_y&K)FJoNe7wRlW4;$(yRkB7TBMoMEbs^<>#=6gjp=IT;

OXZ8)%@l5o6I7 zh?1DlNHi2GK{PRqMr%~iFa8T^jRoT;qc!1!UxX%-ni&2hra_|7O1pFsDYl_3YfJY} zyWQ^2?7VmHJ?EG^v(rqsoXO3-bKiTv-#O?0-Z>8k$0i;J00#gD01!GBLBXSHiv*fB zg!xLr%@r4m=FqtXzjpkh+%Pe3SI;yneGT7l1J{i}KY-RiA;%6LB|N(XOiSQh0=hg@ zi4={AN+ybNflcOxY+y$$8H(npkOQ?>nWJXJ_}O&6nPs&l`)b>FX&!@Zs-MClIg%v~})v-hEgkM7!X_)kyV{kP|y z{nnRl6oabvoyNPXHVtfewb#S#tL}WIyS^dr0nZJI%*ZW=q;IU&Xc zsgwr98x?PbgJlV%8WA*Ndj@te#-tY8#q|F(TkBB@8z5Zslhn`;(*ejoUdyR4w!$D9)S=-fOIet2{&QYSYOUy)Z_F9WB8*98dO=|Po z>bEQasIxaLo-b;#q)VsXs#yt;`xYA&@XD!_Vo2{1LR%usB0xX|1n#7R0*h*x3X=}i zn%n87hA26^e&L+2hnAgKoZ|{GHN|5hC{!p_RJyW^Up8vP16Y&%5J>eL1X!u%RUJDQ znk>fJ56_>`!O+yv*u)%!<}{k*c>~oL1YE?c%6Z#pY2T7|8jIzWQFU3t%u%Y<#a0Lz zB?7F)!faaQnl(~4eO8@Cpg97I2xzeV>R)Asz@+1G25gEXw``IY)~|N+!CS5OFND(P zYKIpu*i1t9wJk#ztZ-#=a&|f+a8vUwXAsB`eqaRceFexYx^7D7$W5eZl)I zmj05KMTPBPXl7P9HzHL6t^VxZk<+A-O)r!qiQuKi>`fm?fM8N5+PtR!JyDe(?O1o3 zqm9h1inGCInfG~CvaN8)NTrIPHSAQx3Ib#Y?!R#~;e77d6T>N0FPxmcq9<$_uD_+K z`rG^0kE$Yz#6BVkhRGWn$#PWZ0$Zb;BeWZLn*;zVOU=9eG4RJ-8xE&K{NyulT-!^q zw4c&n1yCUpM%6$l36t2cS{no#-~aeM08q!y=vyLK{u#4;PS#sBJAGV%Rk2jXLKT}L a)c8NRxGL?xqM26!0000 1: - print("{len(found)} ghcomponents found with the nickname {} - will return None.".format(component_nickname)) - return - return found[0] - - -def set_value(component, val): - component.Script_ClearPersistentData() - component.AddPersistentData(val) - component.ExpireSolution(True) - - -def set_values(component, vals): - """ - Data type of vals must match the type of the component. - See TYPES list. - """ - ghtype = TYPES[component.TypeName] - - component.Script_ClearPersistentData() - if not isinstance(vals, list): - vals = [vals] - for v in vals: - component.PersistentData.Append(ghtype(v)) - component.ExpireSolution(True) - - -TYPES = { - "Arc": Grasshopper.Kernel.Types.GH_Arc, - "Boolean": Grasshopper.Kernel.Types.GH_Boolean, - "Box": Grasshopper.Kernel.Types.GH_Box, - "Brep": Grasshopper.Kernel.Types.GH_Brep, - "Circle": Grasshopper.Kernel.Types.GH_Circle, - "ComplexNumber": Grasshopper.Kernel.Types.GH_ComplexNumber, - "Curve": Grasshopper.Kernel.Types.GH_Curve, - "Guid": Grasshopper.Kernel.Types.GH_Guid, - "Integer": Grasshopper.Kernel.Types.GH_Integer, - "Interval": Grasshopper.Kernel.Types.GH_Interval, - "Interval2D": Grasshopper.Kernel.Types.GH_Interval2D, - "Line": Grasshopper.Kernel.Types.GH_Line, - "Mesh": Grasshopper.Kernel.Types.GH_Mesh, - "Number": Grasshopper.Kernel.Types.GH_Number, - "Plane": Grasshopper.Kernel.Types.GH_Plane, - "Point": Grasshopper.Kernel.Types.GH_Point, - "Rectangle": Grasshopper.Kernel.Types.GH_Rectangle, - "String": Grasshopper.Kernel.Types.GH_String, - "SubD": Grasshopper.Kernel.Types.GH_SubD, - "Surface": Grasshopper.Kernel.Types.GH_Surface, - "Vector": Grasshopper.Kernel.Types.GH_Vector, - "Text": Grasshopper.Kernel.Types.GH_String, -} - - -for dp_name, dp_vals in dp_pred.items(): - # print dp_name, dp_vals - component_name = "GENERATED_{}".format(dp_name) - component = find_component_by_nickname(ghdoc, component_name) - print("{}: {}".format(component_name, dp_vals)) - - if not dp_vals: - print("empty values for {}".format(comp)) - else: - set_values(component, dp_vals) - - txt = "Generated & Predicted\n" - txt += "---------------------\n\n" - txt += "Design Parameters:\n\n" - for name, values in dp_pred.items(): - txt += "{}: {}\n".format(name, values) - txt += "\n" - txt += "Performance Attributes:\n\n" - for name, values in pa_pred.items(): - txt += "{}: {}\n".format(name, values) - - sample_summary = txt diff --git a/src/aixd_grasshopper/components/aixd_GenSelect/icon.png b/src/aixd_grasshopper/components/aixd_GenSelect/icon.png deleted file mode 100644 index c40957c21c8554a9f5414710ef9c56e7c1767a8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1551 zcmV+q2JrcbP)|n5(G_4&se)fCw z=FQK&=NxojUR)PaaipV}IdkXx&d)vP4z}OD`vU-o0KfnM0ss&I6F}Vns175QPpA~f z1*#`2sU~}+oBAl{iMu_F-`_KEub*&tm}14Lcy5A1f)DX1;Mp-Sjlq$E5fM_vs7;8o zhU~nFn&+&I7t?q)W#-=dz4t1_fe=FJ#8h6a?^g{~O~8vC;{}CB5Bow0njm7^VDzLl z_DjupVsZW5Z{JzHHT}btmEa64EiLhxQwy836Ej!3rO&g4i%# z$^l$JMHLQ)0%PwRh;Z1DnT|~S(%#lLzc&V-pPz>tH*SDF{0@Tc!gntuTSs=jl}@)> zNvF|>CZd=k+V>s~XGM{E?t7f#YNcI*w~LGyj%PC+GOi9 z?a8FmOl%q(iVOkb{u(;KguxyoLW__-pC-pwR<4;x3(ucF50@`rhX0n8->gvk#L=1f zP!ijQHDn2qK4u6t(D8k!fkYuqgp%O@V125X*cjWiWFkXk0zuf080rulR|lXzC@FZO z;A2#O-}(EmEiK9a17BKNl5)4$1w;h|IpFYqiYinng9=CqpalXHIm_n?>1!tvvaDO;Cvb?bp|?J@>Em_2$*9 zS0U)dix*+x%tB?s+^PEI2X0VxT~-#psJ(b5l~GHLMh{N^dHofmg29@CaECxoM25OQ zd;1$FXO1^uclYaaPd^>CTdiYrb8}#ffz8cLD2wvJ{jDuNLalf1ti4yfeE7w8vb%dw zc1tUcV->XkXl%+=-w0DAUQ=cL@TWidY;KK~tE+RvsY%;PlDZlWIRM|jeS1Vu*EJAe zwew&{>Z~}r{oyue=D|$aE6vO8uWWz{17I97WZU&1Z?L28?|(N_a;??cKaTeZf%}>3 z`da#Ae4b_D14xnthzPv*5E0e=UMA$E?00**ro|Di*!%@2Q)CmLYdw6D8#8KpO7Gv!~|DLv8t~Y&-2?*_}Hw20U5MxA;okQ^sikctJlChZBhDcCH z{RK$mFzm@FE}7X&vpFoBizyIb>Pz#&(I`(zqwNngvIw+C5Y8PWPy?#K{7C{8Frc88 z)CP*gvI)I)aw_V(+G^`i=BheTJ3HL5vAFpAi^q1XaARw$yB!lm_xnX;5W;BLALoxo zb&!!=0b+}hD(DN-hd%O*^7`4Sow|;FRrjBo%;gJ2Xvzc6#?j;P98U!B`vZ#{F-h^(vQxUQ49V%uOBlS&goQ&de6pb@6_ zJR_y&K)FJoNe7wRlW4;$(yRkB7TBMoMEbs^<>#=6gjp=IT;