Compare commits
5 Commits
3.0.8
...
formatting
| Author | SHA1 | Date | |
|---|---|---|---|
| 89f4c75cc9 | |||
| d20f7ea82a | |||
| 2e44a891e8 | |||
| edce76491f | |||
| ecbd0eab09 |
@@ -1,8 +1,8 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import ICurve, IHasUnits
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits, IHasVolume
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
@@ -93,3 +93,180 @@ class Line(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Line"):
|
||||
start = Point(x=start_x, y=start_y, z=start_z, units=units)
|
||||
end = Point(x=end_x, y=end_y, z=end_z, units=units)
|
||||
return cls(start=start, end=end, units=units)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"):
|
||||
"""
|
||||
a polyline curve, defined by a set of vertices.
|
||||
"""
|
||||
|
||||
value: List[float] = field(default_factory=list)
|
||||
closed: bool = False
|
||||
domain: Interval = field(default_factory=Interval.unit_interval)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
points = self.get_points()
|
||||
total_length = 0.0
|
||||
for i in range(len(points) - 1):
|
||||
total_length += points[i].distance_to(points[i + 1])
|
||||
if self.closed and points:
|
||||
total_length += points[-1].distance_to(points[0])
|
||||
return total_length
|
||||
|
||||
@property
|
||||
def _domain(self) -> Interval:
|
||||
"""
|
||||
internal domain property for ICurve interface
|
||||
"""
|
||||
return self.domain
|
||||
|
||||
def get_points(self) -> List[Point]:
|
||||
"""
|
||||
converts the raw coordinate list into Point objects
|
||||
"""
|
||||
if len(self.value) % 3 != 0:
|
||||
raise ValueError(
|
||||
"Polyline value list is malformed: expected length to be multiple of 3"
|
||||
)
|
||||
|
||||
points = []
|
||||
for i in range(0, len(self.value), 3):
|
||||
points.append(
|
||||
Point(
|
||||
x=self.value[i],
|
||||
y=self.value[i + 1],
|
||||
z=self.value[i + 2],
|
||||
units=self.units,
|
||||
)
|
||||
)
|
||||
return points
|
||||
|
||||
def to_list(self) -> List[float]:
|
||||
"""
|
||||
returns the values of this Polyline as a list of numbers
|
||||
"""
|
||||
result = []
|
||||
result.append(len(self.value) + 6) # total list length
|
||||
# type indicator for polyline ?? not sure about this
|
||||
result.append("Objects.Geometry.Polyline")
|
||||
result.append(1 if self.closed else 0)
|
||||
result.append(self.domain.start)
|
||||
result.append(self.domain.end)
|
||||
result.append(len(self.value))
|
||||
result.extend(self.value)
|
||||
result.append(Units.get_encoding_from_unit(self.units))
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, coords: List[float], units: str | Units) -> "Polyline":
|
||||
"""
|
||||
creates a new Polyline based on a list of coordinates
|
||||
"""
|
||||
point_count = int(coords[5])
|
||||
return cls(
|
||||
closed=(int(coords[2]) == 1),
|
||||
domain=Interval(start=coords[3], end=coords[4]),
|
||||
value=coords[6 : 6 + point_count],
|
||||
units=units,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Mesh(
|
||||
Base,
|
||||
IHasArea,
|
||||
IHasVolume,
|
||||
IHasUnits,
|
||||
speckle_type="Objects.Geometry.Mesh",
|
||||
detachable={"vertices", "faces", "colors", "textureCoordinates"},
|
||||
chunkable={
|
||||
"vertices": 31250,
|
||||
"faces": 62500,
|
||||
"colors": 62500,
|
||||
"textureCoordinates": 31250,
|
||||
},
|
||||
):
|
||||
vertices: List[float] = field(default_factory=list)
|
||||
faces: List[int] = field(default_factory=list)
|
||||
colors: List[int] = field(default_factory=list)
|
||||
textureCoordinates: List[float] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def vertices_count(self) -> int:
|
||||
return len(self.vertices) // 3
|
||||
|
||||
@property
|
||||
def texture_coordinates_count(self) -> int:
|
||||
return len(self.textureCoordinates) // 2
|
||||
|
||||
def get_point(self, index: int) -> Point:
|
||||
index *= 3
|
||||
return Point(
|
||||
x=self.vertices[index],
|
||||
y=self.vertices[index + 1],
|
||||
z=self.vertices[index + 2],
|
||||
units=self.units,
|
||||
)
|
||||
|
||||
def get_points(self) -> List[Point]:
|
||||
if len(self.vertices) % 3 != 0:
|
||||
raise ValueError(
|
||||
"Mesh vertices list is malformed: expected length to be multiple of 3"
|
||||
)
|
||||
|
||||
points = []
|
||||
for i in range(0, len(self.vertices), 3):
|
||||
points.append(
|
||||
Point(
|
||||
x=self.vertices[i],
|
||||
y=self.vertices[i + 1],
|
||||
z=self.vertices[i + 2],
|
||||
units=self.units,
|
||||
)
|
||||
)
|
||||
return points
|
||||
|
||||
def get_texture_coordinate(self, index: int) -> Tuple[float, float]:
|
||||
index *= 2
|
||||
return (self.textureCoordinates[index], self.textureCoordinates[index + 1])
|
||||
|
||||
def align_vertices_with_texcoords_by_index(self) -> None:
|
||||
if not self.textureCoordinates:
|
||||
return
|
||||
|
||||
if self.texture_coordinates_count == self.vertices_count:
|
||||
return
|
||||
|
||||
faces_unique = []
|
||||
vertices_unique = []
|
||||
has_colors = len(self.colors) > 0
|
||||
colors_unique = [] if has_colors else None
|
||||
|
||||
n_index = 0
|
||||
while n_index < len(self.faces):
|
||||
n = self.faces[n_index]
|
||||
if n < 3:
|
||||
n += 3
|
||||
|
||||
if n_index + n >= len(self.faces):
|
||||
break
|
||||
|
||||
faces_unique.append(n)
|
||||
for i in range(1, n + 1):
|
||||
vert_index = self.faces[n_index + i]
|
||||
new_vert_index = len(vertices_unique) // 3
|
||||
|
||||
point = self.get_point(vert_index)
|
||||
vertices_unique.extend([point.x, point.y, point.z])
|
||||
|
||||
if colors_unique is not None:
|
||||
colors_unique.append(self.colors[vert_index])
|
||||
faces_unique.append(new_vert_index)
|
||||
|
||||
n_index += n + 1
|
||||
|
||||
self.vertices = vertices_unique
|
||||
self.colors = colors_unique if colors_unique is not None else self.colors
|
||||
self.faces = faces_unique
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Generic, List, TypeVar
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleInvalidUnitException
|
||||
from specklepy.objects.base import Base
|
||||
@@ -37,22 +37,15 @@ class IDisplayValue(Generic[T], metaclass=ABCMeta):
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasUnits(metaclass=ABCMeta):
|
||||
"""Interface for objects that have units."""
|
||||
|
||||
units: str | Units
|
||||
_units: str = field(repr=False, init=False)
|
||||
|
||||
@property
|
||||
def units(self) -> str:
|
||||
"""Get the units of the object"""
|
||||
return self._units
|
||||
|
||||
@units.setter
|
||||
def units(self, value: str | Units):
|
||||
"""
|
||||
While this property accepts any string value, geometry expects units
|
||||
to be specific strings (see Units enum)
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
self._units = value
|
||||
elif isinstance(value, Units):
|
||||
@@ -63,6 +56,38 @@ class IHasUnits(metaclass=ABCMeta):
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasArea(metaclass=ABCMeta):
|
||||
area: float
|
||||
_area: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self._area
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
raise ValueError(f"Area must be a number, got {type(value)}")
|
||||
self._area = float(value)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasVolume(metaclass=ABCMeta):
|
||||
volume: float
|
||||
_volume: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return self._volume
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
raise ValueError(f"Volume must be a number, got {type(value)}")
|
||||
self._volume = float(value)
|
||||
|
||||
|
||||
# data object interfaces
|
||||
class IProperties(metaclass=ABCMeta):
|
||||
@property
|
||||
@@ -71,7 +96,7 @@ class IProperties(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
|
||||
class IDataObject(IProperties, IDisplayValue[list[Base]], metaclass=ABCMeta):
|
||||
class IDataObject(IProperties, IDisplayValue[List[Base]], metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
@@ -19,9 +20,9 @@ class Interval(Base, speckle_type="Objects.Primitive.Interval"):
|
||||
def unit_interval(cls) -> "Interval":
|
||||
return cls(start=0, end=1)
|
||||
|
||||
def to_list(self) -> list[float]:
|
||||
def to_list(self) -> List[float]:
|
||||
return [self.start, self.end]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: list[float]) -> "Interval":
|
||||
def from_list(cls, args: List[float]) -> "Interval":
|
||||
return cls(start=args[0], end=args[1])
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ColorProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.ColorProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str] = field(default_factory=list)
|
||||
value: int
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class GroupProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.GroupProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str] = field(default_factory=list)
|
||||
name: str = field(default="Unnamed Group")
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class InstanceProxy(
|
||||
Base,
|
||||
IHasUnits,
|
||||
speckle_type="Models.Proxies.InstanceProxy",
|
||||
):
|
||||
definition_id: str
|
||||
transform: List[float] = field(default_factory=list)
|
||||
max_depth: int = 50
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class InstanceDefinitionProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.InstanceDefinitionProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str] = field(default_factory=list)
|
||||
max_depth: int = 50
|
||||
name: str = field(default="Unnamed Instance")
|
||||
@@ -5,28 +5,15 @@ from specklepy.objects.geometry import Line, Point
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
# test points
|
||||
# points
|
||||
p1 = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
p2 = Point(x=4.0, y=6.0, z=8.0, units=Units.m, applicationId="asdf")
|
||||
|
||||
p3 = Point(units="m", x=0, y=0, z=0)
|
||||
|
||||
print("Distance between points:", p1.distance_to(p2))
|
||||
|
||||
ser_p1 = serialize(p1)
|
||||
p1_again = deserialize(ser_p1)
|
||||
|
||||
print("\nOriginal point:")
|
||||
debug(p1)
|
||||
print("\nSerialized point:")
|
||||
debug(ser_p1)
|
||||
print("\nDeserialized point:")
|
||||
debug(p1_again)
|
||||
|
||||
# # test Line
|
||||
# test Line
|
||||
line = Line(start=p1, end=p2, units=Units.m, domain=Interval(start=0.0, end=1.0))
|
||||
|
||||
# print(f"\nLine length: {line.length}")
|
||||
print(f"\nLine length: {line.length}")
|
||||
|
||||
ser_line = serialize(line)
|
||||
line_again = deserialize(ser_line)
|
||||
@@ -0,0 +1,182 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Mesh
|
||||
|
||||
# create a speckle cube mesh (but more colorful)
|
||||
vertices = [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
]
|
||||
|
||||
# define faces (triangles)
|
||||
faces = [
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
3,
|
||||
4,
|
||||
6,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
4,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
7,
|
||||
3,
|
||||
3,
|
||||
1,
|
||||
5,
|
||||
6,
|
||||
3,
|
||||
1,
|
||||
6,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
3,
|
||||
6,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
3,
|
||||
0,
|
||||
5,
|
||||
4,
|
||||
]
|
||||
|
||||
# create colors (one per vertex)
|
||||
colors = [
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
]
|
||||
|
||||
texture_coordinates = [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
]
|
||||
|
||||
# create the mesh
|
||||
cube_mesh = Mesh(
|
||||
vertices=vertices,
|
||||
faces=faces,
|
||||
colors=colors,
|
||||
textureCoordinates=texture_coordinates,
|
||||
units="mm",
|
||||
area=0.0,
|
||||
volume=0.0,
|
||||
)
|
||||
|
||||
print("\nMesh Details:")
|
||||
print(f"Number of vertices: {cube_mesh.vertices_count}")
|
||||
print(f"Number of texture coordinates: {cube_mesh.texture_coordinates_count}")
|
||||
|
||||
print("\nSome vertex points:")
|
||||
for i in range(4):
|
||||
point = cube_mesh.get_point(i)
|
||||
print(f"Vertex {i}: ({point.x}, {point.y}, {point.z})")
|
||||
|
||||
print("\nSome texture coordinates:")
|
||||
for i in range(4):
|
||||
u, v = cube_mesh.get_texture_coordinate(i)
|
||||
print(f"Texture coordinate {i}: ({u}, {v})")
|
||||
|
||||
print("\nTesting serialization...")
|
||||
ser_mesh = serialize(cube_mesh)
|
||||
mesh_again = deserialize(ser_mesh)
|
||||
|
||||
print("\nOriginal mesh:")
|
||||
debug(cube_mesh)
|
||||
print("\nDeserialized mesh:")
|
||||
debug(mesh_again)
|
||||
|
||||
print("\nTesting vertex-texture coordinate alignment...")
|
||||
cube_mesh.align_vertices_with_texcoords_by_index()
|
||||
print("Alignment complete.")
|
||||
|
||||
print(f"Vertices count after alignment: {cube_mesh.vertices_count}")
|
||||
print(
|
||||
f"Texture coordinates count after alignment: {cube_mesh.texture_coordinates_count}"
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Point
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
# test points
|
||||
p1 = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
p2 = Point(x=4.0, y=6.0, z=8.0, units=Units.m, applicationId="asdf")
|
||||
|
||||
print("Distance between points:", p1.distance_to(p2))
|
||||
|
||||
ser_p1 = serialize(p1)
|
||||
p1_again = deserialize(ser_p1)
|
||||
|
||||
print("\nOriginal point:")
|
||||
debug(p1)
|
||||
print("\nSerialized point:")
|
||||
debug(ser_p1)
|
||||
print("\nDeserialized point:")
|
||||
debug(p1_again)
|
||||
@@ -0,0 +1,51 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Polyline
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
# create points for first polyline - not closed, in meters
|
||||
points1_coords = [1.0, 1.0, 0.0, 2.0, 1.0, 0.0, 2.0, 2.0, 0.0, 1.0, 2.0, 0.0]
|
||||
|
||||
# Create points for second polyline - closed, in ft
|
||||
points2_coords = [0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 3.0, 3.0, 0.0, 0.0, 3.0, 0.0]
|
||||
|
||||
# create polylines
|
||||
polyline1 = Polyline(
|
||||
value=points1_coords,
|
||||
closed=False,
|
||||
units=Units.m,
|
||||
domain=Interval(start=0.0, end=1.0),
|
||||
)
|
||||
|
||||
polyline2 = Polyline(
|
||||
value=points2_coords,
|
||||
closed=True,
|
||||
units=Units.feet,
|
||||
domain=Interval(start=0.0, end=1.0),
|
||||
applicationId="polyllllineeee",
|
||||
)
|
||||
|
||||
print("Polyline 1 length (meters):", polyline1.length)
|
||||
print("Polyline 2 length (feet):", polyline2.length)
|
||||
|
||||
ser_poly1 = serialize(polyline1)
|
||||
poly1_again = deserialize(ser_poly1)
|
||||
|
||||
print("\nOriginal polyline 1:")
|
||||
debug(polyline1)
|
||||
print("\nSerialized polyline 1:")
|
||||
debug(ser_poly1)
|
||||
print("\nDeserialized polyline 1:")
|
||||
debug(poly1_again)
|
||||
|
||||
ser_poly2 = serialize(polyline2)
|
||||
poly2_again = deserialize(ser_poly2)
|
||||
|
||||
print("\nOriginal polyline 2:")
|
||||
debug(polyline2)
|
||||
print("\nSerialized polyline 2:")
|
||||
debug(ser_poly2)
|
||||
print("\nDeserialized polyline 2:")
|
||||
debug(poly2_again)
|
||||
Reference in New Issue
Block a user