From 516eff4d8b4aec984fb7cc3531d4abcd5d1dbc65 Mon Sep 17 00:00:00 2001 From: Dogukan Karatas Date: Thu, 23 Jan 2025 14:30:09 +0100 Subject: [PATCH] helper classes added --- src/specklepy/objects/geometry/arc.py | 31 +++++++++++------ src/specklepy/objects/geometry/line.py | 22 +++++++----- src/specklepy/objects/geometry/mesh.py | 36 +++++++++++--------- src/specklepy/objects/geometry/polyline.py | 9 ++++- src/specklepy/objects/primitive.py | 13 +++++-- src/specklepy/objects/tests/test_arc.py | 8 ++--- src/specklepy/objects/tests/test_line.py | 1 + src/specklepy/objects/tests/test_mesh.py | 22 ++++-------- src/specklepy/objects/tests/test_polyline.py | 2 ++ 9 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/specklepy/objects/geometry/arc.py b/src/specklepy/objects/geometry/arc.py index 46f8cb4..91d324a 100644 --- a/src/specklepy/objects/geometry/arc.py +++ b/src/specklepy/objects/geometry/arc.py @@ -8,8 +8,7 @@ from specklepy.objects.interfaces import ICurve, IHasUnits @dataclass(kw_only=True) -class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc"): - +class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc", serialize_ignore={"radius", "length"}): plane: Plane startPoint: Point midPoint: Point @@ -17,15 +16,27 @@ class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc"): @property def radius(self) -> float: - return self.startPoint.distance_to(self.plane.origin) + return self.__dict__.get('_radius') - @property - def measure(self) -> float: - start_to_mid = self.startPoint.distance_to(self.midPoint) - mid_to_end = self.midPoint.distance_to(self.endPoint) - r = self.radius - return (2 * math.asin(start_to_mid / (2 * r))) + (2 * math.asin(mid_to_end / (2 * r))) + @radius.setter + def radius(self, value: float) -> None: + self.__dict__['_radius'] = value @property def length(self) -> float: - return self.radius * self.measure + return self.__dict__.get('_length') + + @length.setter + def length(self, value: float) -> None: + self.__dict__['_length'] = value + + def calculate_radius(self) -> float: + return self.startPoint.distance_to(self.plane.origin) + + def calculate_length(self) -> float: + start_to_mid = self.startPoint.distance_to(self.midPoint) + mid_to_end = self.midPoint.distance_to(self.endPoint) + r = self.calculate_radius() + angle = (2 * math.asin(start_to_mid / (2 * r))) + \ + (2 * math.asin(mid_to_end / (2 * r))) + return r * angle diff --git a/src/specklepy/objects/geometry/line.py b/src/specklepy/objects/geometry/line.py index 0812d8e..d08ff37 100644 --- a/src/specklepy/objects/geometry/line.py +++ b/src/specklepy/objects/geometry/line.py @@ -6,17 +6,23 @@ from specklepy.objects.interfaces import ICurve, IHasUnits @dataclass(kw_only=True) -class Line(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Line"): - """ - a line defined by two points in 3D space - """ - +class Line( + Base, + IHasUnits, + ICurve, + speckle_type="Objects.Geometry.Line", + serialize_ignore={"length"} +): start: Point end: Point @property def length(self) -> float: - """ - calculate the length of the line using Point's distance_to method - """ + return self.__dict__.get('_length') + + @length.setter + def length(self, value: float) -> None: + self.__dict__['_length'] = value + + def calculate_length(self) -> float: return self.start.distance_to(self.end) diff --git a/src/specklepy/objects/geometry/mesh.py b/src/specklepy/objects/geometry/mesh.py index a7231ff..391aef0 100644 --- a/src/specklepy/objects/geometry/mesh.py +++ b/src/specklepy/objects/geometry/mesh.py @@ -20,6 +20,8 @@ class Mesh( "colors": 62500, "textureCoordinates": 31250, }, + serialize_ignore={"area", "volume", + "vertices_count", "texture_coordinates_count"}, ): """ a 3D mesh consisting of vertices and faces with optional colors and texture coordinates @@ -44,8 +46,7 @@ class Mesh( """ get the number of vertices in the mesh """ - if not self.vertices: - return 0 + if len(self.vertices) % 3 != 0: raise ValueError( f"Invalid vertices list: length ({len( @@ -53,19 +54,6 @@ class Mesh( ) return len(self.vertices) // 3 - @property - def faces_count(self) -> int: - """ - get the number of faces in the mesh - """ - count = 0 - i = 0 - while i < len(self.faces): - n = self.faces[i] - count += 1 - i += n + 1 - return count - @property def texture_coordinates_count(self) -> int: """ @@ -75,6 +63,21 @@ class Mesh( @property def area(self) -> float: + return self.__dict__.get('_area') + + @area.setter + def area(self, value: float) -> None: + self.__dict__['_area'] = value + + @property + def volume(self) -> float: + return self.__dict__.get('_volume') + + @volume.setter + def volume(self, value: float) -> None: + self.__dict__['_volume'] = value + + def calculate_area(self) -> float: """ calculate total surface area of the mesh """ @@ -102,8 +105,7 @@ class Mesh( return total_area - @property - def volume(self) -> float: + def calculate_volume(self) -> float: """ calculate volume of the mesh if it is closed """ diff --git a/src/specklepy/objects/geometry/polyline.py b/src/specklepy/objects/geometry/polyline.py index cec725c..2555077 100644 --- a/src/specklepy/objects/geometry/polyline.py +++ b/src/specklepy/objects/geometry/polyline.py @@ -7,7 +7,7 @@ from specklepy.objects.interfaces import ICurve, IHasUnits @dataclass(kw_only=True) -class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"): +class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline", serialize_ignore={"length"}): """ a polyline curve, defined by a set of vertices. """ @@ -40,6 +40,13 @@ class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline" @property def length(self) -> float: + return self.__dict__.get('_length') + + @length.setter + def length(self, value: float) -> None: + self.__dict__['_length'] = value + + def calculate_length(self) -> float: points = self.get_points() total_length = 0.0 for i in range(len(points) - 1): diff --git a/src/specklepy/objects/primitive.py b/src/specklepy/objects/primitive.py index 89f1ee0..b1da171 100644 --- a/src/specklepy/objects/primitive.py +++ b/src/specklepy/objects/primitive.py @@ -4,7 +4,7 @@ from specklepy.objects.base import Base @dataclass(kw_only=True) -class Interval(Base, speckle_type="Objects.Primitive.Interval"): +class Interval(Base, speckle_type="Objects.Primitive.Interval", serialize_ignore={"length"}): start: float = 0.0 end: float = 0.0 @@ -13,8 +13,17 @@ class Interval(Base, speckle_type="Objects.Primitive.Interval"): @property def length(self) -> float: + return self.__dict__.get('_length') + + @length.setter + def length(self, value: float) -> None: + self.__dict__['_length'] = value + + def calculate_length(self) -> float: return abs(self.end - self.start) @classmethod def unit_interval(cls) -> "Interval": - return cls(start=0, end=1) + interval = cls(start=0, end=1) + interval.length = interval.calculate_length() + return interval diff --git a/src/specklepy/objects/tests/test_arc.py b/src/specklepy/objects/tests/test_arc.py index e60f8a3..ae22d58 100644 --- a/src/specklepy/objects/tests/test_arc.py +++ b/src/specklepy/objects/tests/test_arc.py @@ -71,19 +71,15 @@ def test_arc_domain(sample_arc): def test_arc_radius(sample_arc): + sample_arc.radius = sample_arc.calculate_radius() assert sample_arc.radius == pytest.approx(1.0) def test_arc_length(sample_arc): - # for a semicircle, length should be pi * radius + sample_arc.length = sample_arc.calculate_length() assert sample_arc.length == pytest.approx(math.pi) -def test_arc_measure(sample_arc): - # for a semicircle, measure should be pi radians - assert sample_arc.measure == pytest.approx(math.pi) - - def test_arc_units(sample_points, sample_plane): start, mid, end = sample_points arc = Arc( diff --git a/src/specklepy/objects/tests/test_line.py b/src/specklepy/objects/tests/test_line.py index ff8a42d..c575fbe 100644 --- a/src/specklepy/objects/tests/test_line.py +++ b/src/specklepy/objects/tests/test_line.py @@ -42,6 +42,7 @@ def test_line_domain(sample_line): def test_line_length(sample_line): + sample_line.length = sample_line.calculate_length() assert sample_line.length == 5.0 diff --git a/src/specklepy/objects/tests/test_mesh.py b/src/specklepy/objects/tests/test_mesh.py index 90a6817..369b8dd 100644 --- a/src/specklepy/objects/tests/test_mesh.py +++ b/src/specklepy/objects/tests/test_mesh.py @@ -96,10 +96,6 @@ def test_mesh_vertex_count(sample_mesh): assert sample_mesh.vertices_count == 8 # cube has 8 vertices -def test_mesh_face_count(sample_mesh): - assert sample_mesh.faces_count == 6 # cube has 6 faces - - def test_mesh_texture_coordinates_count(full_mesh): assert full_mesh.texture_coordinates_count == 8 # one UV per vertex @@ -147,24 +143,18 @@ def test_mesh_is_closed(sample_mesh): def test_mesh_area(sample_mesh): + calculated_area = sample_mesh.calculate_area() + sample_mesh.area = calculated_area assert sample_mesh.area == pytest.approx(6.0) def test_mesh_volume(sample_mesh): - points = sample_mesh.get_points() - min_coords = [min(p.x for p in points), min( - p.y for p in points), min(p.z for p in points)] - max_coords = [max(p.x for p in points), max( - p.y for p in points), max(p.z for p in points)] + calculated_volume = sample_mesh.calculate_volume() + sample_mesh.volume = calculated_volume - assert all( - minimum == -0.5 for minimum in min_coords), "Cube minimum should be at -0.5" - assert all( - maximum == 0.5 for maximum in max_coords), "Cube maximum should be at 0.5" - - volume = sample_mesh.volume - assert volume == pytest.approx(1.0), f"Expected volume 1.0, got {volume}" + # Verify volume is set correctly + assert sample_mesh.volume == pytest.approx(1.0) def test_mesh_invalid_vertices(): diff --git a/src/specklepy/objects/tests/test_polyline.py b/src/specklepy/objects/tests/test_polyline.py index 63e3965..224a97c 100644 --- a/src/specklepy/objects/tests/test_polyline.py +++ b/src/specklepy/objects/tests/test_polyline.py @@ -65,11 +65,13 @@ def test_polyline_is_closed_with_tolerance(open_square_coords): def test_polyline_length_open(sample_polyline): + sample_polyline.length = sample_polyline.calculate_length() assert sample_polyline.length == 3.0 def test_polyline_length_closed(closed_square_coords): polyline = Polyline(value=closed_square_coords, units=Units.m) + polyline.length = polyline.calculate_length() assert polyline.length == 4.0