|
1 | 1 | #! python3
|
2 | 2 |
|
3 | 3 | from ghpythonlib.componentbase import executingcomponent as component
|
4 |
| -import os |
5 |
| -import tempfile |
6 |
| -import requests |
7 |
| -import threading |
8 |
| -import Rhino |
9 |
| -import Rhino.Geometry as rg |
| 4 | +# import threading |
| 5 | +# import asyncio |
| 6 | +# import json |
10 | 7 | import scriptcontext as sc
|
| 8 | +# import Rhino.Geometry as rg |
| 9 | +# import System.Drawing as sd |
| 10 | +#import websockets |
11 | 11 |
|
12 | 12 |
|
13 |
| -class DFHTTPListener(component): |
14 |
| - |
| 13 | +class DFWSServerListener(component): |
15 | 14 | def RunScript(self,
|
| 15 | + i_start: bool, |
16 | 16 | i_load: bool,
|
17 |
| - i_ply_url: str): |
18 |
| - |
19 |
| - sc.sticky.setdefault('ply_url', None) |
20 |
| - sc.sticky.setdefault('imported_geom', None) |
21 |
| - sc.sticky.setdefault('status_message','Idle') |
| 17 | + i_stop: bool, |
| 18 | + i_host: str, |
| 19 | + i_port: int): # Port to bind |
| 20 | + |
| 21 | + # --- Persistent state across runs --- |
| 22 | + sc.sticky.setdefault('ws_thread', None) |
| 23 | + sc.sticky.setdefault('ws_loop', None) |
| 24 | + sc.sticky.setdefault('ws_server', None) |
| 25 | + sc.sticky.setdefault('ws_buffer', []) |
| 26 | + sc.sticky.setdefault('ws_latest', None) |
| 27 | + sc.sticky.setdefault('status', 'Idle') |
| 28 | + sc.sticky.setdefault('prev_start', False) |
| 29 | + sc.sticky.setdefault('prev_stop', False) |
22 | 30 | sc.sticky.setdefault('prev_load', False)
|
23 |
| - sc.sticky.setdefault('thread_running', False) |
24 |
| - |
25 |
| - def _import_job(url): |
26 |
| - try: |
27 |
| - if not url.lower().endswith('.ply'): |
28 |
| - raise ValueError("URL must end in .ply") |
29 |
| - |
30 |
| - resp = requests.get(url, timeout=30) |
31 |
| - resp.raise_for_status() |
32 |
| - fn = os.path.basename(url) |
33 |
| - tmp = os.path.join(tempfile.gettempdir(), fn) |
34 |
| - with open(tmp, 'wb') as f: |
35 |
| - f.write(resp.content) |
36 |
| - |
37 |
| - doc = Rhino.RhinoDoc.ActiveDoc |
38 |
| - before_ids = {o.Id for o in doc.Objects} |
39 |
| - |
40 |
| - opts = Rhino.FileIO.FilePlyReadOptions() |
41 |
| - ok = Rhino.FileIO.FilePly.Read(tmp, doc, opts) |
42 |
| - if not ok: |
43 |
| - raise RuntimeError("Rhino.FilePly.Read failed") |
44 |
| - |
45 |
| - after_ids = {o.Id for o in doc.Objects} |
46 |
| - new_ids = after_ids - before_ids |
47 |
| - |
48 |
| - geom = None |
49 |
| - for guid in new_ids: |
50 |
| - g = doc.Objects.FindId(guid).Geometry |
51 |
| - if isinstance(g, rg.PointCloud): |
52 |
| - geom = g.Duplicate() |
53 |
| - break |
54 |
| - elif isinstance(g, rg.Mesh): |
55 |
| - geom = g.DuplicateMesh() |
56 |
| - break |
57 |
| - |
58 |
| - for guid in new_ids: |
59 |
| - doc.Objects.Delete(guid, True) |
60 |
| - doc.Views.Redraw() |
61 |
| - |
62 |
| - sc.sticky['imported_geom'] = geom |
63 |
| - count = geom.Count if isinstance(geom, rg.PointCloud) else geom.Vertices.Count |
64 |
| - if isinstance(geom, rg.PointCloud): |
65 |
| - sc.sticky['status_message'] = f"Done: {count} points" |
66 |
| - else: |
67 |
| - sc.sticky['status_message'] = f"Done: {count} vertices" |
68 |
| - ghenv.Component.Message = sc.sticky.get('status_message') # noqa: F821 |
69 |
| - |
70 |
| - except Exception as e: |
71 |
| - sc.sticky['imported_geom'] = None |
72 |
| - sc.sticky['status_message'] = f"Error: {e}" |
73 |
| - finally: |
74 |
| - try: |
75 |
| - os.remove(tmp) |
76 |
| - except Exception: |
77 |
| - pass |
78 |
| - sc.sticky['thread_running'] = False |
79 |
| - ghenv.Component.ExpireSolution(True) # noqa: F821 |
80 |
| - |
81 |
| - if sc.sticky['ply_url'] != i_ply_url: |
82 |
| - sc.sticky['ply_url'] = i_ply_url |
83 |
| - sc.sticky['status_message'] = "URL changed. Press Load" |
84 |
| - sc.sticky['thread_running'] = False |
85 |
| - sc.sticky['prev_load'] = False |
86 |
| - |
87 |
| - if i_load and not sc.sticky['prev_load'] and not sc.sticky['thread_running']: |
88 |
| - sc.sticky['status_message'] = "Loading..." |
89 |
| - sc.sticky['thread_running'] = True |
90 |
| - threading.Thread(target=_import_job, args=(i_ply_url,), daemon=True).start() |
91 |
| - |
92 |
| - sc.sticky['prev_load'] = i_load |
93 |
| - ghenv.Component.Message = sc.sticky.get('status_message', "") # noqa: F821 |
94 |
| - |
95 |
| - # output |
96 |
| - o_geometry = sc.sticky.get('imported_geom') |
97 | 31 |
|
98 |
| - return [o_geometry] |
| 32 | + # async def handler(ws, path): |
| 33 | + # """Receive JSON-encoded dicts and buffer valid points.""" |
| 34 | + # try: |
| 35 | + # async for msg in ws: |
| 36 | + # data = json.loads(msg) |
| 37 | + # if isinstance(data, dict) and {'x','y','z'}.issubset(data): |
| 38 | + # sc.sticky['ws_buffer'].append(data) |
| 39 | + # sc.sticky['status'] = f"Buffered {len(sc.sticky['ws_buffer'])} pts" |
| 40 | + # ghenv.Component.ExpireSolution(True) # noqa: F821 |
| 41 | + # except Exception: |
| 42 | + # pass |
| 43 | + |
| 44 | + # def server_thread(): |
| 45 | + # # Create and set a new event loop in this thread |
| 46 | + # loop = asyncio.new_event_loop() |
| 47 | + # sc.sticky['ws_loop'] = loop |
| 48 | + # asyncio.set_event_loop(loop) |
| 49 | + # try: |
| 50 | + # # Start the WebSocket server on this loop |
| 51 | + # start_srv = websockets.serve(handler, i_host, i_port) |
| 52 | + # server = loop.run_until_complete(start_srv) |
| 53 | + # sc.sticky['ws_server'] = server |
| 54 | + # sc.sticky['status'] = f"Listening ws://{i_host}:{i_port}" |
| 55 | + # ghenv.Component.ExpireSolution(True) # noqa: F821 |
| 56 | + # # Serve forever until stopped |
| 57 | + # loop.run_forever() |
| 58 | + # except Exception as ex: |
| 59 | + # sc.sticky['status'] = f"Server error: {type(ex).__name__}: {ex}" |
| 60 | + # ghenv.Component.ExpireSolution(True) # noqa: F821 |
| 61 | + # finally: |
| 62 | + # # Cleanup: wait for server to close then shutdown loop |
| 63 | + # srv = sc.sticky.get('ws_server') |
| 64 | + # if srv: |
| 65 | + # loop.run_until_complete(srv.wait_closed()) |
| 66 | + # loop.close() |
| 67 | + # sc.sticky['ws_loop'] = None |
| 68 | + # sc.sticky['ws_server'] = None |
| 69 | + |
| 70 | + # def start(): |
| 71 | + # # Begin server thread on rising edge |
| 72 | + # if sc.sticky['ws_thread'] is None: |
| 73 | + # sc.sticky['status'] = 'Starting WebSocket server...' |
| 74 | + # ghenv.Component.Message = sc.sticky['status'] # noqa: F821 |
| 75 | + # t = threading.Thread(target=server_thread, daemon=True) |
| 76 | + # sc.sticky['ws_thread'] = t |
| 77 | + # t.start() |
| 78 | + |
| 79 | + # def stop(): |
| 80 | + # # Signal server and loop to stop |
| 81 | + # server = sc.sticky.get('ws_server') |
| 82 | + # loop = sc.sticky.get('ws_loop') |
| 83 | + # if server and loop: |
| 84 | + # loop.call_soon_threadsafe(server.close) |
| 85 | + # loop.call_soon_threadsafe(loop.stop) |
| 86 | + # sc.sticky['status'] = 'Stopped' |
| 87 | + # sc.sticky['ws_buffer'] = [] |
| 88 | + # sc.sticky['ws_thread'] = None |
| 89 | + # ghenv.Component.Message = sc.sticky['status'] # noqa: F821 |
| 90 | + |
| 91 | + # # Handle toggles |
| 92 | + # if i_start and not sc.sticky['prev_start']: |
| 93 | + # start() |
| 94 | + # if i_stop and not sc.sticky['prev_stop']: |
| 95 | + # stop() |
| 96 | + # if i_load and not sc.sticky['prev_load']: |
| 97 | + # buf = sc.sticky['ws_buffer'] |
| 98 | + # if buf: |
| 99 | + # pc = rg.PointCloud() |
| 100 | + # for pt in buf: |
| 101 | + # pc.Add(rg.Point3d(pt['x'], pt['y'], pt['z']), sd.Color.White) |
| 102 | + # sc.sticky['ws_latest'] = pc |
| 103 | + # sc.sticky['status'] = f"Retrieved {pc.Count} pts" |
| 104 | + # sc.sticky['ws_buffer'] = [] |
| 105 | + # else: |
| 106 | + # sc.sticky['status'] = 'No data buffered' |
| 107 | + # ghenv.Component.Message = sc.sticky['status'] # noqa: F821 |
| 108 | + |
| 109 | + # # Update previous states |
| 110 | + # sc.sticky['prev_start'] = i_start |
| 111 | + # sc.sticky['prev_stop'] = i_stop |
| 112 | + # sc.sticky['prev_load'] = i_load |
| 113 | + |
| 114 | + # # Always update message |
| 115 | + # ghenv.Component.Message = sc.sticky['status'] # noqa: F821 |
| 116 | + |
| 117 | + # o_cloud = sc.sticky.get('ws_latest') |
| 118 | + # return [o_cloud] |
0 commit comments