Skip to content

Commit

Permalink
simplifying ipython->webviewer
Browse files Browse the repository at this point in the history
  • Loading branch information
Clement Jambou committed Nov 7, 2024
1 parent 662e6da commit ae58855
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/foundation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@
from foundation.types.types_3d.point_3d import Point3D
from foundation.types.types_3d.curve import Helix3D
from foundation.operations.sweep import Sweep
from foundation.utils_websocket import set_port
14 changes: 10 additions & 4 deletions src/foundation/operations/extrude.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from foundation.operations.base import Operation
from foundation.types.node import Node

from foundation.sketch.base import SketchElement
from foundation.sketch.point import Point
from foundation.sketch.closed_sketch_shape import Circle, ClosedSketchShape
from foundation.types.node_children import NodeChildren
from foundation.sketch.sketch import Sketch
Expand Down Expand Up @@ -69,8 +69,14 @@ def get_frame(self):
# NOTE this is quite restrictive as a definition ( different type of holes for machining will need to be implemented
# including threads, profiles ... )
class Hole(Extrusion):
def __init__(self, point, radius, depth):
def __init__(
self,
point: Point,
radius: UnCastFloat,
depth: UnCastFloat,
other_depth: UnCastFloat = 0.0,
):
shape = Circle(point, radius)
super().__init__(shape=shape, end=depth, cut=True)
self.diameter = radius
super().__init__(shape=shape, end=depth, start=other_depth, cut=True)
self.diameter = cast_to_float_parameter(radius)
self.point = point
7 changes: 3 additions & 4 deletions src/foundation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ def show(x: DISPLAY_TYPE) -> None:
dag = show_dag(x)

try:
ws = websocket.create_connection("ws://127.0.0.1:3000")
json_str = json.dumps(dag)
ws.send(json_str)
reset_ids()
from foundation.utils_websocket import show_ext

show_ext(dag)
except Exception:
print("WebSocket not available")

Expand Down
94 changes: 94 additions & 0 deletions src/foundation/utils_websocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import asyncio
import threading
import time
from typing import Any, Dict
import json
import websockets
from foundation.utils import reset_ids

# Global variables to manage the server and clients
server_instance = None
connected_clients = set()
message_buffer: list[str] = [] # Buffer to store messages when no clients are connected
server_event_loop = None # Event loop for the server

PORT = 3001


def set_port(port: int):
global PORT
PORT = port


async def handle_connection(websocket, path):
"""Handle incoming WebSocket connections."""
connected_clients.add(websocket)
print(f"Client connected: {websocket.remote_address}")

# Send buffered messages to the newly connected client
for message in message_buffer:
try:
await websocket.send(message)
except Exception as e:
print(f"Error sending buffered message to {websocket.remote_address}: {e}")

# Clear the buffer after sending
message_buffer.clear()

try:
# Keep the connection open to send multiple messages
await websocket.wait_closed()
finally:
connected_clients.remove(websocket)
print(f"Client disconnected: {websocket.remote_address}")


async def start_server():
"""Start the WebSocket server if not already running."""
global server_instance
if server_instance is None:
server_instance = await websockets.serve(handle_connection, "127.0.0.1", PORT)
print(f"WebSocket server started on ws://127.0.0.1:{PORT}")


def start_server_in_background():
global server_event_loop
server_event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(server_event_loop)
server_event_loop.run_until_complete(start_server())
server_event_loop.run_forever()


def send_to_clients(data: Dict):
"""Send data to all connected WebSocket clients. If no clients, buffer the data."""
message = json.dumps(data)
if connected_clients and server_event_loop is not None:
asyncio.run_coroutine_threadsafe(_send_all_clients(message), server_event_loop)
else:
print("No clients connected to send data. Buffering message.")
message_buffer.append(message) # Store serialized message


async def _send_all_clients(message: str):
"""Helper coroutine to send messages to all clients."""
if connected_clients:
await asyncio.gather(
*(client.send(message) for client in connected_clients),
return_exceptions=True,
)


def show_ext(dag: Any) -> None:
"""Function to generate DAG data and send it via WebSocket."""
try:
global server_instance
if server_instance is None:
# Start the server in a background thread
threading.Thread(target=start_server_in_background, daemon=True).start()
# Give the server time to start
time.sleep(0.1)
# Send the data
send_to_clients(dag)
reset_ids()
except Exception as e:
print(f"WebSocket error: {e}")

0 comments on commit ae58855

Please sign in to comment.