Compare commits

..

1 Commits

Author SHA1 Message Date
izzy lyseggen 9f5631cd90 feat(objects): mesh transform helper 2021-12-13 12:37:53 +00:00
11 changed files with 65 additions and 138 deletions
+4 -16
View File
@@ -1,10 +1,6 @@
import re import re
from warnings import warn from gql.client import SyncClientSession
from specklepy.logging.exceptions import ( from specklepy.logging.exceptions import SpeckleException
GraphQLException,
SpeckleException,
SpeckleWarning,
)
from typing import Dict from typing import Dict
from specklepy.api import resources from specklepy.api import resources
@@ -18,8 +14,9 @@ from specklepy.api.resources import (
subscriptions, subscriptions,
) )
from specklepy.api.models import ServerInfo from specklepy.api.models import ServerInfo
from gql import Client from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport from gql.transport.requests import RequestsHTTPTransport
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.websockets import WebsocketsTransport from gql.transport.websockets import WebsocketsTransport
@@ -80,8 +77,6 @@ class SpeckleClient:
# Check compatibility with the server # Check compatibility with the server
try: try:
serverInfo = self.server.get() serverInfo = self.server.get()
if isinstance(serverInfo, Exception):
raise serverInfo
if not isinstance(serverInfo, ServerInfo): if not isinstance(serverInfo, ServerInfo):
raise Exception("Couldn't get ServerInfo") raise Exception("Couldn't get ServerInfo")
except Exception as ex: except Exception as ex:
@@ -116,13 +111,6 @@ class SpeckleClient:
self._init_resources() self._init_resources()
if isinstance(self.user.get(), GraphQLException):
warn(
SpeckleWarning(
f"Invalid token - could not authenticate Speckle Client for server {self.url}"
)
)
def execute_query(self, query: str) -> Dict: def execute_query(self, query: str) -> Dict:
return self.httpclient.execute(query) return self.httpclient.execute(query)
+17 -17
View File
@@ -118,8 +118,8 @@ class StreamWrapper:
commit_id: str = None commit_id: str = None
object_id: str = None object_id: str = None
branch_name: str = None branch_name: str = None
_client: SpeckleClient = None client: SpeckleClient = None
_account: Account = None account: Account = None
def __repr__(self): def __repr__(self):
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )" return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
@@ -179,15 +179,15 @@ class StreamWrapper:
""" """
Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file) Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file)
""" """
if self._account: if self.account:
return self._account return self.account
self._account = next( self.account = next(
(a for a in get_local_accounts() if self.host in a.serverInfo.url), (a for a in get_local_accounts() if self.host in a.serverInfo.url),
None, None,
) )
return self._account return self.account
def get_client(self, token: str = None) -> SpeckleClient: def get_client(self, token: str = None) -> SpeckleClient:
""" """
@@ -200,22 +200,22 @@ class StreamWrapper:
Returns: Returns:
SpeckleClient -- authenticated with a corresponding local account or the provided token SpeckleClient -- authenticated with a corresponding local account or the provided token
""" """
if self._client and token is None: if self.client and token is None:
return self._client return self.client
if not self._account: if not self.account:
self.get_account() self.get_account()
if not self._client: if not self.client:
self._client = SpeckleClient(host=self.host, use_ssl=self.use_ssl) self.client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
if self._account is None and token is None: if self.account is None and token is None:
warn(f"No local account found for server {self.host}", SpeckleWarning) warn(f"No local account found for server {self.host}", SpeckleWarning)
return self._client return self.client
self._client.authenticate(self._account.token if self._account else token) self.client.authenticate(self.account.token if self.account else token)
return self._client return self.client
def get_transport(self, token: str = None) -> ServerTransport: def get_transport(self, token: str = None) -> ServerTransport:
""" """
@@ -225,6 +225,6 @@ class StreamWrapper:
Returns: Returns:
ServerTransport -- constructed for this stream with a pre-authenticated client ServerTransport -- constructed for this stream with a pre-authenticated client
""" """
if not self._client or not self._client.me: if not self.client or not self.client.me:
self.get_client(token) self.get_client(token)
return ServerTransport(self.stream_id, self._client) return ServerTransport(self.stream_id, self.client)
+1 -6
View File
@@ -252,9 +252,6 @@ class Base(_RegisteringBase):
if t is None: if t is None:
return value return value
if value is None:
return None
if t.__module__ == "typing": if t.__module__ == "typing":
origin = getattr(t, "__origin__") origin = getattr(t, "__origin__")
t = ( t = (
@@ -313,9 +310,7 @@ class Base(_RegisteringBase):
@units.setter @units.setter
def units(self, value: str): def units(self, value: str):
units = get_units_from_string(value) self._units = get_units_from_string(value)
if units:
self._units = units
def get_member_names(self) -> List[str]: def get_member_names(self) -> List[str]:
"""Get all of the property names on this object, dynamic or not""" """Get all of the property names on this object, dynamic or not"""
+9
View File
@@ -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 -8
View File
@@ -1,5 +1,4 @@
from warnings import warn from specklepy.logging.exceptions import SpeckleException
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
UNITS = ["mm", "cm", "m", "in", "ft", "yd", "mi"] UNITS = ["mm", "cm", "m", "in", "ft", "yd", "mi"]
@@ -29,12 +28,6 @@ UNITS_ENCODINGS = {
def get_units_from_string(unit: str): def get_units_from_string(unit: str):
if not isinstance(unit, str):
warn(
f"Invalid units: expected type str but received {type(unit)} ({unit}). Skipping - no units will be set.",
SpeckleWarning,
)
return
unit = str.lower(unit) unit = str.lower(unit)
for name, alternates in UNITS_STRINGS.items(): for name, alternates in UNITS_STRINGS.items():
if unit in alternates: if unit in alternates:
@@ -1,15 +1,11 @@
import ujson import ujson
import hashlib import hashlib
import re import re
from uuid import uuid4 from uuid import uuid4
from warnings import warn
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
from specklepy.objects.base import Base, DataChunk from specklepy.objects.base import Base, DataChunk
from specklepy.logging.exceptions import ( from specklepy.logging.exceptions import SerializationException, SpeckleException
SerializationException,
SpeckleException,
SpeckleWarning,
)
from specklepy.transports.abstract_transport import AbstractTransport from specklepy.transports.abstract_transport import AbstractTransport
import specklepy.objects.geometry import specklepy.objects.geometry
import specklepy.objects.other import specklepy.objects.other
@@ -21,19 +17,6 @@ def hash_obj(obj: Any) -> str:
return hashlib.sha256(ujson.dumps(obj).encode()).hexdigest()[:32] return hashlib.sha256(ujson.dumps(obj).encode()).hexdigest()[:32]
def safe_json_loads(obj: str, obj_id=None) -> Any:
try:
return ujson.loads(obj)
except ValueError as err:
import json
warn(
f"Failed to deserialise object (id: {obj_id}). This is likely a ujson big int error - falling back to json. \nError: {err}",
SpeckleWarning,
)
return json.loads(obj)
class BaseObjectSerializer: class BaseObjectSerializer:
read_transport: AbstractTransport read_transport: AbstractTransport
write_transports: List[AbstractTransport] write_transports: List[AbstractTransport]
@@ -77,19 +60,14 @@ class BaseObjectSerializer:
chunkable = False chunkable = False
detach = False detach = False
# skip props marked to be ignored with "__" or "_" # skip nulls or props marked to be ignored with "__" or "_"
if prop.startswith(("__", "_")): if value is None or prop.startswith(("__", "_")):
continue continue
# don't prepopulate id as this will mess up hashing # don't prepopulate id as this will mess up hashing
if prop == "id": if prop == "id":
continue continue
# allow serialisation of nulls
if value is None:
object_builder[prop] = value
continue
# only bother with chunking and detaching if there is a write transport # only bother with chunking and detaching if there is a write transport
if self.write_transports: if self.write_transports:
dynamic_chunk_match = prop.startswith("@") and re.match( dynamic_chunk_match = prop.startswith("@") and re.match(
@@ -260,7 +238,7 @@ class BaseObjectSerializer:
""" """
if not obj_string: if not obj_string:
return None return None
obj = safe_json_loads(obj_string) obj = ujson.loads(obj_string)
return self.recompose_base(obj=obj) return self.recompose_base(obj=obj)
def recompose_base(self, obj: dict) -> Base: def recompose_base(self, obj: dict) -> Base:
@@ -276,7 +254,7 @@ class BaseObjectSerializer:
if not obj: if not obj:
return return
if isinstance(obj, str): if isinstance(obj, str):
obj = safe_json_loads(obj) obj = ujson.loads(obj)
if "speckle_type" in obj and obj["speckle_type"] == "reference": if "speckle_type" in obj and obj["speckle_type"] == "reference":
obj = self.get_child(obj=obj) obj = self.get_child(obj=obj)
@@ -314,7 +292,7 @@ class BaseObjectSerializer:
raise SpeckleException( raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}" f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
) )
ref_obj = safe_json_loads(ref_obj_str, ref_hash) ref_obj = ujson.loads(ref_obj_str)
base.__setattr__(prop, self.recompose_base(obj=ref_obj)) base.__setattr__(prop, self.recompose_base(obj=ref_obj))
# 3. handle all other cases (base objects, lists, and dicts) # 3. handle all other cases (base objects, lists, and dicts)
@@ -372,5 +350,4 @@ class BaseObjectSerializer:
raise SpeckleException( raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}" f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
) )
return ujson.loads(ref_obj_str)
return safe_json_loads(ref_obj_str, ref_hash)
+4 -9
View File
@@ -92,15 +92,10 @@ class BatchSender(object):
def _bg_send_batch(self, session, batch): def _bg_send_batch(self, session, batch):
object_ids = [obj[0] for obj in batch] object_ids = [obj[0] for obj in batch]
try: server_has_object = session.post(
server_has_object = session.post( url=f"{self.server_url}/api/diff/{self.stream_id}",
url=f"{self.server_url}/api/diff/{self.stream_id}", data={"objects": json.dumps(object_ids)},
data={"objects": json.dumps(object_ids)}, ).json()
).json()
except Exception as ex:
raise SpeckleException(
f"Invalid credentials - cannot send objects to server {self.server_url}"
) from ex
new_object_ids = [x for x in object_ids if not server_has_object[x]] new_object_ids = [x for x in object_ids if not server_has_object[x]]
new_object_ids = set(new_object_ids) new_object_ids = set(new_object_ids)
-12
View File
@@ -77,18 +77,6 @@ def test_speckle_type_cannot_be_set(base: Base) -> None:
assert base.speckle_type == "Base" assert base.speckle_type == "Base"
def test_setting_units():
b = Base(units="foot")
assert b.units == "ft"
with pytest.raises(SpeckleException):
b.units = "big"
b.units = None # invalid args are skipped
b.units = 7
assert b.units == "ft"
def test_base_of_custom_speckle_type() -> None: def test_base_of_custom_speckle_type() -> None:
b1 = Base.of_type("BirdHouse", name="Tweety's Crib") b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
assert b1.speckle_type == "BirdHouse" assert b1.speckle_type == "BirdHouse"
-31
View File
@@ -1,31 +0,0 @@
import pytest
from specklepy.api import operations
from specklepy.api.client import SpeckleClient
from specklepy.objects.base import Base
from specklepy.transports.server import ServerTransport
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
def test_invalid_authentication():
client = SpeckleClient()
with pytest.warns(SpeckleWarning):
client.authenticate("fake token")
def test_invalid_send():
client = SpeckleClient()
client.me = {"token": "fake token"}
transport = ServerTransport("3073b96e86", client)
with pytest.raises(SpeckleException):
operations.send(Base(), [transport])
def test_invalid_receive():
client = SpeckleClient()
client.me = {"token": "fake token"}
transport = ServerTransport("fake stream", client)
with pytest.raises(SpeckleException):
operations.receive("fake object", transport)
-6
View File
@@ -89,9 +89,3 @@ class TestSerialization:
deserialised = operations.deserialize(untyped) deserialised = operations.deserialize(untyped)
assert deserialised == {"foo": "bar"} assert deserialised == {"foo": "bar"}
def test_big_int(self):
big_int = '{"big": ' + str(2 ** 64) + "}"
deserialised = operations.deserialize(big_int)
assert deserialised == {"big": 2 ** 64}
+20 -1
View File
@@ -1,7 +1,7 @@
from typing import List from typing import List
import pytest import pytest
from specklepy.api import operations from specklepy.api import operations
from specklepy.objects.geometry import Point, Vector from specklepy.objects.geometry import Mesh, Point, Vector
from specklepy.objects.other import ( from specklepy.objects.other import (
Transform, Transform,
BlockInstance, BlockInstance,
@@ -43,6 +43,15 @@ def vector_value():
return [1, 1, 2] 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() @pytest.fixture()
def transform(): def transform():
"""Translates to [1, 2, 0] and scales z by 0.5""" """Translates to [1, 2, 0] and scales z by 0.5"""
@@ -130,3 +139,13 @@ def test_transform_serialisation(transform: Transform):
deserialized = operations.deserialize(serialized) deserialized = operations.deserialize(serialized)
assert transform.get_id() == deserialized.get_id() 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()