Source code for ggblab.ggbapplet

import asyncio
import re
import ipykernel.connect

from IPython.core.getipython import get_ipython
from ipylab import JupyterFrontEnd

from .comm import ggb_comm
from .construction import ggb_construction
from .parser import ggb_parser

[docs] class GeoGebra: """Main interface for controlling GeoGebra applets from Python. This class implements a singleton pattern to ensure only one GeoGebra instance per kernel session. It provides async methods for sending commands and calling GeoGebra API functions. The communication uses a dual-channel architecture: - IPython Comm: Primary control channel - Unix socket/TCP WebSocket: Out-of-band response delivery during cell execution Attributes: construction (ggb_construction): File loader/saver for .ggb files parser (ggb_parser): Dependency graph parser comm (ggb_comm): Communication layer (initialized after init()) kernel_id (str): Current Jupyter kernel ID app (JupyterFrontEnd): ipylab frontend interface Example: >>> ggb = GeoGebra() >>> await ggb.init() >>> await ggb.command("A=(0,0)") >>> result = await ggb.function("getValue", ["A"]) """ _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): self.initialized = False self.construction = ggb_construction() self.parser = ggb_parser()
[docs] async def init(self): """Initialize the GeoGebra widget and communication channels. This method: 1. Starts the out-of-band socket server (Unix socket on POSIX, TCP WebSocket on Windows) 2. Registers the IPython Comm target ('ggblab-comm') 3. Opens the GeoGebra widget panel via ipylab with communication settings The widget is launched programmatically to pass kernel-specific settings (Comm target, socket path) before initialization, avoiding the limitations of fixed arguments from Launcher/Command Palette. Returns: GeoGebra: Self reference for method chaining. Example: >>> ggb = await GeoGebra().init() >>> # GeoGebra panel opens in split-right position """ if not self.initialized: self.comm = ggb_comm() self.comm.start() while self.comm.socketPath is None: await asyncio.sleep(.01) self.comm.register_target() _connection_file = ipykernel.connect.get_connection_file() self.kernel_id = re.search(r'kernel-(.*)\.json', _connection_file).group(1) self.app = JupyterFrontEnd() self.app.commands.execute('ggblab:create', { 'kernelId': self.kernel_id, 'commTarget': 'ggblab-comm', 'insertMode': 'split-right', 'socketPath': self.comm.socketPath, # 'wsPort': self.comm.wsPort, }) self._initialized = True return self
[docs] async def function(self, f, args=None): """Call a GeoGebra API function. Args: f (str): GeoGebra API function name (e.g., "getValue", "getXML"). args (list, optional): Function arguments. Defaults to None. Returns: Any: Function return value from GeoGebra. Example: >>> value = await ggb.function("getValue", ["A"]) >>> xml = await ggb.function("getXML", ["A"]) >>> all_objs = await ggb.function("getAllObjectNames") """ r = await self.comm.send_recv({ "type": "function", "payload": { "name": f, "args": args } }) return r['value']
[docs] async def command(self, c): """Execute a GeoGebra command. Args: c (str): GeoGebra command string (e.g., "A=(0,0)", "Circle(A, 2)"). Returns: dict: Response from GeoGebra (typically includes object label). Example: >>> await ggb.command("A=(0,0)") >>> await ggb.command("B=(3,4)") >>> await ggb.command("Circle(A, Distance(A, B))") """ return await self.comm.send_recv({ "type": "command", "payload": c })