Repository: mcneel/compute.rhino3d (language: python)

Issue:
Write sample that communicates with 3rd party app
Hops doesn't specifically need a compute instance to talk to for solving. Provide a simple example that shows this off.


Discussion / hints:
Linked with [COMPUTE-164](https://mcneel.myjetbrains.com/youtrack/issue/COMPUTE-164)
Preferably a local CPython 3 sample

Fix patch:
diff --git a/src/compute.components/RemoteDefinition.cs b/src/compute.components/RemoteDefinition.cs
index c24635ca..d2208159 100644
--- a/src/compute.components/RemoteDefinition.cs
+++ b/src/compute.components/RemoteDefinition.cs
@@ -149,6 +149,8 @@ public void SetComponentOutputs(Schema schema, IGH_DataAccess DA, List<IGH_Param
                 }
 
                 var structure = new Grasshopper.Kernel.Data.GH_Structure<Grasshopper.Kernel.Types.IGH_Goo>();
+                Grasshopper.Kernel.Types.IGH_Goo singleGoo = null;
+                bool setSingleItem = datatree.InnerTree.Count == 1;
                 foreach (var kv in datatree.InnerTree)
                 {
                     var tokens = kv.Key.Trim(new char[] { '{', '}' }).Split(';');
@@ -160,14 +162,21 @@ public void SetComponentOutputs(Schema schema, IGH_DataAccess DA, List<IGH_Param
                             elements.Add(int.Parse(token));
                         }
                     }
+                    if (setSingleItem && (elements.Count != 1 || elements[0] != 0))
+                        setSingleItem = false;
+
                     var path = new Grasshopper.Kernel.Data.GH_Path(elements.ToArray());
                     for (int gooIndex = 0; gooIndex < kv.Value.Count; gooIndex++)
                     {
                         var goo = GooFromReshopperObject(kv.Value[gooIndex]);
+                        singleGoo = goo;
                         structure.Insert(goo, path, gooIndex);
                     }
                 }
-                DA.SetDataTree(paramIndex, structure);
+                if (setSingleItem && singleGoo!=null)
+                    DA.SetData(paramIndex, singleGoo);
+                else
+                    DA.SetDataTree(paramIndex, structure);
             }
 
             foreach (var error in schema.Errors)
@@ -212,6 +221,8 @@ static Grasshopper.Kernel.Types.IGH_Goo GooFromReshopperObject(ResthopperObject
 
             switch (obj.Type)
             {
+                case "System.Double":
+                    return new Grasshopper.Kernel.Types.GH_Number(double.Parse(data));
                 case "System.String":
                     return new Grasshopper.Kernel.Types.GH_String(data);
                 case "System.Int32":


Test patch:
diff --git a/src/tests/hops_python.py b/src/tests/hops_python.py
new file mode 100644
index 00000000..34007d8a
--- /dev/null
+++ b/src/tests/hops_python.py
@@ -0,0 +1,64 @@
+'''
+Poorly written example of a python script that could be called from Hops
+without involving Rhino/Grasshopper at all
+'''
+from http.server import HTTPServer, BaseHTTPRequestHandler
+import json
+
+
+class HopsServer(BaseHTTPRequestHandler):
+    def _set_headers(self):
+        self.send_response(200)
+        self.send_header("Content-type", "application/json")
+        self.end_headers()
+
+    def do_GET(self):
+        # self.path = '/add'
+        self._set_headers()
+        data = {
+            'Description': 'Add two numbers using cpython',
+            'Inputs': [
+                {'Name': 'a', 'AtLeast': 1, 'AtMost': 1, 'Description': 'First value for addition', 'ParamType': 'Number'},
+                {'Name': 'b', 'AtLeast': 1, 'AtMost': 1, 'Description': 'Second value for addition', 'ParamType': 'Number'},
+            ],
+            'Outputs': [
+                {'Name': 'sum', 'ParamType': 'Number'}
+            ]
+        }
+        s = json.dumps(data)
+        self.wfile.write(s.encode(encoding='utf_8'))
+
+    def do_HEAD(self):
+        self._set_headers()
+
+    def do_POST(self):
+        # read the message and convert it into a python dictionary
+        length = int(self.headers.get('Content-Length'))
+        jsoninput = self.rfile.read(length)
+        data = json.loads(jsoninput)
+        a = float(data['values'][0]['InnerTree']['0'][0]['data'])
+        b = float(data['values'][1]['InnerTree']['0'][0]['data'])
+        sum = a + b
+        output = {
+            'values': [
+                {
+                    'ParamName': 'sum',
+                    'InnerTree': {
+                        '0': [{
+                            'type': 'System.Double',
+                            'data': sum
+                        }]
+                    }
+                }
+            ]
+        }
+        self._set_headers()
+        s = json.dumps(output)
+        self.wfile.write(s.encode(encoding='utf_8'))
+
+
+if __name__ == "__main__":
+    location = ('localhost', 5000)
+    httpd = HTTPServer(location, HopsServer)
+    print(f"Starting httpd server on {location[0]}:{location[1]}")
+    httpd.serve_forever()