Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `Toolbar`: lets the backend show/hide or enable/disable a frontend-defined toolbar button by id (`app.toolbar.set_visible(id, bool)`, `app.toolbar.set_enabled(id, bool)`). `App.toolbar`/`Workspace.toolbar` expose it, delegated the same way as `background_color`, `world_axis`, etc.
- `App(title=...)`: sets the browser tab title of the viewer page (default `"COMPAS ThreeJS"`), passed to the frontend as a `title` query param. `Workspace.open_in_browser(title=...)` overrides it per workspace, falling back to the App's title.

### Changed

Expand Down
36 changes: 36 additions & 0 deletions examples/tab_title.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from compas.colors import Color
from compas.geometry import Box
from compas.geometry import Point
from compas.geometry import Sphere

from compas_threejs.materials import Material
from compas_threejs.tag import TextTag
from compas_threejs.ui import Button
from compas_threejs.viewer import App

# `title` sets the browser tab title (and the toolbar heading, replacing the
# default "COMPAS ThreeJS") for the main workspace's page.
viz = App(title="Bridge Inspector")

box = Box(1, 1, 1)
viz.add_geometry(box, material=Material(Color.blue()))
viz.add_tag(TextTag(point=Point(0, 0, 0.5), text="The Box", color=Color.blue()))

# A second workspace opened in its own browser tab. Without a `title` it falls
# back to the App's own title; pass one to give it a distinct tab title instead.
detail_workspace = viz.get_workspace("sphere")
detail_workspace.add_default_lighting()

sphere = Sphere(1)
detail_workspace.add_geometry(sphere, material=Material(Color.red()))
detail_workspace.add_tag(TextTag(point=Point(0, 0, 1), text="The Sphere", color=Color.red()))


def open_detail_view():
detail_workspace.open_in_browser(title="Bridge Inspector - Sphere Detail")


open_btn = Button("Open Sphere Detail View", action=open_detail_view)
viz.add_ui_element(open_btn)

viz.start(show=True)
16 changes: 13 additions & 3 deletions src/compas_threejs/viewer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import webbrowser
from typing import Optional
from typing import Union
from urllib.parse import quote

from compas.geometry import Point
from rich.console import Console
Expand Down Expand Up @@ -81,7 +82,13 @@ class App:

"""

def __init__(self, host: str = "127.0.0.1", websocket_port: int = 9001, frontend_dir=None):
def __init__(
self,
host: str = "127.0.0.1",
websocket_port: int = 9001,
frontend_dir=None,
title: str = "COMPAS ThreeJS",
):
"""
Parameters
----------
Expand All @@ -91,11 +98,14 @@ def __init__(self, host: str = "127.0.0.1", websocket_port: int = 9001, frontend
frontend to its own copy - e.g. one it rebuilds itself from a branch or fork of the
frontend source - independent of whatever happens to be bundled in this installed
package.
title : str, optional
Browser tab title for the viewer page. Default is "COMPAS ThreeJS".
"""
# Server
self.host = host
self.websocket_port = websocket_port
self.websocket_server_thread = None
self.title = title

self.server = AppServer(frontend_dir=frontend_dir)
self.outbox = Outbox(self.server)
Expand Down Expand Up @@ -253,7 +263,7 @@ def toolbar(self):
def url(self) -> str:
"""Get the URL to access the viewer in a web browser."""
display_host = self.host if self.host != "0.0.0.0" else get_local_ip()
return f"http://{display_host}:{self.websocket_port}/?ws_host={display_host}&ws_port={self.websocket_port}"
return f"http://{display_host}:{self.websocket_port}/?ws_host={display_host}&ws_port={self.websocket_port}&title={quote(self.title)}"

def set_view(self, view: Union[CameraView, Point, tuple, list], target=None):
"""Set the main workspace's camera view from a preset or explicit point.
Expand Down Expand Up @@ -348,7 +358,7 @@ def show(self):
else:
connect_host = self.host

shareable_url = f"http://{connect_host}:{self.websocket_port}/?ws_host={connect_host}&ws_port={self.websocket_port}"
shareable_url = f"http://{connect_host}:{self.websocket_port}/?ws_host={connect_host}&ws_port={self.websocket_port}&title={quote(self.title)}"
console.log(f"[underline yellow]{shareable_url}[/underline yellow]")
webbrowser.open(shareable_url)

Expand Down
17 changes: 14 additions & 3 deletions src/compas_threejs/viewer/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import IntEnum
from typing import Optional
from typing import Union
from urllib.parse import quote

import compas_pb
from compas.colors import Color
Expand Down Expand Up @@ -607,9 +608,19 @@ def stop_spinner(self):
workspace_id=self.workspace_id,
)

def open_in_browser(self):
"""Launches a browser window tracking only this workspace."""
def open_in_browser(self, title: Optional[str] = None):
"""Launches a browser window tracking only this workspace.

Parameters
----------
title : str, optional
Browser tab title for this workspace's page. Defaults to the App's own `title`.
"""
connect_host = self.app.host if self.app.host != "0.0.0.0" else "127.0.0.1"

url = f"http://{connect_host}:{self.app.websocket_port}/?ws_host={connect_host}&ws_port={self.app.websocket_port}&workspace={self.workspace_id}"
url = (
f"http://{connect_host}:{self.app.websocket_port}/"
f"?ws_host={connect_host}&ws_port={self.app.websocket_port}"
f"&workspace={self.workspace_id}&title={quote(title or self.app.title)}"
)
webbrowser.open(url)
Loading