Skip to content

Commit 900f32a

Browse files
committed
Dot primitive that echo's Rhino dot command
1 parent c3d397e commit 900f32a

File tree

7 files changed

+192
-62
lines changed

7 files changed

+192
-62
lines changed

notebooks/20_geometry.ipynb

Lines changed: 8 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"cells": [
33
{
44
"cell_type": "code",
5-
"execution_count": 2,
5+
"execution_count": 1,
66
"metadata": {},
77
"outputs": [],
88
"source": [
@@ -18,70 +18,23 @@
1818
"from compas.geometry import Vector\n",
1919
"from compas.geometry import SphericalSurface\n",
2020
"from compas.geometry import CylindricalSurface\n",
21+
"from compas_notebook.geometry import Dot\n",
2122
"from compas_notebook.viewer import Viewer"
2223
]
2324
},
2425
{
2526
"cell_type": "code",
26-
"execution_count": 5,
27+
"execution_count": null,
2728
"metadata": {},
2829
"outputs": [],
29-
"source": [
30-
"point = Point(-1, 2, 3)\n",
31-
"vector = Vector(1, 1, 2)\n",
32-
"plane = Plane([0, 0, -1], [0, 0, 1])\n",
33-
"frame = Frame(point, [1, 0, 0], [0, 1, 0])\n",
34-
"\n",
35-
"line = Line([0, 0, 0], point)\n",
36-
"cloud = Pointcloud.from_bounds(x=8, y=5, z=3, n=13)\n",
37-
"polyline = Polyline(cloud.points)\n",
38-
"\n",
39-
"# quadric curves\n",
40-
"circle = Circle(radius=1.5, frame=Frame([2, 0, 0], [0, 1, 0], [0, 0, 1]))\n",
41-
"ellipse = Ellipse(major=2.0, minor=1.0, frame=Frame([5, 0, 0], [0, 1, 0], [0, 0, 1]))\n",
42-
"\n",
43-
"# Create analytical surfaces\n",
44-
"sphere_surface = SphericalSurface(radius=1.5, frame=Frame([0, 0, 0], [1, 0, 0], [0, 1, 0]))\n",
45-
"cylinder_surface = CylindricalSurface(radius=0.8, frame=Frame([3, 0, 0], [1, 0, 0], [0, 1, 0]))"
46-
]
30+
"source": "point = Point(-1, 2, 3)\nvector = Vector(1, 1, 2)\nplane = Plane([0, 0, -1], [0, 0, 1])\nframe = Frame(point, [1, 0, 0], [0, 1, 0])\n\nline = Line([0, 0, 0], point)\ncloud = Pointcloud.from_bounds(x=8, y=5, z=3, n=13)\npolyline = Polyline(cloud.points)\n\n# quadric curves\ncircle = Circle(radius=1.5, frame=Frame([2, 0, 0], [0, 1, 0], [0, 0, 1]))\nellipse = Ellipse(major=2.0, minor=1.0, frame=Frame([5, 0, 0], [0, 1, 0], [0, 0, 1]))\n\n# Create analytical surfaces\nsphere_surface = SphericalSurface(radius=1.5, frame=Frame([0, 0, 0], [1, 0, 0], [0, 1, 0]))\ncylinder_surface = CylindricalSurface(radius=0.8, frame=Frame([3, 0, 0], [1, 0, 0], [0, 1, 0]))\n\n# Dot: text label at a point (constant screen size)\ndot = Dot([8, 5, 3], \"Corner\")"
4731
},
4832
{
4933
"cell_type": "code",
50-
"execution_count": 7,
34+
"execution_count": null,
5135
"metadata": {},
52-
"outputs": [
53-
{
54-
"data": {
55-
"application/vnd.jupyter.widget-view+json": {
56-
"model_id": "d32833ac778148c0920dfa0e9b672076",
57-
"version_major": 2,
58-
"version_minor": 0
59-
},
60-
"text/plain": [
61-
"VBox(children=(HBox(children=(Button(icon='search-plus', layout=Layout(height='32px', width='48px'), style=But…"
62-
]
63-
},
64-
"metadata": {},
65-
"output_type": "display_data"
66-
}
67-
],
68-
"source": [
69-
"viewer = Viewer()\n",
70-
"\n",
71-
"viewer.scene.add(point, color=Color.red(), pointsize=0.3)\n",
72-
"viewer.scene.add(line)\n",
73-
"viewer.scene.add(frame)\n",
74-
"viewer.scene.add(polyline, color=Color.blue())\n",
75-
"viewer.scene.add(cloud, color=Color.green(), pointsize=0.3)\n",
76-
"viewer.scene.add(circle, color=Color.red())\n",
77-
"viewer.scene.add(ellipse, color=Color.green())\n",
78-
"viewer.scene.add(vector)\n",
79-
"viewer.scene.add(plane)\n",
80-
"viewer.scene.add(sphere_surface, color=Color.cyan())\n",
81-
"viewer.scene.add(cylinder_surface, color=Color.magenta())\n",
82-
"\n",
83-
"viewer.show()"
84-
]
36+
"outputs": [],
37+
"source": "viewer = Viewer()\n\nviewer.scene.add(point, color=Color.red(), pointsize=0.3)\nviewer.scene.add(line)\nviewer.scene.add(frame)\nviewer.scene.add(polyline, color=Color.blue())\nviewer.scene.add(cloud, color=Color.green(), pointsize=0.3)\nviewer.scene.add(circle, color=Color.red())\nviewer.scene.add(ellipse, color=Color.green())\nviewer.scene.add(vector)\nviewer.scene.add(plane)\nviewer.scene.add(sphere_surface, color=Color.cyan())\nviewer.scene.add(cylinder_surface, color=Color.magenta())\nviewer.scene.add(dot, color=Color.black())\n\nviewer.show()"
8538
}
8639
],
8740
"metadata": {
@@ -105,4 +58,4 @@
10558
},
10659
"nbformat": 4,
10760
"nbformat_minor": 4
108-
}
61+
}

src/compas_notebook/conversions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .geometry import box_to_threejs
44
from .geometry import cone_to_threejs
55
from .geometry import cylinder_to_threejs
6+
from .geometry import dot_to_threejs
67
from .geometry import line_to_threejs
78
from .geometry import point_to_threejs
89
from .geometry import pointcloud_to_threejs
@@ -26,6 +27,7 @@
2627
"color_to_threejs",
2728
"cone_to_threejs",
2829
"cylinder_to_threejs",
30+
"dot_to_threejs",
2931
"line_to_threejs",
3032
"nodes_and_edges_to_threejs",
3133
"nodes_to_threejs",

src/compas_notebook/conversions/geometry.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from compas.geometry import Surface
1919
from compas.geometry import Torus
2020
from compas.geometry import Vector
21+
from compas_notebook.geometry import Dot
2122
from compas_notebook.conversions.meshes import vertices_and_edges_to_threejs
2223
from compas_notebook.conversions.meshes import vertices_and_faces_to_threejs
2324

@@ -35,7 +36,7 @@ def line_to_threejs(line: Line) -> three.BufferGeometry:
3536
:class:`three.BufferGeometry`
3637
3738
"""
38-
vertices = numpy.array([line.start, line.end], dtype=numpy.float64)
39+
vertices = numpy.array([line.start, line.end], dtype=numpy.float32)
3940
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
4041
return geometry
4142

@@ -93,7 +94,7 @@ def point_to_threejs(point: Point) -> three.SphereGeometry:
9394
BufferGeometry(...)
9495
9596
"""
96-
vertices = numpy.array([point], dtype=numpy.float64)
97+
vertices = numpy.array([point], dtype=numpy.float32)
9798
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
9899
return geometry
99100

@@ -115,7 +116,7 @@ def pointcloud_to_threejs(pointcloud: Pointcloud) -> three.SphereGeometry:
115116
>>>
116117
117118
"""
118-
vertices = numpy.array(pointcloud.points, dtype=numpy.float64)
119+
vertices = numpy.array(pointcloud.points, dtype=numpy.float32)
119120
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
120121
return geometry
121122

@@ -133,7 +134,7 @@ def polyline_to_threejs(polyline: Polyline) -> three.BufferGeometry:
133134
:class:`three.BufferGeometry`
134135
135136
"""
136-
vertices = numpy.array(polyline.points, dtype=numpy.float64)
137+
vertices = numpy.array(polyline.points, dtype=numpy.float32)
137138
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
138139
return geometry
139140

@@ -156,7 +157,7 @@ def circle_to_threejs(circle: Circle, max_angle: float = 5.0) -> three.BufferGeo
156157

157158
n = max(8, int(math.ceil(360.0 / max_angle)))
158159
polyline = circle.to_polyline(n=n)
159-
vertices = numpy.array(polyline.points, dtype=numpy.float64)
160+
vertices = numpy.array(polyline.points, dtype=numpy.float32)
160161
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
161162
return geometry
162163

@@ -178,7 +179,7 @@ def ellipse_to_threejs(ellipse: Ellipse, max_angle: float = 5.0) -> three.Buffer
178179
"""
179180
n = max(8, int(math.ceil(360.0 / max_angle)))
180181
polyline = ellipse.to_polyline(n=n)
181-
vertices = numpy.array(polyline.points, dtype=numpy.float64)
182+
vertices = numpy.array(polyline.points, dtype=numpy.float32)
182183
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
183184
return geometry
184185

@@ -326,7 +327,7 @@ def curve_to_threejs(curve: Curve, resolution: int = 100) -> three.BufferGeometr
326327
327328
"""
328329
polyline = curve.to_polyline(n=resolution)
329-
vertices = numpy.array(polyline.points, dtype=numpy.float64)
330+
vertices = numpy.array(polyline.points, dtype=numpy.float32)
330331
geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)})
331332
return geometry
332333

@@ -505,3 +506,34 @@ def torus_to_threejs(torus: Torus) -> three.TorusGeometry:
505506
radialSegments=64,
506507
tubularSegments=32,
507508
)
509+
510+
511+
def dot_to_threejs(dot: Dot, fontsize: int = 48, color: str = "white") -> three.Sprite:
512+
"""Convert a COMPAS Dot to PyThreeJS Sprite.
513+
514+
The sprite maintains constant screen size regardless of zoom level.
515+
516+
Parameters
517+
----------
518+
dot : :class:`compas_notebook.geometry.Dot`
519+
The dot to convert.
520+
fontsize : int, optional
521+
Font size for the text texture.
522+
color : str, optional
523+
Text color.
524+
525+
Returns
526+
-------
527+
:class:`three.Sprite`
528+
A sprite with text texture positioned at the dot location.
529+
530+
"""
531+
texture = three.TextTexture(string=dot.text, size=fontsize, color=color, squareTexture=False)
532+
material = three.SpriteMaterial(map=texture, sizeAttenuation=False, transparent=True)
533+
sprite = three.Sprite(material=material)
534+
sprite.position = [dot.point.x, dot.point.y, dot.point.z]
535+
# scale based on text length - roughly 0.6 width per character
536+
aspect = len(dot.text) * 0.6
537+
scale = 0.025
538+
sprite.scale = [scale * aspect, scale, 1]
539+
return sprite
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .dot import Dot
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from compas.geometry import Geometry
2+
from compas.geometry import Point
3+
4+
5+
class Dot(Geometry):
6+
"""A dot is text displayed at a point location.
7+
8+
The dot maintains constant screen size regardless of zoom level,
9+
similar to Rhino's Dot command.
10+
11+
Parameters
12+
----------
13+
point : :class:`compas.geometry.Point` | list[float]
14+
The location of the dot.
15+
text : str
16+
The text to display.
17+
name : str, optional
18+
The name of the dot.
19+
20+
Attributes
21+
----------
22+
point : :class:`compas.geometry.Point`
23+
The location of the dot.
24+
text : str
25+
The text to display.
26+
27+
Examples
28+
--------
29+
>>> from compas.geometry import Point
30+
>>> from compas_notebook.geometry import Dot
31+
>>> dot = Dot([0, 0, 0], "Hello")
32+
>>> dot.point
33+
Point(x=0.000, y=0.000, z=0.000)
34+
>>> dot.text
35+
'Hello'
36+
37+
"""
38+
39+
DATASCHEMA = {
40+
"type": "object",
41+
"properties": {
42+
"point": Point.DATASCHEMA,
43+
"text": {"type": "string"},
44+
},
45+
"required": ["point", "text"],
46+
}
47+
48+
@property
49+
def __data__(self):
50+
return {
51+
"point": self._point.__data__,
52+
"text": self._text,
53+
}
54+
55+
@classmethod
56+
def __from_data__(cls, data):
57+
return cls(
58+
point=Point.__from_data__(data["point"]),
59+
text=data["text"],
60+
)
61+
62+
def __init__(self, point, text, name=None):
63+
super().__init__(name=name)
64+
self._point = None
65+
self._text = None
66+
self.point = point
67+
self.text = text
68+
69+
def __repr__(self):
70+
return f"Dot({self.point!r}, {self.text!r})"
71+
72+
def __eq__(self, other):
73+
if not isinstance(other, Dot):
74+
return False
75+
return self.point == other.point and self.text == other.text
76+
77+
@property
78+
def point(self):
79+
return self._point
80+
81+
@point.setter
82+
def point(self, value):
83+
if not isinstance(value, Point):
84+
value = Point(*value)
85+
self._point = value
86+
87+
@property
88+
def text(self):
89+
return self._text
90+
91+
@text.setter
92+
def text(self, value):
93+
self._text = str(value)
94+
95+
def transform(self, transformation):
96+
"""Transform the dot.
97+
98+
Parameters
99+
----------
100+
transformation : :class:`compas.geometry.Transformation`
101+
The transformation to apply.
102+
103+
Returns
104+
-------
105+
None
106+
107+
"""
108+
self.point.transform(transformation)

src/compas_notebook/scene/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from compas.geometry import Torus
2929
from compas.geometry import Vector
3030

31+
from compas_notebook.geometry import Dot
32+
3133
from compas.datastructures import Graph
3234
from compas.datastructures import Mesh
3335

@@ -42,6 +44,7 @@
4244
from .coneobject import ThreeConeObject
4345
from .curveobject import ThreeCurveObject
4446
from .cylinderobject import ThreeCylinderObject
47+
from .dotobject import ThreeDotObject
4548
from .ellipseobject import ThreeEllipseObject
4649
from .frameobject import ThreeFrameObject
4750
from .groupobject import ThreeGroupObject
@@ -89,6 +92,7 @@ def register_scene_objects():
8992
register(Cone, ThreeConeObject, context="Notebook")
9093
register(Curve, ThreeCurveObject, context="Notebook")
9194
register(Cylinder, ThreeCylinderObject, context="Notebook")
95+
register(Dot, ThreeDotObject, context="Notebook")
9296
register(Ellipse, ThreeEllipseObject, context="Notebook")
9397
register(Frame, ThreeFrameObject, context="Notebook")
9498
register(Graph, ThreeGraphObject, context="Notebook")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from compas.colors import Color
2+
from compas.scene import GeometryObject
3+
from compas.scene.descriptors.color import ColorAttribute
4+
5+
from compas_notebook.conversions import dot_to_threejs
6+
7+
from .sceneobject import ThreeSceneObject
8+
9+
10+
class ThreeDotObject(ThreeSceneObject, GeometryObject):
11+
"""Scene object for drawing a dot (text at a point)."""
12+
13+
color = ColorAttribute(default=Color.black())
14+
15+
def __init__(self, fontsize=256, **kwargs):
16+
super().__init__(**kwargs)
17+
self.fontsize = fontsize
18+
19+
def draw(self):
20+
"""Draw the dot associated with the scene object.
21+
22+
Returns
23+
-------
24+
list[three.Sprite]
25+
List of pythreejs objects created.
26+
27+
"""
28+
sprite = dot_to_threejs(self.geometry, fontsize=self.fontsize, color=self.color.hex)
29+
self._guids = [sprite]
30+
return self.guids

0 commit comments

Comments
 (0)