Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f5631cd90 | |||
| af50afe3ff | |||
| b6493df77f | |||
| 59d3c8c3ea | |||
| 4e3405f1fb | |||
| 49eabdd712 | |||
| 96a31f0678 | |||
| 91506b0b20 | |||
| b0de9e31b5 | |||
| 2075783134 | |||
| 071f2449c3 |
@@ -30,7 +30,7 @@ class Commit(BaseModel):
|
|||||||
parents: Optional[List[str]]
|
parents: Optional[List[str]]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, createdAt: {self.createdAt} )"
|
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, branchName: {self.branchName}, createdAt: {self.createdAt} )"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class Resource(ResourceBase):
|
|||||||
stream(id: $stream_id) {
|
stream(id: $stream_id) {
|
||||||
commit(id: $commit_id) {
|
commit(id: $commit_id) {
|
||||||
id
|
id
|
||||||
referencedObject
|
|
||||||
message
|
message
|
||||||
|
referencedObject
|
||||||
authorId
|
authorId
|
||||||
authorName
|
authorName
|
||||||
authorAvatar
|
authorAvatar
|
||||||
@@ -79,6 +79,7 @@ class Resource(ResourceBase):
|
|||||||
authorId
|
authorId
|
||||||
authorName
|
authorName
|
||||||
authorAvatar
|
authorAvatar
|
||||||
|
branchName
|
||||||
createdAt
|
createdAt
|
||||||
sourceApplication
|
sourceApplication
|
||||||
totalChildrenCount
|
totalChildrenCount
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ class Base(_RegisteringBase):
|
|||||||
try:
|
try:
|
||||||
attr.__set__(self, value)
|
attr.__set__(self, value)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass # the prop probably doesn't have a setter
|
return # the prop probably doesn't have a setter
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -391,6 +391,15 @@ class Mesh(
|
|||||||
area: float = None
|
area: float = None
|
||||||
volume: float = None
|
volume: float = None
|
||||||
|
|
||||||
|
def transform_to(self, transform: "Transform") -> "Mesh":
|
||||||
|
mesh = Mesh(vertices=transform.apply_to_points_values(self.vertices))
|
||||||
|
for attr in set(self.get_serializable_attributes()) - {"vertices"}:
|
||||||
|
orig_val = getattr(self, attr, None)
|
||||||
|
if orig_val:
|
||||||
|
setattr(mesh, attr, orig_val)
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
||||||
degreeU: int = None
|
degreeU: int = None
|
||||||
|
|||||||
@@ -1,7 +1,28 @@
|
|||||||
|
from typing import List
|
||||||
|
from specklepy.objects.geometry import Point, Vector
|
||||||
from .base import Base
|
from .base import Base
|
||||||
|
|
||||||
OTHER = "Objects.Other."
|
OTHER = "Objects.Other."
|
||||||
|
|
||||||
|
IDENTITY_TRANSFORM = [
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
||||||
name: str = None
|
name: str = None
|
||||||
@@ -10,3 +31,169 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
|||||||
roughness: float = 1
|
roughness: float = 1
|
||||||
diffuse: int = -2894893 # light gray arbg
|
diffuse: int = -2894893 # light gray arbg
|
||||||
emissive: int = -16777216 # black arbg
|
emissive: int = -16777216 # black arbg
|
||||||
|
|
||||||
|
|
||||||
|
class Transform(
|
||||||
|
Base,
|
||||||
|
speckle_type=OTHER + "Transform",
|
||||||
|
serialize_ignore={"translation", "scaling", "is_identity"},
|
||||||
|
):
|
||||||
|
"""The 4x4 transformation matrix
|
||||||
|
|
||||||
|
The 3x3 sub-matrix determines scaling.
|
||||||
|
The 4th column defines translation, where the last value is a divisor (usually equal to 1).
|
||||||
|
"""
|
||||||
|
|
||||||
|
_value: List[float] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> List[float]:
|
||||||
|
"""The transform matrix represented as a flat list of 16 floats"""
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: List[float]) -> None:
|
||||||
|
try:
|
||||||
|
value = [float(x) for x in value]
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise ValueError(
|
||||||
|
f"Could not create a Transform object with the requested value. Input must be a 16 element list of numbers. Value provided: {value}"
|
||||||
|
)
|
||||||
|
if len(value) != 16:
|
||||||
|
raise ValueError(
|
||||||
|
f"Could not create a Transform object: input list should be 16 floats long, but was {len(value)} long"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def translation(self) -> List[float]:
|
||||||
|
"""The final column of the matrix which defines the translation"""
|
||||||
|
return [self._value[i] for i in (3, 7, 11, 15)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scaling(self) -> List[float]:
|
||||||
|
"""The 3x3 scaling sub-matrix"""
|
||||||
|
return [self._value[i] for i in (0, 1, 2, 4, 5, 6, 8, 9, 10)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_identity(self) -> bool:
|
||||||
|
return self.value == IDENTITY_TRANSFORM
|
||||||
|
|
||||||
|
def apply_to_point(self, point: Point) -> Point:
|
||||||
|
"""Transform a single speckle Point
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
point {Point} -- the speckle Point to transform
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Point -- a new transformed point
|
||||||
|
"""
|
||||||
|
coords = self.apply_to_point_value([point.x, point.y, point.z])
|
||||||
|
return Point(x=coords[0], y=coords[1], z=coords[2], units=point.units)
|
||||||
|
|
||||||
|
def apply_to_point_value(self, point_value: List[float]) -> List[float]:
|
||||||
|
"""Transform a list of three floats representing a point
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
point_value {List[float]} -- a list of 3 floats
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[float] -- the list with the transform applied
|
||||||
|
"""
|
||||||
|
transformed = [
|
||||||
|
point_value[0] * self._value[i]
|
||||||
|
+ point_value[1] * self._value[i + 1]
|
||||||
|
+ point_value[2] * self._value[i + 2]
|
||||||
|
+ self._value[i + 3]
|
||||||
|
for i in range(0, 15, 4)
|
||||||
|
]
|
||||||
|
|
||||||
|
return [transformed[i] / transformed[3] for i in range(3)]
|
||||||
|
|
||||||
|
def apply_to_points(self, points: List[Point]) -> List[Point]:
|
||||||
|
"""Transform a list of speckle Points
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
points {List[Point]} -- the list of speckle Points to transform
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Point] -- a new list of transformed points
|
||||||
|
"""
|
||||||
|
return [self.apply_to_point(point) for point in points]
|
||||||
|
|
||||||
|
def apply_to_points_values(self, points_value: List[float]) -> List[float]:
|
||||||
|
"""Transform a list of speckle Points
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
points {List[float]} -- a flat list of floats representing points to transform
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[float] -- a new transformed list
|
||||||
|
"""
|
||||||
|
if len(points_value) % 3 != 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Cannot apply transform as the points list is malformed: expected length to be multiple of 3"
|
||||||
|
)
|
||||||
|
transformed = []
|
||||||
|
for i in range(0, len(points_value), 3):
|
||||||
|
transformed.extend(self.apply_to_point_value(points_value[i : i + 3]))
|
||||||
|
|
||||||
|
return transformed
|
||||||
|
|
||||||
|
def apply_to_vector(self, vector: Vector) -> Vector:
|
||||||
|
"""Transform a single speckle Vector
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
point {Vector} -- the speckle Vector to transform
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Vector -- a new transformed point
|
||||||
|
"""
|
||||||
|
coords = self.apply_to_vector_value([vector.x, vector.y, vector.z])
|
||||||
|
return Vector(x=coords[0], y=coords[1], z=coords[2], units=vector.units)
|
||||||
|
|
||||||
|
def apply_to_vector_value(self, vector_value: List[float]) -> List[float]:
|
||||||
|
"""Transform a list of three floats representing a vector
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
vector_value {List[float]} -- a list of 3 floats
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[float] -- the list with the transform applied
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
vector_value[0] * self._value[i]
|
||||||
|
+ vector_value[1] * self._value[i + 1]
|
||||||
|
+ vector_value[2] * self._value[i + 2]
|
||||||
|
for i in range(0, 15, 4)
|
||||||
|
][:3]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, value: List[float] = None) -> "Transform":
|
||||||
|
"""Returns a Transform object from a list of 16 numbers. If no value is provided, an identity transform will be returned.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
value {List[float]} -- the matrix as a flat list of 16 numbers (defaults to the identity transform)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Transform -- a complete transform object
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
value = IDENTITY_TRANSFORM
|
||||||
|
return cls(value=value)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockDefinition(
|
||||||
|
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
|
||||||
|
):
|
||||||
|
name: str = None
|
||||||
|
basePoint: Point = None
|
||||||
|
geometry: List[Base] = None
|
||||||
|
|
||||||
|
|
||||||
|
class BlockInstance(
|
||||||
|
Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"}
|
||||||
|
):
|
||||||
|
blockDefinition: BlockDefinition = None
|
||||||
|
transform: Transform = None
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
from typing import List
|
||||||
|
import pytest
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.objects.geometry import Mesh, Point, Vector
|
||||||
|
from specklepy.objects.other import (
|
||||||
|
Transform,
|
||||||
|
BlockInstance,
|
||||||
|
BlockDefinition,
|
||||||
|
IDENTITY_TRANSFORM,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def point():
|
||||||
|
return Point(x=1, y=10, z=2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def points():
|
||||||
|
return [Point(x=1 + i, y=10 + i, z=2 + i) for i in range(5)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def point_value():
|
||||||
|
return [1, 10, 2]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def points_values():
|
||||||
|
coords = []
|
||||||
|
for i in range(5):
|
||||||
|
coords.extend([1 + i, 10 + i, 2 + 1])
|
||||||
|
return coords
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def vector():
|
||||||
|
return Vector(x=1, y=10, z=2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def vector_value():
|
||||||
|
return [1, 1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def mesh():
|
||||||
|
return Mesh(
|
||||||
|
vertices=[-7, 5, 1, -8, 4, 0, -7, 3, 0, -6, 4, 0],
|
||||||
|
faces=[1, 1, 2, 3, 0],
|
||||||
|
units="feet",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def transform():
|
||||||
|
"""Translates to [1, 2, 0] and scales z by 0.5"""
|
||||||
|
return Transform.from_list(
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
2.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.5,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_point_transform(point: Point, transform: Transform):
|
||||||
|
new_point = transform.apply_to_point(point)
|
||||||
|
|
||||||
|
assert new_point.x == point.x + 1
|
||||||
|
assert new_point.y == point.y + 2
|
||||||
|
assert new_point.z == point.z * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_points_transform(points: List[Point], transform: Transform):
|
||||||
|
new_points = transform.apply_to_points(points)
|
||||||
|
|
||||||
|
for (i, new_point) in enumerate(new_points):
|
||||||
|
assert new_point.x == points[i].x + 1
|
||||||
|
assert new_point.y == points[i].y + 2
|
||||||
|
assert new_point.z == points[i].z * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_point_value_transform(point_value: List[float], transform: Transform):
|
||||||
|
new_coords = transform.apply_to_point_value(point_value)
|
||||||
|
|
||||||
|
assert new_coords[0] == point_value[0] + 1
|
||||||
|
assert new_coords[1] == point_value[1] + 2
|
||||||
|
assert new_coords[2] == point_value[2] * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_points_values_transform(points_values: List[float], transform: Transform):
|
||||||
|
new_coords = transform.apply_to_points_values(points_values)
|
||||||
|
|
||||||
|
for i in range(0, len(points_values), 3):
|
||||||
|
assert new_coords[i] == points_values[i] + 1
|
||||||
|
assert new_coords[i + 1] == points_values[i + 1] + 2
|
||||||
|
assert new_coords[i + 2] == points_values[i + 2] * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_vector_transform(vector: Vector, transform: Transform):
|
||||||
|
new_vector = transform.apply_to_vector(vector)
|
||||||
|
|
||||||
|
assert new_vector.x == vector.x
|
||||||
|
assert new_vector.y == vector.y
|
||||||
|
assert new_vector.z == vector.z * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_vector_value_transform(vector_value: List[float], transform: Transform):
|
||||||
|
new_coords = transform.apply_to_vector_value(vector_value)
|
||||||
|
|
||||||
|
assert new_coords[0] == vector_value[0]
|
||||||
|
assert new_coords[1] == vector_value[1]
|
||||||
|
assert new_coords[2] == vector_value[2] * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform_fails_with_malformed_value():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Transform.from_list("asdf")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Transform.from_list([7, 8, 9])
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform_serialisation(transform: Transform):
|
||||||
|
serialized = operations.serialize(transform)
|
||||||
|
deserialized = operations.deserialize(serialized)
|
||||||
|
|
||||||
|
assert transform.get_id() == deserialized.get_id()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mesh_transform(mesh: Mesh, transform: Transform):
|
||||||
|
new_mesh = mesh.transform_to(transform)
|
||||||
|
|
||||||
|
assert mesh.vertices != new_mesh.vertices
|
||||||
|
|
||||||
|
new_mesh.vertices = mesh.vertices
|
||||||
|
|
||||||
|
assert mesh.get_id() == new_mesh.get_id()
|
||||||
Reference in New Issue
Block a user