From d5c4eadcd7a07a17268ffc058e039935f296a1d5 Mon Sep 17 00:00:00 2001 From: Erik Strand <erik.strand@cba.mit.edu> Date: Mon, 14 Feb 2022 05:38:54 -0500 Subject: [PATCH] Add grasshopper demo files --- .gitignore | 2 + grasshopper/__init__.py | 0 grasshopper/cmaes_demo.py | 64 +++++++++++++++++++++++++++++ grasshopper/generate_mesh.py | 78 ++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 grasshopper/__init__.py create mode 100644 grasshopper/cmaes_demo.py create mode 100644 grasshopper/generate_mesh.py diff --git a/.gitignore b/.gitignore index a8e1c5a..9e8cc4b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.swo *.stl .DS_Store +.venv +__pycache__ diff --git a/grasshopper/__init__.py b/grasshopper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grasshopper/cmaes_demo.py b/grasshopper/cmaes_demo.py new file mode 100644 index 0000000..9012d26 --- /dev/null +++ b/grasshopper/cmaes_demo.py @@ -0,0 +1,64 @@ +import cma +import pathlib +from generate_mesh import generate_default_parameters, generate_3dm_and_stl + +def optimize(outdir = "."): + outdir = pathlib.Path(outdir) + outdir.mkdir(parents=True, exist_ok=True) + outdir = outdir.resolve() + logfile = open(outdir / "optimization_log.txt", "w") + + # We will "optimize" the height and angle. + # We'll start with a distribution centered at a height of 0.1 and an angle of 0.2 rad. + # The second parameter sets sigma. + es = cma.CMAEvolutionStrategy([0.1, 0.2], 0.2) + gen = 1 + parameters = generate_default_parameters() + while not es.stop(): + # Ask CMA-ES for the new generation of potential solutions. + solutions = es.ask() + + # Write them to our log. + for idx, solution in enumerate(solutions): + logfile.write(f"gen {gen} sample {idx + 1}: ") + logfile.write(' '.join([str(x) for x in solution]) + '\n') + + # Generate and save the meshes. + for idx, solution in enumerate(solutions): + parameters["vg_height"] = solutions[idx][0] + parameters["vg_angle"] = solutions[idx][1] + path_stl = outdir / f"gen_{gen}_s_{idx + 1}_vg.stl" + path_3dm = outdir / f"gen_{gen}_s_{idx + 1}_vg.3dm" + parameters["stl_path"] = path_stl.as_posix() + parameters["3dm_path"] = path_3dm.as_posix() + generate_3dm_and_stl(parameters) + + # Score each sample. + # Here is where we will launch the simulations. + # For now I'm just going to cheat and define a simple objective. + scores = [] + for idx, solution in enumerate(solutions): + height = solutions[idx][0] + angle = solutions[idx][1] + score = (height - 0.25)**2 + (angle - 0.3)**2 + scores.append(score) + + # Log results and submit them to CMA-ES. + logfile.write(f"gen {gen} scores: ") + logfile.write(' '.join([str(x) for x in scores]) + '\n') + es.tell(solutions, scores) + + # This occasionally prints a one line summary of the current state. + es.disp() + gen += 1 + + # Save the final result. + result = es.result_pretty() + solution = result.xbest + logfile.write("best parameters: ") + logfile.write(' '.join([str(x) for x in solution]) + '\n') + + +if __name__ == "__main__": + optimize("data") + diff --git a/grasshopper/generate_mesh.py b/grasshopper/generate_mesh.py new file mode 100644 index 0000000..3e77dd3 --- /dev/null +++ b/grasshopper/generate_mesh.py @@ -0,0 +1,78 @@ +import json +import pathlib +import rhino3dm +import compute_rhino3d.Util +import compute_rhino3d.Grasshopper as gh + +compute_rhino3d.Util.url = "http://localhost:8081/" +#compute_rhino3d.Util.apiKey = "" + +def generate_default_parameters(outdir = "."): + this_dir = pathlib.Path(outdir).resolve() + path_stl = this_dir / "vortex_generators.stl" + path_3dm = this_dir / "vortex_generators.3dm" + parameters = { + "platform_length" : 8.0, + "step_length" : 3.0, + "step_height" : 0.5, + "floor_thickness" : 1.5, + "model_width" : 12.0, + "vg_length" : 0.5, + "vg_height" : 0.2, + "vg_thickness" : 0.05, + "vg_angle" : 0.2, + "vg_inter_spacing" : 0.6, + "vg_intra_spacing" : 1.2, + "n_vg_pairs" : 6, + "stl_path" : path_stl.as_posix(), + "save_stl" : True, + "3dm_path" : path_3dm.as_posix(), + "save_3dm" : True + } + return parameters + +# Create input trees. +def generate_3dm_and_stl(parameters): + trees = [] + def add_tree(name): + tree = gh.DataTree(name) + tree.Append([0], [parameters[name]]) + trees.append(tree) + + add_tree("platform_length") + add_tree("step_length") + add_tree("step_height") + add_tree("floor_thickness") + add_tree("model_width") + add_tree("vg_length") + add_tree("vg_height") + add_tree("vg_thickness") + add_tree("vg_angle") + add_tree("vg_inter_spacing") + add_tree("vg_intra_spacing") + add_tree("n_vg_pairs") + add_tree("stl_path") + add_tree("save_stl") + + # Evaluate the model. + path_stl = parameters["stl_path"] + print(f"Writing mesh to {path_stl}") + output = gh.EvaluateDefinition('vortex_generators.gh', trees) + + # Decode results. + branch = output['values'][0]['InnerTree']['{0}'] + breps = [rhino3dm.CommonObject.Decode(json.loads(item['data'])) for item in branch] + + # Save a 3dm file, if requested. + if parameters["save_3dm"]: + path_3dm = parameters["3dm_path"] + print(f"Writing {len(breps)} breps to {path_3dm}") + model = rhino3dm.File3dm() + for brep in breps: + model.Objects.AddBrep(brep) + model.Write(path_3dm) + +if __name__ == "__main__": + parameters = generate_default_parameters() + generate_3dm_and_stl(parameters) + -- GitLab