From 95de5cbb30e5ff48b8a51bbcbbe1599645c3853a Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Tue, 6 May 2025 10:12:29 +0100 Subject: [PATCH] Introducing Text class (#419) * add text class and tests * formatting * fix default values * comments * comment * sort imports * import alignments * compare properties, not Base objects * revert irrelevant changes * tests * use correct fixture * fix tests property --- src/specklepy/objects/annotation/__init__.py | 8 ++ src/specklepy/objects/annotation/text.py | 54 ++++++++++ tests/objects/test_text.py | 100 +++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 src/specklepy/objects/annotation/__init__.py create mode 100644 src/specklepy/objects/annotation/text.py create mode 100644 tests/objects/test_text.py diff --git a/src/specklepy/objects/annotation/__init__.py b/src/specklepy/objects/annotation/__init__.py new file mode 100644 index 0000000..d394489 --- /dev/null +++ b/src/specklepy/objects/annotation/__init__.py @@ -0,0 +1,8 @@ +from .text import AlignmentHorizontal, AlignmentVertical, Text + +# re-export them at the geometry package level +__all__ = [ + "Text", + "AlignmentHorizontal", + "AlignmentVertical", +] diff --git a/src/specklepy/objects/annotation/text.py b/src/specklepy/objects/annotation/text.py new file mode 100644 index 0000000..c1a115c --- /dev/null +++ b/src/specklepy/objects/annotation/text.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional + +from specklepy.objects.base import Base +from specklepy.objects.geometry import Plane, Point +from specklepy.objects.interfaces import IHasUnits + + +class AlignmentHorizontal(Enum): + Left = 0 + Center = 1 + Right = 2 + + +class AlignmentVertical(Enum): + Top = 0 + Center = 1 + Bottom = 2 + + +@dataclass(kw_only=True) +class Text(Base, IHasUnits, speckle_type="Objects.Annotation.Text"): + """ + Text class for representation in the viewer. + Units will be 'Units.None' if the text size is defined in pixels. + """ + + value: str # Plain text, without formatting + origin: Point # Relation to the text is defined by AlignmentH and AlignmentV + height: float # Font height in linear units or pixels (if Units.None) + alignmentH: AlignmentHorizontal = field( + default_factory=lambda: AlignmentHorizontal.Left + ) + alignmentV: AlignmentVertical = field(default_factory=lambda: AlignmentVertical.Top) + plane: Optional[Plane] = field( + default_factory=lambda: None + ) # None if the text object orientation follows camera view + maxWidth: Optional[float] = field( + default_factory=lambda: None + ) # Maximum width of the text field. None, if don't split into lines + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"value: {self.value}, " + f"origin: {self.origin}, " + f"height: {self.height}, " + f"alignmentH: {self.alignmentH}, " + f"alignmentV: {self.alignmentV}, " + f"plane: {self.plane}, " + f"maxWidth: {self.maxWidth}, " + f"units: {self.units})" + ) diff --git a/tests/objects/test_text.py b/tests/objects/test_text.py new file mode 100644 index 0000000..0233395 --- /dev/null +++ b/tests/objects/test_text.py @@ -0,0 +1,100 @@ +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.objects.annotation import AlignmentHorizontal, AlignmentVertical, Text +from specklepy.objects.geometry import Plane, Point, Vector +from specklepy.objects.models.units import Units + + +@pytest.fixture +def sample_point() -> Point: + return Point(x=0.0, y=0.0, z=0.0, units=Units.m) + + +@pytest.fixture +def sample_plane(sample_point: Point) -> Plane: + normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) + ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) + return Plane( + origin=sample_point, normal=normal, xdir=xdir, ydir=ydir, units=Units.m + ) + + +@pytest.fixture +def sample_text(sample_point: Point) -> Text: + return Text(value="text", origin=sample_point, height=0.5, units=Units.m) + + +@pytest.fixture +def sample_text_all_properties(sample_point: Point, sample_plane: Plane) -> Text: + return Text( + value="text", + origin=sample_point, + height=0.5, + alignmentH=AlignmentHorizontal.Center, + alignmentV=AlignmentVertical.Center, + plane=sample_plane, + maxWidth=20, + units=Units.m, + ) + + +def test_text_creation_minimal(sample_point: Point): + text_value = "text" + + text_obj = Text(value=text_value, origin=sample_point, height=0.5, units=Units.m) + assert text_obj.value == text_value + assert text_obj.origin == sample_point + assert text_obj.height == 0.5 + assert text_obj.alignmentH == AlignmentHorizontal.Left + assert text_obj.alignmentV == AlignmentVertical.Top + assert text_obj.plane is None + assert text_obj.maxWidth is None + assert text_obj.units == Units.m.value + + +def test_text_creation_extended(sample_point: Point, sample_plane: Plane): + text_value = "text" + max_width = 20 + + text_obj = Text( + value=text_value, + origin=sample_point, + height=0.5, + alignmentH=AlignmentHorizontal.Center, + alignmentV=AlignmentVertical.Center, + plane=sample_plane, + maxWidth=max_width, + units=Units.m, + ) + assert text_obj.value == text_value + assert text_obj.origin == sample_point + assert text_obj.height == 0.5 + assert text_obj.alignmentH == AlignmentHorizontal.Center + assert text_obj.alignmentV == AlignmentVertical.Center + assert text_obj.plane == sample_plane + assert text_obj.maxWidth == max_width + assert text_obj.units == Units.m.value + + +def test_point_serialization(sample_text_all_properties: Text): + serialized = serialize(sample_text_all_properties) + deserialized = deserialize(serialized) + + assert isinstance(deserialized, Text) + assert deserialized.value == sample_text_all_properties.value + assert deserialized.origin.x == sample_text_all_properties.origin.x + assert deserialized.origin.y == sample_text_all_properties.origin.y + assert deserialized.origin.z == sample_text_all_properties.origin.z + assert deserialized.height == sample_text_all_properties.height + assert deserialized.alignmentH == sample_text_all_properties.alignmentH + assert deserialized.alignmentV == sample_text_all_properties.alignmentV + assert deserialized.plane.origin.x == sample_text_all_properties.plane.origin.x + assert deserialized.plane.origin.y == sample_text_all_properties.plane.origin.y + assert deserialized.plane.origin.z == sample_text_all_properties.plane.origin.z + assert deserialized.plane.normal.x == sample_text_all_properties.plane.normal.x + assert deserialized.plane.normal.y == sample_text_all_properties.plane.normal.y + assert deserialized.plane.normal.z == sample_text_all_properties.plane.normal.z + assert deserialized.maxWidth == sample_text_all_properties.maxWidth + assert deserialized.units == sample_text_all_properties.units