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