diff --git a/CHANGELOG.md b/CHANGELOG.md index d61ffa5..6a4274b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added support for `frames, circle, ellipse, vector, plane, curve, analystical surface primitives` +* Dot command added + ### Changed ### Removed diff --git a/notebooks/00_basics.ipynb b/notebooks/00_basics.ipynb index b3db2c3..8d3f982 100644 --- a/notebooks/00_basics.ipynb +++ b/notebooks/00_basics.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/notebooks/10_scene.ipynb b/notebooks/10_scene.ipynb index a9aeab2..20156b7 100644 --- a/notebooks/10_scene.ipynb +++ b/notebooks/10_scene.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "code", "execution_count": 2, diff --git a/notebooks/20_geometry.ipynb b/notebooks/20_geometry.ipynb index 1ec2557..09ae476 100644 --- a/notebooks/20_geometry.ipynb +++ b/notebooks/20_geometry.ipynb @@ -5,72 +5,41 @@ "execution_count": 1, "metadata": {}, "outputs": [], - "source": [ - "%pip install -q compas_notebook" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "from compas.colors import Color\n", + "from compas.geometry import Circle\n", + "from compas.geometry import Ellipse\n", + "from compas.geometry import Frame\n", "from compas.geometry import Line\n", + "from compas.geometry import Plane\n", "from compas.geometry import Point\n", "from compas.geometry import Pointcloud\n", "from compas.geometry import Polyline\n", + "from compas.geometry import Vector\n", + "from compas.geometry import SphericalSurface\n", + "from compas.geometry import CylindricalSurface\n", + "from compas_notebook.geometry import Dot\n", "from compas_notebook.viewer import Viewer" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "cloud = Pointcloud.from_bounds(x=8, y=5, z=3, n=13)\n", - "\n", - "point = Point(-1, 2, 3)\n", - "line = Line([0, 0, 0], point)\n", - "polyline = Polyline(cloud.points)" - ] + "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\")" }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "a55820df515f4eb0a67494e16869aac2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(HBox(children=(Button(icon='search-plus', layout=Layout(height='32px', width='48px'), style=But…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "viewer = Viewer()\n", - "\n", - "viewer.scene.add(point, color=Color.red(), pointsize=0.3)\n", - "viewer.scene.add(line)\n", - "viewer.scene.add(polyline, color=Color.blue())\n", - "viewer.scene.add(cloud, color=Color.green(), pointsize=0.3)\n", - "\n", - "viewer.show()" - ] + "outputs": [], + "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()" } ], "metadata": { "kernelspec": { - "display_name": "compas2", + "display_name": "compas_opzuid", "language": "python", "name": "python3" }, @@ -84,9 +53,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.12.12" } }, "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/30_shapes.ipynb b/notebooks/30_shapes.ipynb index bec67ab..9436e87 100644 --- a/notebooks/30_shapes.ipynb +++ b/notebooks/30_shapes.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "code", "execution_count": 2, diff --git a/notebooks/60_breps.ipynb b/notebooks/60_breps.ipynb index a47316a..f79cd0d 100644 --- a/notebooks/60_breps.ipynb +++ b/notebooks/60_breps.ipynb @@ -1,22 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "code", "execution_count": 2, diff --git a/notebooks/70_graphs.ipynb b/notebooks/70_graphs.ipynb index 2b8334f..5457cef 100644 --- a/notebooks/70_graphs.ipynb +++ b/notebooks/70_graphs.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "code", "execution_count": 2, diff --git a/notebooks/80_groups.ipynb b/notebooks/80_groups.ipynb index 0e2ee94..41f68e8 100644 --- a/notebooks/80_groups.ipynb +++ b/notebooks/80_groups.ipynb @@ -1,22 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "%pip install -q compas_notebook" - ] - }, { "cell_type": "code", "execution_count": 2, diff --git a/src/compas_notebook/conversions/__init__.py b/src/compas_notebook/conversions/__init__.py index 76d101a..a0d6802 100644 --- a/src/compas_notebook/conversions/__init__.py +++ b/src/compas_notebook/conversions/__init__.py @@ -3,6 +3,7 @@ from .geometry import box_to_threejs from .geometry import cone_to_threejs from .geometry import cylinder_to_threejs +from .geometry import dot_to_threejs from .geometry import line_to_threejs from .geometry import point_to_threejs from .geometry import pointcloud_to_threejs @@ -26,6 +27,7 @@ "color_to_threejs", "cone_to_threejs", "cylinder_to_threejs", + "dot_to_threejs", "line_to_threejs", "nodes_and_edges_to_threejs", "nodes_to_threejs", diff --git a/src/compas_notebook/conversions/geometry.py b/src/compas_notebook/conversions/geometry.py index b13b31f..2804b50 100644 --- a/src/compas_notebook/conversions/geometry.py +++ b/src/compas_notebook/conversions/geometry.py @@ -1,16 +1,29 @@ +import re +import math import numpy import pythreejs as three from compas.geometry import Box +from compas.geometry import Circle from compas.geometry import Cone +from compas.geometry import Curve from compas.geometry import Cylinder +from compas.geometry import Ellipse +from compas.geometry import Frame +from compas.geometry import Line +from compas.geometry import Plane from compas.geometry import Point from compas.geometry import Pointcloud from compas.geometry import Polyline from compas.geometry import Sphere +from compas.geometry import Surface from compas.geometry import Torus +from compas.geometry import Vector +from compas_notebook.geometry import Dot +from compas_notebook.conversions.meshes import vertices_and_edges_to_threejs +from compas_notebook.conversions.meshes import vertices_and_faces_to_threejs -def line_to_threejs(line: Point) -> three.BufferGeometry: +def line_to_threejs(line: Line) -> three.BufferGeometry: """Convert a COMPAS line to PyThreeJS. Parameters @@ -28,6 +41,39 @@ def line_to_threejs(line: Point) -> three.BufferGeometry: return geometry +def frame_to_threejs(frame: Frame) -> list[three.BufferGeometry]: + """Convert a COMPAS frame to PyThreeJS. + + Parameters + ---------- + frame : :class:`compas.geometry.Frame` + The frame to convert. + + Returns + ------- + list[three.BufferGeometry] + + """ + + # create lines for each axis + _x_line = Line(frame.point, frame.point + frame.xaxis) + _y_line = Line.from_point_and_vector(frame.point, frame.yaxis) + _z_line = Line.from_point_and_vector(frame.point, frame.zaxis) + + # convert lines to threejs vertex buffers + xline_verts = line_to_threejs(_x_line) + yline_verts = line_to_threejs(_y_line) + zline_verts = line_to_threejs(_z_line) + + # convert from vertex buffers to line objects + xline_lines = three.Line(xline_verts, three.LineBasicMaterial(color="red")) + yline_lines = three.Line(yline_verts, three.LineBasicMaterial(color="green")) + zline_lines = three.Line(zline_verts, three.LineBasicMaterial(color="blue")) + + result = [xline_lines, yline_lines, zline_lines] + return result + + def point_to_threejs(point: Point) -> three.SphereGeometry: """Convert a COMPAS point to PyThreeJS. @@ -93,6 +139,239 @@ def polyline_to_threejs(polyline: Polyline) -> three.BufferGeometry: return geometry +def circle_to_threejs(circle: Circle, max_angle: float = 5.0) -> three.BufferGeometry: + """Convert a COMPAS circle to PyThreeJS. + + Parameters + ---------- + circle : :class:`compas.geometry.Circle` + The circle to convert. + max_angle : float, optional + Maximum angle in degrees between segments. + + Returns + ------- + :class:`three.BufferGeometry` + + """ + + n = max(8, int(math.ceil(360.0 / max_angle))) + polyline = circle.to_polyline(n=n) + vertices = numpy.array(polyline.points, dtype=numpy.float32) + geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)}) + return geometry + + +def ellipse_to_threejs(ellipse: Ellipse, max_angle: float = 5.0) -> three.BufferGeometry: + """Convert a COMPAS ellipse to PyThreeJS. + + Parameters + ---------- + ellipse : :class:`compas.geometry.Ellipse` + The ellipse to convert. + max_angle : float, optional + Maximum angle in degrees between segments. + + Returns + ------- + :class:`three.BufferGeometry` + + """ + n = max(8, int(math.ceil(360.0 / max_angle))) + polyline = ellipse.to_polyline(n=n) + vertices = numpy.array(polyline.points, dtype=numpy.float32) + geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)}) + return geometry + + +def vector_to_threejs(vector: Vector, scale: float = 1.0) -> list[three.Object3D]: + """Convert a COMPAS vector to PyThreeJS as arrow. + + Parameters + ---------- + vector : :class:`compas.geometry.Vector` + The vector to convert. + scale : float, optional + Scale factor for vector length. + + Returns + ------- + list[three.Object3D] + Line and cone representing the arrow. + + """ + # Line from origin to vector * scale + line = Line([0, 0, 0], vector * scale) + line_geom = line_to_threejs(line) + line_obj = three.Line(line_geom, three.LineBasicMaterial(color="blue")) + + # Cone head at tip (10% of length, positioned at end) + length = vector.length * scale + cone_height = length * 0.1 + cone_radius = cone_height * 0.3 + + # Position cone at vector tip + cone_geom = three.CylinderGeometry( + radiusTop=0, + radiusBottom=cone_radius, + height=cone_height, + radialSegments=8, + ) + cone_obj = three.Mesh(cone_geom, three.MeshBasicMaterial(color="blue")) + + # Compute cone position and rotation + # Cone should point in vector direction + tip = vector * scale + cone_obj.position = (tip.x, tip.y, tip.z) + + # Align cone with vector direction + # Three.js cylinder default is Y-up, need to rotate to align with vector + normalized = vector.unitized() + cone_obj.quaternion = _vector_to_quaternion(normalized) + + return [line_obj, cone_obj] + + +def _vector_to_quaternion(vector): + """Helper to compute quaternion for aligning Y-axis with vector.""" + import math + + # Default direction is Y-up [0, 1, 0] + y_axis = Vector(0, 1, 0) + + # Compute rotation axis (cross product) + axis = y_axis.cross(vector) + + # Compute rotation angle + angle = math.acos(max(-1, min(1, y_axis.dot(vector)))) + + if axis.length < 1e-10: + # Vector is parallel or anti-parallel to Y-axis + if vector.y > 0: + return (0, 0, 0, 1) # No rotation + else: + return (0, 0, 1, 0) # 180 degree rotation around Z + + # Normalize axis + axis = axis.unitized() + + # Convert axis-angle to quaternion + half_angle = angle / 2 + s = math.sin(half_angle) + return (axis.x * s, axis.y * s, axis.z * s, math.cos(half_angle)) + + +def plane_to_threejs(plane: Plane, size: float = 1.0, grid: int = 10) -> list[three.Object3D]: + """Convert a COMPAS plane to PyThreeJS as grid. + + Parameters + ---------- + plane : :class:`compas.geometry.Plane` + The plane to convert. + size : float, optional + Size of the grid visualization. + grid : int, optional + Number of grid lines in each direction. + + Returns + ------- + list[three.Object3D] + Grid lines in the plane. + + """ + objects = [] + + # Get frame from plane to have xaxis and yaxis + frame = Frame.from_plane(plane) + + # Create grid lines along xaxis and yaxis directions + step = size / grid + half = size / 2 + + # Lines parallel to xaxis + for i in range(grid + 1): + offset = -half + i * step + start = frame.point + frame.yaxis * offset - frame.xaxis * half + end = frame.point + frame.yaxis * offset + frame.xaxis * half + line = Line(start, end) + line_geom = line_to_threejs(line) + line_obj = three.Line(line_geom, three.LineBasicMaterial(color="lightgray")) + objects.append(line_obj) + + # Lines parallel to yaxis + for i in range(grid + 1): + offset = -half + i * step + start = frame.point + frame.xaxis * offset - frame.yaxis * half + end = frame.point + frame.xaxis * offset + frame.yaxis * half + line = Line(start, end) + line_geom = line_to_threejs(line) + line_obj = three.Line(line_geom, three.LineBasicMaterial(color="lightgray")) + objects.append(line_obj) + + return objects + + +def curve_to_threejs(curve: Curve, resolution: int = 100) -> three.BufferGeometry: + """Convert a COMPAS curve to PyThreeJS. + + Parameters + ---------- + curve : :class:`compas.geometry.Curve` + The curve to convert. + resolution : int, optional + Number of points for discretization. + + Returns + ------- + :class:`three.BufferGeometry` + + """ + polyline = curve.to_polyline(n=resolution) + vertices = numpy.array(polyline.points, dtype=numpy.float32) + geometry = three.BufferGeometry(attributes={"position": three.BufferAttribute(vertices, normalized=False)}) + return geometry + + +def surface_to_threejs(surface: Surface, resolution_u: int = 20, resolution_v: int = 20): + """Convert a COMPAS surface to PyThreeJS. + + Parameters + ---------- + surface : :class:`compas.geometry.Surface` + The surface to convert. + resolution_u : int, optional + Number of divisions in U direction. + resolution_v : int, optional + Number of divisions in V direction. + + Returns + ------- + tuple[three.Mesh, three.LineSegments] + Mesh and edge visualization. + + """ + vertices, faces = surface.to_vertices_and_faces(nu=resolution_u, nv=resolution_v) + + # Create faces + faces_geom = vertices_and_faces_to_threejs(vertices, faces) + mesh = three.Mesh(faces_geom, three.MeshBasicMaterial(color="lightblue", side="DoubleSide")) + + # Create edges + edges = [] + for face in faces: + n = len(face) + for i in range(n): + edge = (face[i], face[(i + 1) % n]) + # Add edge if not duplicate + if (edge[1], edge[0]) not in edges: + edges.append(edge) + + edges_geom = vertices_and_edges_to_threejs(vertices, edges) + lines = three.LineSegments(edges_geom, three.LineBasicMaterial(color="black")) + + return [mesh, lines] + + # ============================================================================= # Shapes # ============================================================================= @@ -227,3 +506,34 @@ def torus_to_threejs(torus: Torus) -> three.TorusGeometry: radialSegments=64, tubularSegments=32, ) + + +def dot_to_threejs(dot: Dot, fontsize: int = 48, color: str = "white") -> three.Sprite: + """Convert a COMPAS Dot to PyThreeJS Sprite. + + The sprite maintains constant screen size regardless of zoom level. + + Parameters + ---------- + dot : :class:`compas_notebook.geometry.Dot` + The dot to convert. + fontsize : int, optional + Font size for the text texture. + color : str, optional + Text color. + + Returns + ------- + :class:`three.Sprite` + A sprite with text texture positioned at the dot location. + + """ + texture = three.TextTexture(string=dot.text, size=fontsize, color=color, squareTexture=False) + material = three.SpriteMaterial(map=texture, sizeAttenuation=False, transparent=True) + sprite = three.Sprite(material=material) + sprite.position = [dot.point.x, dot.point.y, dot.point.z] + # scale based on text length - roughly 0.6 width per character + aspect = len(dot.text) * 0.6 + scale = 0.025 + sprite.scale = [scale * aspect, scale, 1] + return sprite diff --git a/src/compas_notebook/geometry/__init__.py b/src/compas_notebook/geometry/__init__.py new file mode 100644 index 0000000..70efd2e --- /dev/null +++ b/src/compas_notebook/geometry/__init__.py @@ -0,0 +1,3 @@ +from .dot import Dot + +__all__ = ["Dot",] # under protest diff --git a/src/compas_notebook/geometry/dot.py b/src/compas_notebook/geometry/dot.py new file mode 100644 index 0000000..b6358f8 --- /dev/null +++ b/src/compas_notebook/geometry/dot.py @@ -0,0 +1,108 @@ +from compas.geometry import Geometry +from compas.geometry import Point + + +class Dot(Geometry): + """A dot is text displayed at a point location. + + The dot maintains constant screen size regardless of zoom level, + similar to Rhino's Dot command. + + Parameters + ---------- + point : :class:`compas.geometry.Point` | list[float] + The location of the dot. + text : str + The text to display. + name : str, optional + The name of the dot. + + Attributes + ---------- + point : :class:`compas.geometry.Point` + The location of the dot. + text : str + The text to display. + + Examples + -------- + >>> from compas.geometry import Point + >>> from compas_notebook.geometry import Dot + >>> dot = Dot([0, 0, 0], "Hello") + >>> dot.point + Point(x=0.000, y=0.000, z=0.000) + >>> dot.text + 'Hello' + + """ + + DATASCHEMA = { + "type": "object", + "properties": { + "point": Point.DATASCHEMA, + "text": {"type": "string"}, + }, + "required": ["point", "text"], + } + + @property + def __data__(self): + return { + "point": self._point.__data__, + "text": self._text, + } + + @classmethod + def __from_data__(cls, data): + return cls( + point=Point.__from_data__(data["point"]), + text=data["text"], + ) + + def __init__(self, point, text, name=None): + super().__init__(name=name) + self._point = None + self._text = None + self.point = point + self.text = text + + def __repr__(self): + return f"Dot({self.point!r}, {self.text!r})" + + def __eq__(self, other): + if not isinstance(other, Dot): + return False + return self.point == other.point and self.text == other.text + + @property + def point(self): + return self._point + + @point.setter + def point(self, value): + if not isinstance(value, Point): + value = Point(*value) + self._point = value + + @property + def text(self): + return self._text + + @text.setter + def text(self, value): + self._text = str(value) + + def transform(self, transformation): + """Transform the dot. + + Parameters + ---------- + transformation : :class:`compas.geometry.Transformation` + The transformation to apply. + + Returns + ------- + None + + """ + self.point.transform(transformation) diff --git a/src/compas_notebook/scene/__init__.py b/src/compas_notebook/scene/__init__.py index a1d967b..ebf84a1 100644 --- a/src/compas_notebook/scene/__init__.py +++ b/src/compas_notebook/scene/__init__.py @@ -1,3 +1,4 @@ +# ruff: noqa: F401 """This package provides scene object plugins for visualising COMPAS objects in Jupyter Notebooks using three. When working in a notebook, :class:`compas.scene.SceneObject` will automatically use the corresponding PyThreeJS scene object for each COMPAS object type. @@ -10,16 +11,25 @@ from compas.geometry import Box from compas.geometry import Brep from compas.geometry import Capsule +from compas.geometry import Circle from compas.geometry import Cone +from compas.geometry import Curve from compas.geometry import Cylinder +from compas.geometry import Ellipse +from compas.geometry import Frame from compas.geometry import Line +from compas.geometry import Plane from compas.geometry import Point from compas.geometry import Pointcloud from compas.geometry import Polygon from compas.geometry import Polyhedron from compas.geometry import Polyline from compas.geometry import Sphere +from compas.geometry import Surface from compas.geometry import Torus +from compas.geometry import Vector + +from compas_notebook.geometry import Dot from compas.datastructures import Graph from compas.datastructures import Mesh @@ -31,22 +41,29 @@ from .boxobject import ThreeBoxObject from .brepobject import ThreeBrepObject from .capsuleobject import ThreeCapsuleObject +from .circleobject import ThreeCircleObject from .coneobject import ThreeConeObject +from .curveobject import ThreeCurveObject from .cylinderobject import ThreeCylinderObject +from .dotobject import ThreeDotObject +from .ellipseobject import ThreeEllipseObject +from .frameobject import ThreeFrameObject +from .groupobject import ThreeGroupObject from .lineobject import ThreeLineObject +from .planeobject import ThreePlaneObject from .pointobject import ThreePointObject from .pointcloudobject import ThreePointcloudObject from .polygonobject import ThreePolygonObject from .polyhedronobject import ThreePolyhedronObject from .polylineobject import ThreePolylineObject from .sphereobject import ThreeSphereObject +from .surfaceobject import ThreeSurfaceObject from .torusobject import ThreeTorusObject +from .vectorobject import ThreeVectorObject from .graphobject import ThreeGraphObject from .meshobject import ThreeMeshObject -from .groupobject import ThreeGroupObject - @plugin(category="drawing-utils", pluggable_name="clear", requires=["pythreejs"]) def clear_pythreejs(guids=None): @@ -72,34 +89,25 @@ def register_scene_objects(): register(Box, ThreeBoxObject, context="Notebook") register(Brep, ThreeBrepObject, context="Notebook") register(Capsule, ThreeCapsuleObject, context="Notebook") + register(Circle, ThreeCircleObject, context="Notebook") register(Cone, ThreeConeObject, context="Notebook") + register(Curve, ThreeCurveObject, context="Notebook") register(Cylinder, ThreeCylinderObject, context="Notebook") + register(Dot, ThreeDotObject, context="Notebook") + register(Ellipse, ThreeEllipseObject, context="Notebook") + register(Frame, ThreeFrameObject, context="Notebook") register(Graph, ThreeGraphObject, context="Notebook") register(Line, ThreeLineObject, context="Notebook") + register(Mesh, ThreeMeshObject, context="Notebook") + register(Plane, ThreePlaneObject, context="Notebook") register(Point, ThreePointObject, context="Notebook") register(Pointcloud, ThreePointcloudObject, context="Notebook") register(Polygon, ThreePolygonObject, context="Notebook") register(Polyhedron, ThreePolyhedronObject, context="Notebook") register(Polyline, ThreePolylineObject, context="Notebook") register(Sphere, ThreeSphereObject, context="Notebook") + register(Surface, ThreeSurfaceObject, context="Notebook") register(Torus, ThreeTorusObject, context="Notebook") - register(Mesh, ThreeMeshObject, context="Notebook") + register(Vector, ThreeVectorObject, context="Notebook") register(list, ThreeGroupObject, context="Notebook") - -__all__ = [ - "NotebookScene", - "ThreeBoxObject", - "ThreeCapsuleObject", - "ThreeConeObject", - "ThreeCylinderObject", - "ThreeGraphObject", - "ThreePointObject", - "ThreePointcloudObject", - "ThreePolygonObject", - "ThreePolyhedronObject", - "ThreePolylineObject", - "ThreeSceneObject", - "ThreeSphereObject", - "ThreeTorusObject", -] diff --git a/src/compas_notebook/scene/circleobject.py b/src/compas_notebook/scene/circleobject.py new file mode 100644 index 0000000..3abf3ed --- /dev/null +++ b/src/compas_notebook/scene/circleobject.py @@ -0,0 +1,26 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import circle_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeCircleObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing circles.""" + + def draw(self) -> list[three.Line]: + """Draw the circle as a discretized polyline. + + Returns + ------- + list[three.Line] + List of pythreejs objects created. + + """ + geometry = circle_to_threejs(self.geometry) + line = three.LineLoop(geometry, three.LineBasicMaterial(color=self.contrastcolor.hex)) + + self._guids = [line] + + return self.guids diff --git a/src/compas_notebook/scene/curveobject.py b/src/compas_notebook/scene/curveobject.py new file mode 100644 index 0000000..4831045 --- /dev/null +++ b/src/compas_notebook/scene/curveobject.py @@ -0,0 +1,26 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import curve_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeCurveObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing curves.""" + + def draw(self) -> list[three.Line]: + """Draw the curve as discretized polyline. + + Returns + ------- + list[three.Line] + List of pythreejs objects created. + + """ + geometry = curve_to_threejs(self.geometry) + line = three.Line(geometry, three.LineBasicMaterial(color=self.contrastcolor.hex)) + + self._guids = [line] + + return self.guids diff --git a/src/compas_notebook/scene/dotobject.py b/src/compas_notebook/scene/dotobject.py new file mode 100644 index 0000000..b56d9a7 --- /dev/null +++ b/src/compas_notebook/scene/dotobject.py @@ -0,0 +1,30 @@ +from compas.colors import Color +from compas.scene import GeometryObject +from compas.scene.descriptors.color import ColorAttribute + +from compas_notebook.conversions import dot_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeDotObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing a dot (text at a point).""" + + color = ColorAttribute(default=Color.black()) + + def __init__(self, fontsize=256, **kwargs): + super().__init__(**kwargs) + self.fontsize = fontsize + + def draw(self): + """Draw the dot associated with the scene object. + + Returns + ------- + list[three.Sprite] + List of pythreejs objects created. + + """ + sprite = dot_to_threejs(self.geometry, fontsize=self.fontsize, color=self.color.hex) + self._guids = [sprite] + return self.guids diff --git a/src/compas_notebook/scene/ellipseobject.py b/src/compas_notebook/scene/ellipseobject.py new file mode 100644 index 0000000..10582a3 --- /dev/null +++ b/src/compas_notebook/scene/ellipseobject.py @@ -0,0 +1,26 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import ellipse_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeEllipseObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing ellipses.""" + + def draw(self) -> list[three.Line]: + """Draw the ellipse as a discretized polyline. + + Returns + ------- + list[three.Line] + List of pythreejs objects created. + + """ + geometry = ellipse_to_threejs(self.geometry) + line = three.LineLoop(geometry, three.LineBasicMaterial(color=self.contrastcolor.hex)) + + self._guids = [line] + + return self.guids diff --git a/src/compas_notebook/scene/frameobject.py b/src/compas_notebook/scene/frameobject.py new file mode 100644 index 0000000..cf7e1b7 --- /dev/null +++ b/src/compas_notebook/scene/frameobject.py @@ -0,0 +1,25 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import frame_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeFrameObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing frames""" + + def draw(self): + """Draw the frame associated with the scene object as a set of lines: x-axis in red, y-axis in green, z-axis in blue. + + Returns + ------- + list[three.Line] + List of pythreejs objects created. + + """ + + + self._guids = frame_to_threejs(self.geometry) + + return self.guids diff --git a/src/compas_notebook/scene/lineobject.py b/src/compas_notebook/scene/lineobject.py index 4b11224..b69f161 100644 --- a/src/compas_notebook/scene/lineobject.py +++ b/src/compas_notebook/scene/lineobject.py @@ -9,8 +9,8 @@ class ThreeLineObject(ThreeSceneObject, GeometryObject): """Scene object for drawing line.""" - def draw(self): - """Draw the line associated with the scene object. + def draw(self)-> list[three.Line]: + """Draw the frame associated with the scene object. Returns ------- diff --git a/src/compas_notebook/scene/planeobject.py b/src/compas_notebook/scene/planeobject.py new file mode 100644 index 0000000..5ebad6d --- /dev/null +++ b/src/compas_notebook/scene/planeobject.py @@ -0,0 +1,23 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import plane_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreePlaneObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing planes as grids.""" + + def draw(self) -> list[three.Object3D]: + """Draw the plane as a grid. + + Returns + ------- + list[three.Object3D] + List of pythreejs objects created. + + """ + self._guids = plane_to_threejs(self.geometry) + + return self.guids diff --git a/src/compas_notebook/scene/surfaceobject.py b/src/compas_notebook/scene/surfaceobject.py new file mode 100644 index 0000000..b0d1edf --- /dev/null +++ b/src/compas_notebook/scene/surfaceobject.py @@ -0,0 +1,23 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import surface_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeSurfaceObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing surfaces.""" + + def draw(self) -> list[three.Object3D]: + """Draw the surface as mesh with edges. + + Returns + ------- + list[three.Object3D] + List of pythreejs objects created. + + """ + self._guids = surface_to_threejs(self.geometry) + + return self.guids diff --git a/src/compas_notebook/scene/vectorobject.py b/src/compas_notebook/scene/vectorobject.py new file mode 100644 index 0000000..58c56e6 --- /dev/null +++ b/src/compas_notebook/scene/vectorobject.py @@ -0,0 +1,23 @@ +import pythreejs as three +from compas.scene import GeometryObject + +from compas_notebook.conversions.geometry import vector_to_threejs + +from .sceneobject import ThreeSceneObject + + +class ThreeVectorObject(ThreeSceneObject, GeometryObject): + """Scene object for drawing vectors as arrows.""" + + def draw(self) -> list[three.Object3D]: + """Draw the vector as arrow (line + cone head). + + Returns + ------- + list[three.Object3D] + List of pythreejs objects created. + + """ + self._guids = vector_to_threejs(self.geometry) + + return self.guids