From 6f00f66acf3df0e2b4a7738dd9ad52dd6b2e62b4 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Tue, 16 Jan 2024 20:33:20 +0000 Subject: [PATCH] types meshes to test --- __init__.py | 79 +- speckle/converter/geometry/mesh.py | 665 +++++++++------ speckle/converter/geometry/point.py | 149 +--- speckle/converter/geometry/polygon.py | 356 ++++---- speckle/converter/geometry/polyline.py | 94 +-- speckle/converter/geometry/transform.py | 38 +- speckle/converter/geometry/utils.py | 99 ++- speckle/converter/layers/__init__.py | 65 +- speckle/converter/layers/feature.py | 23 +- speckle/converter/layers/layer_conversions.py | 7 +- speckle/converter/layers/symbology.py | 766 ++++++++++++------ speckle/converter/layers/utils.py | 82 +- speckle/converter/utils.py | 52 ++ tests/unit/geometry_tests/conftest.py | 17 +- tests/unit/geometry_tests/test_mesh.py | 23 + tests/unit/geometry_tests/test_point.py | 22 +- tests/unit/geometry_tests/test_utils.py | 30 +- 17 files changed, 1508 insertions(+), 1059 deletions(-) create mode 100644 speckle/converter/utils.py diff --git a/__init__.py b/__init__.py index ca8c19c6..52563722 100644 --- a/__init__.py +++ b/__init__.py @@ -1,54 +1,53 @@ # -*- coding: utf-8 -*- -try: - import os - import sys - path = os.path.dirname(os.path.abspath(__file__)) - if path not in sys.path: - sys.path.insert(0, path) +import os +import sys - from plugin_utils.installer import ensure_dependencies, startDebugger - from speckle.utils.panel_logging import logger +path = os.path.dirname(os.path.abspath(__file__)) +if path not in sys.path: + sys.path.insert(0, path) - from qgis.core import Qgis +from plugin_utils.installer import ensure_dependencies, startDebugger +from speckle.utils.panel_logging import logger - # noinspection PyPep8Naming - def classFactory(iface): # pylint: disable=invalid-name - """Load SpeckleQGIS class from file SpeckleQGIS. +from qgis.core import Qgis - :param iface: A QGIS interface instance. - :type iface: QgsInterface - """ - # Set qgisInterface to enable logToUser notifications - logger.qgisInterface = iface - iface.pluginToolBar().setVisible(True) +# noinspection PyPep8Naming +def classFactory(iface): # pylint: disable=invalid-name + """Load SpeckleQGIS class from file SpeckleQGIS. - # Ensure dependencies are installed in the machine - startDebugger() - ensure_dependencies("QGIS") + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ - from speckle_qgis import SpeckleQGIS - from specklepy.logging import metrics + # Set qgisInterface to enable logToUser notifications + logger.qgisInterface = iface + iface.pluginToolBar().setVisible(True) - version = ( - Qgis.QGIS_VERSION.encode("iso-8859-1", errors="ignore") - .decode("utf-8") - .split(".")[0] - ) - metrics.set_host_app("QGIS", f"QGIS{version}") - return SpeckleQGIS(iface) + # Ensure dependencies are installed in the machine + startDebugger() + ensure_dependencies("QGIS") - class EmptyClass: - # https://docs.qgis.org/3.28/en/docs/pyqgis_developer_cookbook/plugins/plugins.html#mainplugin-py - def __init__(self, iface): - pass + from speckle_qgis import SpeckleQGIS + from specklepy.logging import metrics - def initGui(self): - pass + version = ( + Qgis.QGIS_VERSION.encode("iso-8859-1", errors="ignore") + .decode("utf-8") + .split(".")[0] + ) + metrics.set_host_app("QGIS", f"QGIS{version}") + return SpeckleQGIS(iface) - def unload(self): - pass -except ModuleNotFoundError: - pass +class EmptyClass: + # https://docs.qgis.org/3.28/en/docs/pyqgis_developer_cookbook/plugins/plugins.html#mainplugin-py + def __init__(self, iface): + pass + + def initGui(self): + pass + + def unload(self): + pass diff --git a/speckle/converter/geometry/mesh.py b/speckle/converter/geometry/mesh.py index 7c65d559..6850adb0 100644 --- a/speckle/converter/geometry/mesh.py +++ b/speckle/converter/geometry/mesh.py @@ -1,181 +1,150 @@ import inspect -import math -import time from typing import List from specklepy.objects.geometry import Mesh, Point from specklepy.objects.other import RenderMaterial import shapefile -from shapefile import TRIANGLE_STRIP, TRIANGLE_FAN, OUTER_RING -from speckle.converter.geometry.point import applyTransformMatrix, pointToNative, pointToNativeWithoutTransforms, scalePointToNative, transformSpecklePt -from speckle.converter.geometry.utils import fix_orientation, projectToPolygon, triangulatePolygon +from shapefile import OUTER_RING +from speckle.converter.geometry.point import ( + pointToNative, +) +from speckle.converter.geometry.utils import ( + apply_pt_transform_matrix, + fix_orientation, + projectToPolygon, + triangulatePolygon, + transform_speckle_pt_on_receive, +) from speckle.converter.layers.symbology import featureColorfromNativeRenderer -from speckle.converter.layers.utils import get_scale_factor, get_scale_factor_to_meter, getDisplayValueList -#from speckle.utils.panel_logging import logger +from speckle.converter.layers.utils import ( + getDisplayValueList, +) +from speckle.converter.utils import ( + get_scale_factor, +) + from speckle.utils.panel_logging import logToUser -from qgis.core import ( - QgsMultiPolygon, QgsPolygon, QgsLineString, QgsFeature, QgsVectorLayer +try: + from qgis.core import ( + QgsMultiPolygon, + QgsPolygon, + QgsLineString, + QgsFeature, + QgsVectorLayer, ) -#from panda3d.core import Triangulator +except ModuleNotFoundError: + pass -def meshToNative(meshes: List[Mesh], dataStorage) -> QgsMultiPolygon: - try: - multiPolygon = QgsMultiPolygon() - for mesh in meshes: - parts_list, types_list = deconstructSpeckleMesh(mesh, dataStorage) - for part in parts_list: - polygon = QgsPolygon() - units = dataStorage.currentUnits - if not isinstance(units, str): - units = "m" - pts = [Point(x=pt[0], y=pt[1], z=pt[2], units=units) for pt in part] - pts.append(pts[0]) - boundary = QgsLineString([pointToNative(pt, dataStorage) for pt in pts]) - polygon.setExteriorRing(boundary) - if polygon is not None: - multiPolygon.addGeometry(polygon) - return multiPolygon - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return None - -def writeMeshToShp(meshes: List, path: str, dataStorage): - """Converts a Speckle Mesh to QgsGeometry""" +def deconstructSpeckleMesh(mesh: Mesh, dataStorage): try: - #print("06___________________writeMeshToShp") + scale = get_scale_factor(mesh.units, dataStorage) + parts_list = [] + types_list = [] - try: - w = shapefile.Writer(path) - except Exception as e: - logToUser(e) - return - #print(w) - w.field('speckle_id', 'C') + count = 0 # sequence of vertex (not of flat coord list) + for f in mesh.faces: # real number of loops will be at least 3 times less + try: + vertices = mesh.faces[count] + if mesh.faces[count] == 0: + vertices = 3 + if mesh.faces[count] == 1: + vertices = 4 - shapes = [] - #print(meshes) - for i, geom in enumerate(meshes): + face = [] + for i in range(vertices): + index_faces = count + 1 + i + index_vertices = mesh.faces[index_faces] * 3 + + pt = Point( + x=mesh.vertices[index_vertices], + y=mesh.vertices[index_vertices + 1], + z=mesh.vertices[index_vertices + 2], + units="m", + ) + pt = apply_pt_transform_matrix(pt, dataStorage) + face.append([scale * pt.x, scale * pt.y, scale * pt.z]) - meshList: List = getDisplayValueList(geom) - #print(geom) - w = fill_multi_mesh_parts(w, meshList, geom.id, dataStorage) + parts_list.append(face) + types_list.append(OUTER_RING) + count += vertices + 1 + except: + break # when out of range - r''' - if geom.speckle_type =='Objects.Geometry.Mesh' and isinstance(geom, Mesh): - mesh = geom - w = fill_mesh_parts(w, mesh, geom.id, dataStorage) - else: - try: - if geom.displayValue and isinstance(geom.displayValue, Mesh): - mesh = geom.displayValue - w = fill_mesh_parts(w, mesh, geom.id, dataStorage) - elif geom.displayValue and isinstance(geom.displayValue, List): - print("__ this should be a common case") - w = fill_multi_mesh_parts(w, geom.displayValue, geom.id, dataStorage) - except: - try: - if geom["@displayValue"] and isinstance(geom["@displayValue"], Mesh): - mesh = geom["@displayValue"] - w = fill_mesh_parts(w, mesh, geom.id, dataStorage) - elif geom["@displayValue"] and isinstance(geom["@displayValue"], List): - w = fill_multi_mesh_parts(w, geom["@displayValue"], geom.id, dataStorage) - except: - try: - if geom.displayMesh and isinstance(geom.displayMesh, Mesh): - mesh = geom.displayMesh - w = fill_mesh_parts(w, mesh, geom.id, dataStorage) - elif geom.displayMesh and isinstance(geom.displayMesh, List): - w = fill_multi_mesh_parts(w, geom.displayMesh, geom.id, dataStorage) - except: pass - ''' - w.close() - #print("06-end___________________Mesh to Native") - return path + return parts_list, types_list except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return None + logToUser(e, level=2, func=inspect.stack()[0][3]) + return [], [] -def fill_multi_mesh_parts(w: shapefile.Writer, meshes: List[Mesh], geom_id: str, dataStorage): - - #print("07___________________fill_multi_mesh_parts") + +def fill_multi_mesh_parts( + w: shapefile.Writer, meshes: List[Mesh], geom_id: str, dataStorage +): try: parts_list = [] types_list = [] for mesh in meshes: - if not isinstance(mesh, Mesh): continue + if not isinstance(mesh, Mesh): + continue try: - #print(f"Fill multi-mesh parts # {geom_id}") - parts_list_x, types_list_x = deconstructSpeckleMesh(mesh, dataStorage) + parts_list_x, types_list_x = deconstructSpeckleMesh(mesh, dataStorage) for i, face in enumerate(parts_list_x): for k, p in enumerate(face): - pt = Point(x = p[0], y= p[1], z = p[2], units = mesh.units) - pt = transformSpecklePt(pt, dataStorage) + pt = Point(x=p[0], y=p[1], z=p[2], units=mesh.units) + pt = transform_speckle_pt_on_receive(pt, dataStorage) parts_list_x[i][k] = [pt.x, pt.y, pt.z] parts_list.extend(parts_list_x) types_list.extend(types_list_x) - except Exception as e: print(e) - - w.multipatch(parts_list, partTypes=types_list ) # one type for each part + except Exception as e: + print(e) + + w.multipatch(parts_list, partTypes=types_list) # one type for each part w.record(geom_id) - #print("07-end___________________fill_multi_mesh_parts") return w except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return None + +def writeMeshToShp(meshes: List, path: str, dataStorage): + """Converts a Speckle Mesh to QgsGeometry""" + try: + try: + w = shapefile.Writer(path) + except Exception as e: + logToUser(e) + return + + w.field("speckle_id", "C") + + for _, geom in enumerate(meshes): + meshList: List = getDisplayValueList(geom) + w = fill_multi_mesh_parts(w, meshList, geom.id, dataStorage) + w.close() + return path + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None + + def fill_mesh_parts(w: shapefile.Writer, mesh: Mesh, geom_id: str, dataStorage): - try: - parts_list, types_list = deconstructSpeckleMesh(mesh, dataStorage) + parts_list, types_list = deconstructSpeckleMesh(mesh, dataStorage) for i, face in enumerate(parts_list): for k, p in enumerate(face): - pt = Point(x = p[0], y= p[1], z = p[2], units = mesh.units) - pt = transformSpecklePt(pt, dataStorage) + pt = Point(x=p[0], y=p[1], z=p[2], units=mesh.units) + pt = transform_speckle_pt_on_receive(pt, dataStorage) parts_list[i][k] = [pt.x, pt.y, pt.z] - w.multipatch(parts_list, partTypes=types_list ) # one type for each part + w.multipatch(parts_list, partTypes=types_list) # one type for each part w.record(geom_id) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return w -def deconstructSpeckleMesh(mesh: Mesh, dataStorage): - - #print("deconstructSpeckleMesh") - try: - scale = get_scale_factor(mesh.units, dataStorage) - parts_list = [] - types_list = [] - - count = 0 # sequence of vertex (not of flat coord list) - for f in mesh.faces: # real number of loops will be at least 3 times less - try: - vertices = mesh.faces[count] - if mesh.faces[count] == 0: vertices = 3 - if mesh.faces[count] == 1: vertices = 4 - - face = [] - for i in range(vertices): - index_faces = count + 1 + i - index_vertices = mesh.faces[index_faces]*3 - - pt = Point(x = mesh.vertices[index_vertices], y = mesh.vertices[index_vertices+1], z = mesh.vertices[index_vertices+2], units = "m") - pt = applyTransformMatrix(pt, dataStorage) - face.append([ scale * pt.x, scale * pt.y, scale * pt.z ]) - - parts_list.append(face) - types_list.append(OUTER_RING) - count += vertices + 1 - except: break # when out of range - - #print("end-deconstructSpeckleMesh") - return parts_list, types_list - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return [],[] def constructMeshFromRaster(vertices, faces, colors, dataStorage): try: @@ -183,9 +152,10 @@ def constructMeshFromRaster(vertices, faces, colors, dataStorage): mesh.units = "m" return mesh except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return None + def constructMesh(vertices, faces, colors, dataStorage): try: if vertices is None or faces is None or colors is None: @@ -195,13 +165,23 @@ def constructMesh(vertices, faces, colors, dataStorage): material = RenderMaterial() material.diffuse = colors[0] material.name = str(colors[0]) - mesh.renderMaterial = material + mesh.renderMaterial = material return mesh except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return None -def meshPartsFromPolygon(polyBorder: List[Point], voidsAsPts: List[List[Point]], existing_vert: int, feature: QgsFeature, feature_geom, layer: QgsVectorLayer, height, dataStorage): + +def meshPartsFromPolygon( + polyBorder: List[Point], + voidsAsPts: List[List[Point]], + existing_vert: int, + feature: "QgsFeature", + feature_geom, + layer: "QgsVectorLayer", + height, + dataStorage, +): try: faces = [] faces_cap = [] @@ -216,22 +196,27 @@ def meshPartsFromPolygon(polyBorder: List[Point], voidsAsPts: List[List[Point]], coef = 1 maxPoints = 5000 - if len(polyBorder) >= maxPoints: coef = int(len(polyBorder)/maxPoints) + if len(polyBorder) >= maxPoints: + coef = int(len(polyBorder) / maxPoints) col = featureColorfromNativeRenderer(feature, layer) - - if len(voidsAsPts) == 0: # only if there is a mesh with no voids and large amount of points + + if ( + len(voidsAsPts) == 0 + ): # only if there is a mesh with no voids and large amount of points # floor: need positive - clockwise (looking down); cap need negative (counter-clockwise) - polyBorder = fix_orientation(polyBorder, True, coef) # clockwise + polyBorder = fix_orientation(polyBorder, True, coef) # clockwise - if height is None: polyBorder.reverse() # when no extrusions: face up, counter-clockwise + if height is None: + polyBorder.reverse() # when no extrusions: face up, counter-clockwise - for k, ptt in enumerate(polyBorder): #pointList: - pt = polyBorder[k*coef] + for k, ptt in enumerate(polyBorder): # pointList: + pt = polyBorder[k * coef] if k < maxPoints: vertices.extend([pt.x, pt.y, pt.z]) total_vertices += 1 - else: break + else: + break ran = range(0, total_vertices) faces = [total_vertices] @@ -240,88 +225,150 @@ def meshPartsFromPolygon(polyBorder: List[Point], voidsAsPts: List[List[Point]], # a cap ################################## if height is not None: - ran = range(total_vertices, 2*total_vertices) - faces.append(total_vertices) + ran = range(total_vertices, 2 * total_vertices) + faces.append(total_vertices) faces2 = [i + existing_vert for i in ran] faces2.reverse() - faces.extend(faces2) + faces.extend(faces2) vertices_copy = vertices.copy() count = 0 for item in vertices_copy: - try: - vertices.extend([vertices_copy[count], vertices_copy[count+1], vertices_copy[count+2] + height]) - count += 3 - except: break + try: + vertices.extend( + [ + vertices_copy[count], + vertices_copy[count + 1], + vertices_copy[count + 2] + height, + ] + ) + count += 3 + except: + break total_vertices *= 2 - + ###################################### add extrusions universal_z_value = polyBorder[0].z for k, pt in enumerate(polyBorder): polyBorder2 = [] try: - vertices_side.extend([polyBorder[k].x, polyBorder[k].y, universal_z_value, - polyBorder[k].x, polyBorder[k].y, height + universal_z_value, - polyBorder[k+1].x, polyBorder[k+1].y, height + universal_z_value, - polyBorder[k+1].x, polyBorder[k+1].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 - - except: - vertices_side.extend([polyBorder[k].x, polyBorder[k].y, universal_z_value, - polyBorder[k].x, polyBorder[k].y, height + universal_z_value, - polyBorder[0].x, polyBorder[0].y, height + universal_z_value, - polyBorder[0].x, polyBorder[0].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 + vertices_side.extend( + [ + polyBorder[k].x, + polyBorder[k].y, + universal_z_value, + polyBorder[k].x, + polyBorder[k].y, + height + universal_z_value, + polyBorder[k + 1].x, + polyBorder[k + 1].y, + height + universal_z_value, + polyBorder[k + 1].x, + polyBorder[k + 1].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 + + except: + vertices_side.extend( + [ + polyBorder[k].x, + polyBorder[k].y, + universal_z_value, + polyBorder[k].x, + polyBorder[k].y, + height + universal_z_value, + polyBorder[0].x, + polyBorder[0].y, + height + universal_z_value, + polyBorder[0].x, + polyBorder[0].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 break - + ran = range(0, total_vertices) - colors = [col for i in ran] # apply same color for all vertices - return total_vertices, vertices + vertices_side, faces + faces_side, colors, iterations + colors = [col for i in ran] # apply same color for all vertices + return ( + total_vertices, + vertices + vertices_side, + faces + faces_side, + colors, + iterations, + ) ###################################### else: - colors = [col for i in ran] # apply same color for all vertices + colors = [col for i in ran] # apply same color for all vertices return total_vertices, vertices, faces, colors, iterations - else: # if there are voids: face should be clockwise + else: # if there are voids: face should be clockwise # if its a large polygon with voids to be triangualted, lower the coef even more: - #maxPoints = 100 - if len(polyBorder) >= maxPoints: coef = int(len(polyBorder)/maxPoints) + # maxPoints = 100 + if len(polyBorder) >= maxPoints: + coef = int(len(polyBorder) / maxPoints) + + universal_z_value = polyBorder[0].z - universal_z_value = polyBorder[0].z - # get points from original geometry ################################# - triangulated_geom, vertices3d_original, iterations = triangulatePolygon(feature_geom, dataStorage) - + triangulated_geom, vertices3d_original, iterations = triangulatePolygon( + feature_geom, dataStorage + ) + # temporary solution, as the list of points is not the same anymore: if triangulated_geom is None or vertices3d_original is None: - return None, None, None, None, None - + return None, None, None, None, None + vertices3d = [] - for v in triangulated_geom['vertices']: - vertices3d.append(v+[0.0]) + for v in triangulated_geom["vertices"]: + vertices3d.append(v + [0.0]) # get substitute value for missing z-val existing_3d_pts = [] - for i, p in enumerate(vertices3d): - if p[2] is not None and str(p[2])!="" and str(p[2]).lower()!="nan" and p[2] != universal_z_value: # only from boundary - p[2] += universal_z_value + for i, p in enumerate(vertices3d): + if ( + p[2] is not None + and str(p[2]) != "" + and str(p[2]).lower() != "nan" + and p[2] != universal_z_value + ): # only from boundary + p[2] += universal_z_value existing_3d_pts.append(p) - if len(existing_3d_pts) == 3: break + if len(existing_3d_pts) == 3: + break pt_list = [] - for i, p in enumerate(triangulated_geom['vertices']): + for i, p in enumerate(triangulated_geom["vertices"]): z_val = vertices3d[i][2] - if z_val is None or str(z_val)!="" or str(z_val).lower()!="nan": + if z_val is None or str(z_val) != "" or str(z_val).lower() != "nan": # #z_val = any_existing_z - if len(existing_3d_pts)>=3: + if len(existing_3d_pts) >= 3: z_val = projectToPolygon(vertices3d[i], existing_3d_pts) - else: + else: z_val = universal_z_value - pt_list.append( [p[0], p[1], z_val ] ) + pt_list.append([p[0], p[1], z_val]) + + triangle_list = [trg for trg in triangulated_geom["triangles"]] - triangle_list = [ trg for trg in triangulated_geom['triangles']] - try: for trg in triangle_list: a = trg[0] @@ -331,86 +378,212 @@ def meshPartsFromPolygon(polyBorder: List[Point], voidsAsPts: List[List[Point]], total_vertices += 3 # all faces are counter-clockwise now if height is None: - faces.extend([3, total_vertices-3, total_vertices-2, total_vertices-1]) - else: # if extruding - faces.extend([3, total_vertices-1, total_vertices-2, total_vertices-3]) # reverse to clock-wise (facing down) - + faces.extend( + [ + 3, + total_vertices - 3, + total_vertices - 2, + total_vertices - 1, + ] + ) + else: # if extruding + faces.extend( + [ + 3, + total_vertices - 1, + total_vertices - 2, + total_vertices - 3, + ] + ) # reverse to clock-wise (facing down) + ran = range(0, total_vertices) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - + logToUser(e, level=2, func=inspect.stack()[0][3]) + # a cap ################################## if height is not None: - # change the pt list to height - pt_list = [ [p[0], p[1], universal_z_value + height] for p in triangulated_geom['vertices']] - - for trg in triangle_list: + # change the pt list to height + pt_list = [ + [p[0], p[1], universal_z_value + height] + for p in triangulated_geom["vertices"] + ] + + for trg in triangle_list: a = trg[0] b = trg[1] c = trg[2] # all faces are counter-clockwise now vertices.extend(pt_list[a] + pt_list[b] + pt_list[c]) total_vertices += 3 - faces_cap.extend([3, total_vertices-3, total_vertices-2, total_vertices-1]) + faces_cap.extend( + [3, total_vertices - 3, total_vertices - 2, total_vertices - 1] + ) ###################################### add extrusions - polyBorder = fix_orientation(polyBorder, True, coef) # clockwise + polyBorder = fix_orientation(polyBorder, True, coef) # clockwise universal_z_value = polyBorder[0].z for k, pt in enumerate(polyBorder): try: - vertices_side.extend([polyBorder[k].x, polyBorder[k].y, universal_z_value, - polyBorder[k].x, polyBorder[k].y, height + universal_z_value, - polyBorder[k+1].x, polyBorder[k+1].y, height + universal_z_value, - polyBorder[k+1].x, polyBorder[k+1].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 - - except: - vertices_side.extend([polyBorder[k].x, polyBorder[k].y, universal_z_value, - polyBorder[k].x, polyBorder[k].y, height + universal_z_value, - polyBorder[0].x, polyBorder[0].y, height + universal_z_value, - polyBorder[0].x, polyBorder[0].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 + vertices_side.extend( + [ + polyBorder[k].x, + polyBorder[k].y, + universal_z_value, + polyBorder[k].x, + polyBorder[k].y, + height + universal_z_value, + polyBorder[k + 1].x, + polyBorder[k + 1].y, + height + universal_z_value, + polyBorder[k + 1].x, + polyBorder[k + 1].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 + + except: + vertices_side.extend( + [ + polyBorder[k].x, + polyBorder[k].y, + universal_z_value, + polyBorder[k].x, + polyBorder[k].y, + height + universal_z_value, + polyBorder[0].x, + polyBorder[0].y, + height + universal_z_value, + polyBorder[0].x, + polyBorder[0].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 break - for v in voidsAsPts: # already at the correct hight (even projected) - v = fix_orientation(v, False, coef) # counter-clockwise + for v in voidsAsPts: # already at the correct hight (even projected) + v = fix_orientation(v, False, coef) # counter-clockwise for k, pt in enumerate(v): - void =[] + void = [] try: - vertices_side.extend([v[k].x, v[k].y, universal_z_value, - v[k].x, v[k].y, height + universal_z_value, - v[k+1].x, v[k+1].y, height + universal_z_value, - v[k+1].x, v[k+1].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 - + vertices_side.extend( + [ + v[k].x, + v[k].y, + universal_z_value, + v[k].x, + v[k].y, + height + universal_z_value, + v[k + 1].x, + v[k + 1].y, + height + universal_z_value, + v[k + 1].x, + v[k + 1].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 + except: - vertices_side.extend([v[k].x, v[k].y, universal_z_value, - v[k].x, v[k].y, height + universal_z_value, - v[0].x, v[0].y, height + universal_z_value, - v[0].x, v[0].y, universal_z_value]) - faces_side.extend([4, total_vertices, total_vertices+1,total_vertices+2,total_vertices+3]) - total_vertices +=4 + vertices_side.extend( + [ + v[k].x, + v[k].y, + universal_z_value, + v[k].x, + v[k].y, + height + universal_z_value, + v[0].x, + v[0].y, + height + universal_z_value, + v[0].x, + v[0].y, + universal_z_value, + ] + ) + faces_side.extend( + [ + 4, + total_vertices, + total_vertices + 1, + total_vertices + 2, + total_vertices + 3, + ] + ) + total_vertices += 4 break - + ############################################ ran = range(0, total_vertices) - colors = [col for i in ran] # apply same color for all vertices - return total_vertices, vertices + vertices_cap + vertices_side, faces + faces_cap + faces_side, colors, iterations - - else: + colors = [col for i in ran] # apply same color for all vertices + return ( + total_vertices, + vertices + vertices_cap + vertices_side, + faces + faces_cap + faces_side, + colors, + iterations, + ) + + else: ran = range(0, total_vertices) - colors = [col for i in ran] # apply same color for all vertices - + colors = [col for i in ran] # apply same color for all vertices + return total_vertices, vertices, faces, colors, iterations - - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return None, None, None, None, None \ No newline at end of file + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None, None, None, None, None + + +def meshToNative(meshes: List[Mesh], dataStorage) -> "QgsMultiPolygon": + try: + multiPolygon = QgsMultiPolygon() + for mesh in meshes: + parts_list, types_list = deconstructSpeckleMesh(mesh, dataStorage) + for part in parts_list: + polygon = QgsPolygon() + units = dataStorage.currentUnits + if not isinstance(units, str): + units = "m" + pts = [Point(x=pt[0], y=pt[1], z=pt[2], units=units) for pt in part] + pts.append(pts[0]) + boundary = QgsLineString([pointToNative(pt, dataStorage) for pt in pts]) + polygon.setExteriorRing(boundary) + + if polygon is not None: + multiPolygon.addGeometry(polygon) + return multiPolygon + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None diff --git a/speckle/converter/geometry/point.py b/speckle/converter/geometry/point.py index c39c59e6..5bc64bd5 100644 --- a/speckle/converter/geometry/point.py +++ b/speckle/converter/geometry/point.py @@ -1,33 +1,44 @@ import inspect import math +from typing import Union import numpy as np -from qgis.core import QgsPoint, QgsPointXY, QgsFeature, QgsVectorLayer +try: + from qgis.core import QgsPoint, QgsPointXY, QgsFeature, QgsVectorLayer +except ModuleNotFoundError: + pass from specklepy.objects.geometry import Point -from speckle.converter.layers.utils import get_scale_factor +from speckle.converter.geometry.utils import ( + apply_pt_offsets_rotation_on_send, + transform_speckle_pt_on_receive, + apply_pt_transform_matrix, +) +from speckle.converter.utils import get_scale_factor from speckle.converter.layers.symbology import featureColorfromNativeRenderer from speckle.utils.panel_logging import logToUser def pointToSpeckle( - pt: QgsPoint or QgsPointXY, feature: QgsFeature, layer: QgsVectorLayer, dataStorage + pt: "QgsPoint" or "QgsPointXY", feature: "QgsFeature", layer: "QgsVectorLayer", dataStorage ): """Converts a QgsPoint to Speckle""" try: if isinstance(pt, QgsPointXY): pt = QgsPoint(pt) - # when unset, z() returns "nan" + x = pt.x() y = pt.y() - z = 0 if math.isnan(pt.z()) else pt.z() + z = 0 if math.isnan(pt.z()) else pt.z() # when unset, z() returns "nan" specklePoint = Point() specklePoint.x = x specklePoint.y = y specklePoint.z = z specklePoint.units = "m" - specklePoint.x, specklePoint.y = applyOffsetsRotation(x, y, dataStorage) + specklePoint.x, specklePoint.y = apply_pt_offsets_rotation_on_send( + x, y, dataStorage + ) col = featureColorfromNativeRenderer(feature, layer) specklePoint["displayStyle"] = {} @@ -38,101 +49,12 @@ def pointToSpeckle( return None -def transformSpecklePt(pt_original: Point, dataStorage) -> Point: # on Receive - offset_x = dataStorage.crs_offset_x - offset_y = dataStorage.crs_offset_y - rotation = dataStorage.crs_rotation - - pt = Point( - x=pt_original.x, y=pt_original.y, z=pt_original.z, units=pt_original.units - ) - - gisLayer = None - try: - gisLayer = dataStorage.latestHostApp.lower().endswith("gis") - # print(gisLayer) - applyTransforms = False if (gisLayer and gisLayer is True) else True - except Exception as e: - print(e) - applyTransforms = True - - # for non-GIS layers - if applyTransforms is True: - # print("transform non-gis layer") - if ( - rotation is not None - and isinstance(rotation, float) - and -360 < rotation < 360 - ): - a = rotation * math.pi / 180 - x2 = pt.x - y2 = pt.y - - # if a > 0: # turn counterclockwise on receive - x2 = pt.x * math.cos(a) - pt.y * math.sin(a) - y2 = pt.x * math.sin(a) + pt.y * math.cos(a) - # elif a < 0: # turn clockwise on receive - # x2 = pt.x*math.cos(a) + pt.y*math.sin(a) - # y2 = -1*pt.x*math.sin(a) + pt.y*math.cos(a) - - pt.x = x2 - pt.y = y2 - if ( - offset_x is not None - and isinstance(offset_x, float) - and offset_y is not None - and isinstance(offset_y, float) - ): - pt.x += offset_x - pt.y += offset_y - - # for GIS layers - if gisLayer is True: - # print("transform GIS layer") - try: - offset_x = dataStorage.current_layer_crs_offset_x - offset_y = dataStorage.current_layer_crs_offset_y - rotation = dataStorage.current_layer_crs_rotation - - if ( - rotation is not None - and isinstance(rotation, float) - and -360 < rotation < 360 - ): - a = rotation * math.pi / 180 - x2 = pt.x - y2 = pt.y - - # if a > 0: # turn counterclockwise on receive - x2 = pt.x * math.cos(a) - pt.y * math.sin(a) - y2 = pt.x * math.sin(a) + pt.y * math.cos(a) - # elif a < 0: # turn clockwise on receive - # x2 = pt.x*math.cos(a) + pt.y*math.sin(a) - # y2 = -1*pt.x*math.sin(a) + pt.y*math.cos(a) - - pt.x = x2 - pt.y = y2 - if ( - offset_x is not None - and isinstance(offset_x, float) - and offset_y is not None - and isinstance(offset_y, float) - ): - pt.x += offset_x - pt.y += offset_y - except Exception as e: - print(e) - - # print(pt) - return pt - - -def pointToNativeWithoutTransforms(pt: Point, dataStorage) -> QgsPoint: +def pointToNative(pt: Point, dataStorage) -> "QgsPoint": """Converts a Speckle Point to QgsPoint""" try: - pt = scalePointToNative(pt, pt.units, dataStorage) - pt = applyTransformMatrix(pt, dataStorage) - newPt = pt # transformSpecklePt(pt, dataStorage) + new_pt = scalePointToNative(pt, pt.units, dataStorage) + new_pt = apply_pt_transform_matrix(new_pt, dataStorage) + newPt = transform_speckle_pt_on_receive(new_pt, dataStorage) return QgsPoint(newPt.x, newPt.y, newPt.z) except Exception as e: @@ -140,36 +62,21 @@ def pointToNativeWithoutTransforms(pt: Point, dataStorage) -> QgsPoint: return None -def pointToNative(pt: Point, dataStorage) -> QgsPoint: +def pointToNativeWithoutTransforms(pt: Point, dataStorage) -> Union["QgsPoint", None]: """Converts a Speckle Point to QgsPoint""" try: - pt = scalePointToNative(pt, pt.units, dataStorage) - pt = applyTransformMatrix(pt, dataStorage) - newPt = transformSpecklePt(pt, dataStorage) + new_pt = scalePointToNative(pt, pt.units, dataStorage) + new_pt = apply_pt_transform_matrix(new_pt, dataStorage) - return QgsPoint(newPt.x, newPt.y, newPt.z) + return QgsPoint(new_pt.x, new_pt.y, new_pt.z) except Exception as e: logToUser(e, level=2, func=inspect.stack()[0][3]) return None -def applyTransformMatrix(pt: Point, dataStorage): - try: - if dataStorage.matrix is not None: - # print(f"__PT: {(pt.x, pt.y, pt.z)}") - # print(dataStorage.matrix) - b = np.matrix([pt.x, pt.y, pt.z, 1]) - res = b * dataStorage.matrix - # print(res) - x, y, z = res.item(0), res.item(1), res.item(2) - # print(f"__PT: {(x, y, z)}") - return Point(x=x, y=y, z=z, units=pt.units) - except Exception as e: - print(e) - return pt - - -def scalePointToNative(point: Point, units: str, dataStorage) -> Point: +def scalePointToNative( + point: Point, units: Union[str, None], dataStorage +) -> Union[Point, None]: """Scale point coordinates to meters""" try: scaleFactor = get_scale_factor(units, dataStorage) # to meters diff --git a/speckle/converter/geometry/polygon.py b/speckle/converter/geometry/polygon.py index 6270ae9b..d17b2832 100644 --- a/speckle/converter/geometry/polygon.py +++ b/speckle/converter/geometry/polygon.py @@ -2,11 +2,18 @@ import inspect import random -from qgis.core import Qgis, QgsGeometry, QgsPolygon, QgsPointXY, QgsPoint, QgsFeature, QgsVectorLayer, QgsCoordinateReferenceSystem +from qgis.core import ( + QgsGeometry, + QgsPolygon, + QgsPointXY, + QgsFeature, + QgsVectorLayer, + QgsCoordinateReferenceSystem, +) -from typing import List, Sequence +from typing import List, Union -from specklepy.objects.geometry import Point, Line, Polyline, Circle, Arc, Polycurve, Mesh +from specklepy.objects.geometry import Point, Line, Polyline, Arc, Polycurve from specklepy.objects import Base from specklepy.objects.GIS.geometry import GisPolygonGeometry @@ -14,30 +21,35 @@ from speckle.converter.geometry.polyline import ( polylineFromVerticesToSpeckle, polylineToNative, - unknownLineToSpeckle + unknownLineToSpeckle, +) +from speckle.converter.geometry.utils import ( + projectToPolygon, + speckleBoundaryToSpecklePts, ) -from speckle.converter.geometry.utils import * -from speckle.converter.layers.symbology import featureColorfromNativeRenderer -from speckle.converter.layers.utils import get_raster_stats, getArrayIndicesFromXY, getElevationLayer, getRasterArrays, isAppliedLayerTransformByKeywords, moveVertically, reprojectPt -#from speckle.utils.panel_logging import logger -import math -from osgeo import gdal -import numpy as np - -#from panda3d.core import Triangulator - -from PyQt5.QtGui import QColor +# from speckle.converter.geometry.utils import * +from speckle.converter.layers.utils import ( + get_raster_stats, + getArrayIndicesFromXY, + getElevationLayer, + getRasterArrays, + moveVertically, + reprojectPt, +) from speckle.utils.panel_logging import logToUser -def polygonToSpeckleMesh(geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLayer, dataStorage): +import numpy as np - polygon = GisPolygonGeometry(units = "m") - #print(dataStorage) - try: +def polygonToSpeckleMesh( + geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLayer, dataStorage +): + polygon = GisPolygonGeometry(units="m") + # print(dataStorage) + try: vertices = [] - faces = [] + faces = [] colors = [] existing_vert = 0 boundary = None @@ -46,110 +58,163 @@ def polygonToSpeckleMesh(geom: QgsGeometry, feature: QgsFeature, layer: QgsVecto polyBorder = speckleBoundaryToSpecklePts(boundary, dataStorage) voids = [] voidsAsPts = [] - + for v in voidsNative: pts_fixed = [] v_speckle = unknownLineToSpeckle(v, True, feature, layer, dataStorage) pts = speckleBoundaryToSpecklePts(v_speckle, dataStorage) - - plane_pts = [ [polyBorder[0].x, polyBorder[0].y, polyBorder[0].z], - [polyBorder[1].x, polyBorder[1].y, polyBorder[1].z], - [polyBorder[2].x, polyBorder[2].y, polyBorder[2].z] ] + + plane_pts = [ + [polyBorder[0].x, polyBorder[0].y, polyBorder[0].z], + [polyBorder[1].x, polyBorder[1].y, polyBorder[1].z], + [polyBorder[2].x, polyBorder[2].y, polyBorder[2].z], + ] for pt in pts: z_val = pt.z - #print(str(z_val)) + # print(str(z_val)) # project the pts on the plane point = [pt.x, pt.y, 0] - z_val = projectToPolygon( point , plane_pts) - pts_fixed.append(Point(units = "m", x = pt.x, y = pt.y, z = z_val)) + z_val = projectToPolygon(point, plane_pts) + pts_fixed.append(Point(units="m", x=pt.x, y=pt.y, z=z_val)) - voids.append(polylineFromVerticesToSpeckle(pts_fixed, True, feature, layer, dataStorage)) - voidsAsPts.append(pts_fixed) + voids.append( + polylineFromVerticesToSpeckle( + pts_fixed, True, feature, layer, dataStorage + ) + ) + voidsAsPts.append(pts_fixed) + + ( + total_vert, + vertices_x, + faces_x, + colors_x, + iterations, + ) = meshPartsFromPolygon( + polyBorder, + voidsAsPts, + existing_vert, + feature, + p, + layer, + None, + dataStorage, + ) - total_vert, vertices_x, faces_x, colors_x, iterations = meshPartsFromPolygon(polyBorder, voidsAsPts, existing_vert, feature, p, layer, None, dataStorage) - if total_vert is None: - return None + return None existing_vert += total_vert vertices.extend(vertices_x) faces.extend(faces_x) colors.extend(colors_x) mesh = constructMesh(vertices, faces, colors, dataStorage) - if mesh is not None: - polygon.displayValue = [ mesh ] + if mesh is not None: + polygon.displayValue = [mesh] polygon.boundary = None - polygon.voids = None - else: + polygon.voids = None + else: polygon.boundary = boundary - polygon.voids = voids - logToUser("Mesh creation from Polygon failed. Boundaries will be used as displayValue", level = 1, func = inspect.stack()[0][3]) - return polygon - + polygon.voids = voids + logToUser( + "Mesh creation from Polygon failed. Boundaries will be used as displayValue", + level=1, + func=inspect.stack()[0][3], + ) + return polygon + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return None -def getZaxisTranslation(layer, boundaryPts, dataStorage): - #### check if elevation is applied and layer exists: - elevationLayer = getElevationLayer(dataStorage) - polygonWkt = dataStorage.project.crs().toWkt() - polygonProj = QgsCoordinateReferenceSystem.fromWkt(polygonWkt).toProj().replace(" +type=crs","") - - translationValue = None - if elevationLayer is not None: + +def getZaxisTranslation(layer, boundaryPts, dataStorage): + #### check if elevation is applied and layer exists: + elevationLayer = getElevationLayer(dataStorage) + polygonWkt = dataStorage.project.crs().toWkt() + polygonProj = ( + QgsCoordinateReferenceSystem.fromWkt(polygonWkt) + .toProj() + .replace(" +type=crs", "") + ) + + translationValue = None + if elevationLayer is not None: all_arrays, all_mins, all_maxs, all_na = getRasterArrays(elevationLayer) settings_elevation_layer = get_raster_stats(elevationLayer) - xres, yres, originX, originY, sizeX, sizeY, rasterWkt, rasterProj = settings_elevation_layer + ( + xres, + yres, + originX, + originY, + sizeX, + sizeY, + rasterWkt, + rasterProj, + ) = settings_elevation_layer allElevations = [] - for pt in boundaryPts: - posX, posY = reprojectPt(pt.x(), pt.y(), polygonWkt, polygonProj, rasterWkt, rasterProj) - index1, index2, remainder1, remainder2 = getArrayIndicesFromXY( settings_elevation_layer, posX, posY ) - #print("___finding elevation__") - #print(posX) - #print(index1) - - if index1 is None: continue - else: - h = all_arrays[0][index1][index2] - allElevations.append(h) + for pt in boundaryPts: + posX, posY = reprojectPt( + pt.x(), pt.y(), polygonWkt, polygonProj, rasterWkt, rasterProj + ) + index1, index2, remainder1, remainder2 = getArrayIndicesFromXY( + settings_elevation_layer, posX, posY + ) + # print("___finding elevation__") + # print(posX) + # print(index1) + + if index1 is None: + continue + else: + h = all_arrays[0][index1][index2] + allElevations.append(h) if len(allElevations) == 0: - translationValue = None + translationValue = None else: - if np.isnan(boundaryPts[0].z()) : # for flat polygons with z=0 + if np.isnan(boundaryPts[0].z()): # for flat polygons with z=0 translationValue = min(allElevations) - else: - translationValue = min(allElevations) - boundaryPts[0].z() - + else: + translationValue = min(allElevations) - boundaryPts[0].z() + return translationValue + def isFlat(ptList): flat = True universal_z_value = ptList[0].z() for i, pt in enumerate(ptList): if isinstance(pt, QgsPointXY): - break - elif np.isnan(pt.z()): - break + break + elif np.isnan(pt.z()): + break elif pt.z() != universal_z_value: flat = False break - return flat + return flat -def polygonToSpeckle(geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLayer, height, projectZval, dataStorage): + +def polygonToSpeckle( + geom: QgsGeometry, + feature: QgsFeature, + layer: QgsVectorLayer, + height, + projectZval, + dataStorage, +): """Converts a QgsPolygon to Speckle""" - #print("Polygon To Speckle") - #print(dataStorage) - polygon = GisPolygonGeometry(units = "m") + # print("Polygon To Speckle") + # print(dataStorage) + polygon = GisPolygonGeometry(units="m") iterations = 0 try: boundary, voidsNative = getPolyBoundaryVoids(geom, feature, layer, dataStorage) - if projectZval is not None: + if projectZval is not None: boundary = moveVertically(boundary, projectZval) - + polyBorder = speckleBoundaryToSpecklePts(boundary, dataStorage) voids = [] voidsAsPts = [] @@ -158,118 +223,129 @@ def polygonToSpeckle(geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLay pts_fixed = [] v_speckle = unknownLineToSpeckle(v, True, feature, layer, dataStorage) pts = speckleBoundaryToSpecklePts(v_speckle, dataStorage) - - plane_pts = [ [polyBorder[0].x, polyBorder[0].y, polyBorder[0].z], - [polyBorder[1].x, polyBorder[1].y, polyBorder[1].z], - [polyBorder[2].x, polyBorder[2].y, polyBorder[2].z] ] + + plane_pts = [ + [polyBorder[0].x, polyBorder[0].y, polyBorder[0].z], + [polyBorder[1].x, polyBorder[1].y, polyBorder[1].z], + [polyBorder[2].x, polyBorder[2].y, polyBorder[2].z], + ] for pt in pts: z_val = pt.z - #print(str(z_val)) + # print(str(z_val)) # project the pts on the plane point = [pt.x, pt.y, 0] - z_val = projectToPolygon( point , plane_pts) - pts_fixed.append(Point(units = "m", x = pt.x, y = pt.y, z = z_val)) + z_val = projectToPolygon(point, plane_pts) + pts_fixed.append(Point(units="m", x=pt.x, y=pt.y, z=z_val)) - voids.append(polylineFromVerticesToSpeckle(pts_fixed, True, feature, layer, dataStorage)) - voidsAsPts.append(pts_fixed) + voids.append( + polylineFromVerticesToSpeckle( + pts_fixed, True, feature, layer, dataStorage + ) + ) + voidsAsPts.append(pts_fixed) polygon.boundary = boundary polygon.voids = voids - iterations, vertices, faces, colors, iterations = meshPartsFromPolygon(polyBorder, voidsAsPts, 0, feature, geom, layer, height, dataStorage) + iterations, vertices, faces, colors, iterations = meshPartsFromPolygon( + polyBorder, voidsAsPts, 0, feature, geom, layer, height, dataStorage + ) mesh = constructMesh(vertices, faces, colors, dataStorage) - if mesh is not None: - polygon.displayValue = [ mesh ] - #polygon["baseGeometry"] = mesh + if mesh is not None: + polygon.displayValue = [mesh] + # polygon["baseGeometry"] = mesh # https://latest.speckle.dev/streams/85bc4f61c6/commits/2a5d23a277 - # https://speckle.community/t/revit-add-new-parameters/5170/2 - else: - polygon.displayValue = [ ] - #polygon.boundary = boundary - #polygon.voids = voids - logToUser("Mesh creation from Polygon failed. Boundaries will be used as displayValue", level = 1, func = inspect.stack()[0][3]) - + # https://speckle.community/t/revit-add-new-parameters/5170/2 + else: + polygon.displayValue = [] + logToUser( + "Mesh creation from Polygon failed. Boundaries will be used as displayValue", + level=1, + func=inspect.stack()[0][3], + ) + return polygon, iterations - + except Exception as e: - logToUser("Some polygons might be invalid: " + str(e), level = 1, func = inspect.stack()[0][3]) + logToUser( + "Some polygons might be invalid: " + str(e), + level=1, + func=inspect.stack()[0][3], + ) return None, None - def polygonToNative(poly: Base, dataStorage) -> QgsPolygon: """Converts a Speckle Polygon base object to QgsPolygon. This object must have a 'boundary' and 'voids' properties. Each being a Speckle Polyline and List of polylines respectively.""" - #print(polylineToNative(poly["boundary"])) - + polygon = QgsPolygon() try: - try: # if it's indeed a polygon with QGIS properties + try: # if it's indeed a polygon with QGIS properties boundary = poly.boundary - polygon.setExteriorRing(polylineToNative(boundary, dataStorage )) - except: + polygon.setExteriorRing(polylineToNative(boundary, dataStorage)) + except: try: boundary = poly["boundary"] - polygon.setExteriorRing(polylineToNative(boundary, dataStorage )) - except: return + polygon.setExteriorRing(polylineToNative(boundary, dataStorage)) + except: + return try: voids = poly.voids - for void in voids: - #print(polylineToNative(void)) - polygon.addInteriorRing(polylineToNative(void, dataStorage )) + for void in voids: + polygon.addInteriorRing(polylineToNative(void, dataStorage)) except: try: voids = poly["voids"] - for void in voids: - #print(polylineToNative(void)) - polygon.addInteriorRing(polylineToNative(void, dataStorage )) - except: pass - #print(polygon) - #print() - - #polygon = QgsPolygon( - # polylineToNative(poly["boundary"]), - # [polylineToNative(void) for void in poly["voids"]], - #) + for void in voids: + polygon.addInteriorRing(polylineToNative(void, dataStorage)) + except: + pass return polygon except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return polygon + logToUser(e, level=2, func=inspect.stack()[0][3]) + return polygon -def getPolyBoundaryVoids(geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLayer, dataStorage): + +def getPolyBoundaryVoids( + geom: QgsGeometry, feature: QgsFeature, layer: QgsVectorLayer, dataStorage +): boundary = None voids: List[Union[None, Polyline, Arc, Line, Polycurve]] = [] - try: + try: pointList = [] pt_iterator = [] extRing = None - try: + try: extRing = geom.exteriorRing() pt_iterator = extRing.vertices() - except: - try: + except: + try: extRing = geom.constGet().exteriorRing() pt_iterator = geom.vertices() - except: + except: extRing = geom pt_iterator = geom.vertices() - for pt in pt_iterator: pointList.append(pt) - if extRing is not None: + for pt in pt_iterator: + pointList.append(pt) + if extRing is not None: boundary = unknownLineToSpeckle(extRing, True, feature, layer, dataStorage) - else: return boundary, voids + else: + return boundary, voids try: geom = geom.constGet() - except: pass + except: + pass for i in range(geom.numInteriorRings()): - intRing = unknownLineToSpeckle(geom.interiorRing(i), True, feature, layer, dataStorage) - #intRing = polylineFromVerticesToSpeckle(geom.interiorRing(i).vertices(), True, feature, layer) - voids.append(geom.interiorRing(i)) + intRing = unknownLineToSpeckle( + geom.interiorRing(i), True, feature, layer, dataStorage + ) + voids.append(intRing) return boundary, voids - + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return None, None - + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None, None diff --git a/speckle/converter/geometry/polyline.py b/speckle/converter/geometry/polyline.py index 3900a15d..c87bbc96 100644 --- a/speckle/converter/geometry/polyline.py +++ b/speckle/converter/geometry/polyline.py @@ -1,5 +1,5 @@ import inspect -from math import asin, cos, sin, atan +from math import atan import math import numpy as np from specklepy.objects.geometry import ( @@ -12,29 +12,21 @@ Ellipse, Polycurve, Plane, - Vector, ) from speckle.converter.geometry.point import pointToNative, pointToSpeckle from qgis.core import ( QgsLineString, - QgsPointXY, QgsCompoundCurve, - QgsCurve, QgsCircularString, - QgsPoint, QgsCircle, QgsFeature, QgsVectorLayer, - QgsCompoundCurve, QgsVertexIterator, - QgsGeometry, - QgsCurvePolygon, QgsEllipse, QgsWkbTypes, ) -from qgis._core import Qgis from speckle.converter.geometry.utils import ( addCorrectUnits, getArcNormal, @@ -42,14 +34,11 @@ speckleArcCircleToPoints, ) -# from speckle.utils.panel_logging import logger -from speckle.converter.layers.utils import get_scale_factor, get_scale_factor_to_meter +from speckle.converter.utils import get_scale_factor from typing import List, Tuple, Union from speckle.converter.layers.symbology import featureColorfromNativeRenderer from speckle.utils.panel_logging import logToUser -# from PyQt5.QtGui import QColor - def polylineFromVerticesToSpeckle( vertices: Union[List[Point], QgsVertexIterator], @@ -60,28 +49,27 @@ def polylineFromVerticesToSpeckle( ): """Returns a Speckle Polyline given a list of QgsPoint instances and a boolean indicating if it's closed or not.""" try: - # print(dataStorage) + specklePts: List[Point] = [] if isinstance(vertices, list): if len(vertices) > 0 and isinstance(vertices[0], Point): specklePts = vertices else: - specklePts = [ - pointToSpeckle(pt, feature, layer, dataStorage) for pt in vertices - ] # breaks unexplainably + for pt in vertices: + speckle_pt = pointToSpeckle(pt, feature, layer, dataStorage) + if speckle_pt is not None: + specklePts.append(speckle_pt) elif isinstance(vertices, QgsVertexIterator): - specklePts: list[Point] = [ - pointToSpeckle(pt, feature, layer, dataStorage) for pt in vertices - ] + for pt in vertices: + speckle_pt = pointToSpeckle(pt, feature, layer, dataStorage) + if speckle_pt is not None: + specklePts.append(speckle_pt) else: return None if specklePts[0] is None: logToUser("Polyline conversion failed", level=2) return - # specklePts = [] - # for pt in vertices: - # p = pointToSpeckle(pt) - # specklePts.append(p) + # TODO: Replace with `from_points` function when fix is pushed. polyline = Polyline() polyline.value = [] @@ -124,9 +112,6 @@ def unknownLineToSpeckle( return compoudCurveToSpeckle(poly, feature, layer, dataStorage) elif isinstance(poly, QgsCircularString): return arcToSpeckle(poly, feature, layer, dataStorage) - # elif isinstance(poly, QgsCurvePolygon): - # poly = poly.segmentize() - # return polylineFromVerticesToSpeckle(poly.vertices(), feature, layer) else: return polylineFromVerticesToSpeckle( poly.vertices(), closed, feature, layer, dataStorage @@ -145,8 +130,6 @@ def compoudCurveToSpeckle( poly = poly.constGet() except: pass - # poly = poly.curveToLine() - # return polylineToSpeckle(poly, feature, layer) polycurve = Polycurve(units="m") polycurve.segments = [] @@ -161,7 +144,6 @@ def compoudCurveToSpeckle( for p in parts: if "CircularString" in p: all_pts = [] - # [pt for pt in p.vertices()] for k in range(len(p.split(","))): all_pts.append(poly.childPoint(pt_len - segments_added + k)) @@ -178,7 +160,6 @@ def compoudCurveToSpeckle( newArc = arcToSpeckle(segm, feature, layer, dataStorage) if newArc is not None: polycurve.segments.append(newArc) - # vert.extend(speckleArcCircleToPoints(newArc)) startPt = all_pts[1:][i * 2 + 1] pt_len += 3 segments_added += 1 @@ -200,15 +181,13 @@ def compoudCurveToSpeckle( layer, dataStorage, ) - # print(poly.childPoint(pt_len+1)) - # print(type(poly.childPoint(pt_len+1).x())) if "EMPTY" in str(poly.childPoint(pt_len - segments_added + 1)): en = pointToSpeckle( poly.childPoint(0), feature, layer, dataStorage ) newLine = Line(units="m", start=st, end=en) polycurve.segments.append(newLine) - # print(p) + pt_len += 2 # because the end point will be reused as n-point of the curve segments_added += 1 else: # polyline @@ -240,7 +219,6 @@ def compoudCurveToSpeckle( polycurve.segments.append(newPolyline) pt_len += len(actual_segment_pts) segments_added += 1 - # polycurve.segments.append(p) # if closed_segm: polycurve = p # take the last segment only @@ -255,18 +233,9 @@ def compoudCurveToSpeckle( logToUser(e, level=2, func=inspect.stack()[0][3]) return - # if "CircularString" in str(poly): - # polycurve = polylineFromVerticesToSpeckle(vert, False, feature, layer) - - return polycurve - def anyLineToSpeckle(geom, feature, layer, dataStorage): - # geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType()) - # geomType = geom.type() type = geom.wkbType() - # units = dataStorage.currentUnits - if ( type == QgsWkbTypes.CircularString or type == QgsWkbTypes.CircularStringZ @@ -314,10 +283,7 @@ def polylineToSpeckle( polyline = polylineFromVerticesToSpeckle( poly.vertices(), closed, feature, layer, dataStorage ) - # colors already set in the previous function - # col = featureColorfromNativeRenderer(QgsFeature(), QgsVectorLayer()) - # polyline['displayStyle'] = {} - # polyline['displayStyle']['color'] = col + return polyline except Exception as e: logToUser(e, level=2, func=inspect.stack()[0][3]) @@ -337,9 +303,7 @@ def arcToSpeckle( center, radius = getArcCenter( arc.startPoint, arc.midPoint, arc.endPoint, dataStorage ) - arc.plane = ( - Plane() - ) # .from_list(Point(), Vector(Point(0, 0, 1)), Vector(Point(0,1,0)), Vector(Point(-1,0,0))) + arc.plane = Plane() arc.plane.origin = Point.from_list(center) arc.units = "m" arc.plane.normal = getArcNormal(arc, arc.midPoint) @@ -486,10 +450,8 @@ def circleToNative(poly: Circle, dataStorage) -> QgsLineString: def polycurveToNative(poly: Polycurve, dataStorage) -> QgsLineString: try: curve = QgsCompoundCurve() - pts_comp = [] points = [] - # curve = QgsLineString() singleSegm = 0 try: if len(poly.segments) == 0: @@ -502,31 +464,14 @@ def polycurveToNative(poly: Polycurve, dataStorage) -> QgsLineString: converted = lineToNative(segm, dataStorage) # QgsLineString if singleSegm == 1: return converted - - # if len(points) == 0: - # pts_comp.append(converted.startPoint()) - # curve.addVertex(converted.startPoint()) - # pts_comp.append(converted.endPoint()) - # curve.addVertex(converted.endPoint()) - elif isinstance(segm, Polyline): converted = polylineToNative(segm, dataStorage) # QgsLineString if singleSegm == 1: return converted - - # for k in range(converted.childCount()-1): - # pts_comp.append(converted.childPoint(k)) - # curve.addVertex(converted.childPoint(k)) - elif isinstance(segm, Curve): converted = curveToNative(segm, dataStorage) # QgsLineString if singleSegm == 1: return converted - - # for k in range(converted.childCount()): - # pts_comp.append(converted.childPoint(k)) - # curve.addVertex(converted.childPoint(k)) - elif isinstance(segm, Circle): pts = [ pointToNative(pt, dataStorage) @@ -537,14 +482,8 @@ def polycurveToNative(poly: Polycurve, dataStorage) -> QgsLineString: return circleToNative(segm, dataStorage) else: return None - # converted = circleToNative(segm) # QgsLineString elif isinstance(segm, Arc): - # pts = [pointToNative(pt, dataStorage ) for pt in speckleArcCircleToPoints(segm)] - # converted = QgsLineString(pts) # arcToNative(segm) # QgsLineString converted = arcToNative(segm, dataStorage) - - # curve.addCurve(converted.childPoint(0),converted.childPoint(1),converted.childPoint(2)) - if singleSegm == 1: return arcToNative(segm, dataStorage) elif isinstance(segm, Ellipse): @@ -569,9 +508,6 @@ def polycurveToNative(poly: Polycurve, dataStorage) -> QgsLineString: # add converted segment if converted is not None: curve.addCurve(converted, extendPrevious=True) - # for pt in converted.vertices(): - # if len(points)>0 and pt.x()== points[len(points)-1].x() and pt.y()== points[len(points)-1].y() and pt.z()== points[len(points)-1].z(): pass - # else: points.append(pt) else: logToUser( f"Part of the polycurve cannot be converted", diff --git a/speckle/converter/geometry/transform.py b/speckle/converter/geometry/transform.py index c5ba9f75..2d5946f1 100644 --- a/speckle/converter/geometry/transform.py +++ b/speckle/converter/geometry/transform.py @@ -1,21 +1,20 @@ - import inspect from qgis.core import ( QgsProject, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPointXY, - QgsProject, QgsCoordinateReferenceSystem, QgsCoordinateTransform, - QgsPointXY + QgsProject, + QgsCoordinateReferenceSystem, + QgsCoordinateTransform, + QgsPointXY, ) -from PyQt5.QtGui import QColor - from speckle.utils.panel_logging import logToUser def transform( - project: QgsProject, + project: QgsProject, src: QgsPointXY, crsSrc: QgsCoordinateReferenceSystem, crsDest: QgsCoordinateReferenceSystem, @@ -26,27 +25,10 @@ def transform( xform = QgsCoordinateTransform(crsSrc, crsDest, transformContext) # forward transformation: src -> dest - dest = xform.transform(src) - return dest - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return - -def reverseTransform( - project: QgsProject, - dest: QgsPointXY, - crsSrc: QgsCoordinateReferenceSystem, - crsDest: QgsCoordinateReferenceSystem, -): - """Transforms a QgsPointXY from the destination CRS to the source.""" - try: - transformContext = project.transformContext() - xform = QgsCoordinateTransform(crsSrc, crsDest, transformContext) - - # inverse transformation: dest -> src - src = xform.transform(dest, QgsCoordinateTransform.ReverseTransform) - return src + dest = xform.transform( + src + ) # reverse: (dest, QgsCoordinateTransform.ReverseTransform) + return dest # src except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - diff --git a/speckle/converter/geometry/utils.py b/speckle/converter/geometry/utils.py index d19cad35..505c5224 100644 --- a/speckle/converter/geometry/utils.py +++ b/speckle/converter/geometry/utils.py @@ -136,7 +136,7 @@ def getPolyPtsSegments(geom: Any, dataStorage: "DataStorage"): else: pointListLocal.append(pt) for i, pt in enumerate(pointListLocal): - x, y = applyOffsetsRotation(pt.x(), pt.y(), dataStorage) + x, y = apply_pt_offsets_rotation_on_send(pt.x(), pt.y(), dataStorage) vertices.append([x, y]) try: vertices3d.append([x, y, pt.z()]) @@ -177,12 +177,12 @@ def getPolyPtsSegments(geom: Any, dataStorage: "DataStorage"): if len(pointListLocal) > 2: holes.append( [ - applyOffsetsRotation(p.x(), p.y(), dataStorage) + apply_pt_offsets_rotation_on_send(p.x(), p.y(), dataStorage) for p in pointListLocal ] ) for i, pt in enumerate(pointListLocal): - x, y = applyOffsetsRotation(pt.x(), pt.y(), dataStorage) + x, y = apply_pt_offsets_rotation_on_send(pt.x(), pt.y(), dataStorage) try: vertices3d.append([x, y, pt.z()]) except: @@ -710,7 +710,7 @@ def getArcNormal(poly: Arc, midPt: Point, dataStorage) -> Union[Vector, None]: return None -def applyOffsetsRotation( +def apply_pt_offsets_rotation_on_send( x: float, y: float, dataStorage ) -> Tuple[Union[float, None]]: # on Send try: @@ -735,3 +735,94 @@ def applyOffsetsRotation( except Exception as e: logToUser(e, level=2, func=inspect.stack()[0][3]) return None, None + + +def transform_speckle_pt_on_receive(pt_original: Point, dataStorage) -> Point: + offset_x = dataStorage.crs_offset_x + offset_y = dataStorage.crs_offset_y + rotation = dataStorage.crs_rotation + + pt = Point( + x=pt_original.x, y=pt_original.y, z=pt_original.z, units=pt_original.units + ) + + gisLayer = None + try: + gisLayer = dataStorage.latestHostApp.lower().endswith("gis") + applyTransforms = False if (gisLayer and gisLayer is True) else True + except Exception as e: + print(e) + applyTransforms = True + + # for non-GIS layers + if applyTransforms is True: + if ( + rotation is not None + and (isinstance(rotation, float) or isinstance(rotation, int)) + and -360 < rotation < 360 + ): + a = rotation * math.pi / 180 + x2 = pt.x + y2 = pt.y + + # if a > 0: # turn counterclockwise on receive + x2 = pt.x * math.cos(a) - pt.y * math.sin(a) + y2 = pt.x * math.sin(a) + pt.y * math.cos(a) + + pt.x = x2 + pt.y = y2 + if ( + offset_x is not None + and isinstance(offset_x, float) + and offset_y is not None + and isinstance(offset_y, float) + ): + pt.x += offset_x + pt.y += offset_y + + # for GIS layers + if gisLayer is True: + try: + offset_x = dataStorage.current_layer_crs_offset_x + offset_y = dataStorage.current_layer_crs_offset_y + rotation = dataStorage.current_layer_crs_rotation + + if ( + rotation is not None + and isinstance(rotation, float) + and -360 < rotation < 360 + ): + a = rotation * math.pi / 180 + x2 = pt.x + y2 = pt.y + + # if a > 0: # turn counterclockwise on receive + x2 = pt.x * math.cos(a) - pt.y * math.sin(a) + y2 = pt.x * math.sin(a) + pt.y * math.cos(a) + + pt.x = x2 + pt.y = y2 + if ( + offset_x is not None + and isinstance(offset_x, float) + and offset_y is not None + and isinstance(offset_y, float) + ): + pt.x += offset_x + pt.y += offset_y + except Exception as e: + print(e) + + return pt + + +def apply_pt_transform_matrix(pt: Point, dataStorage) -> Point: + try: + if dataStorage.matrix is not None: + b = np.matrix([pt.x, pt.y, pt.z, 1]) + res = b * dataStorage.matrix + x, y, z = res.item(0), res.item(1), res.item(2) + return Point(x=x, y=y, z=z, units=pt.units) + except Exception as e: + print(e) + return pt diff --git a/speckle/converter/layers/__init__.py b/speckle/converter/layers/__init__.py index 7ee73925..ee87010f 100644 --- a/speckle/converter/layers/__init__.py +++ b/speckle/converter/layers/__init__.py @@ -2,16 +2,19 @@ Contains all Layer related classes and methods. """ import inspect -from typing import Any, List, Union - -from qgis.core import ( - QgsRasterLayer, - QgsVectorLayer, - QgsLayerTree, - QgsLayerTreeGroup, - QgsLayerTreeNode, - QgsLayerTreeLayer, -) +from typing import List, Union + +try: + from qgis.core import ( + QgsRasterLayer, + QgsVectorLayer, + QgsLayerTree, + QgsLayerTreeGroup, + QgsLayerTreeNode, + QgsLayerTreeLayer, + ) +except ModuleNotFoundError: + pass from speckle.utils.panel_logging import logToUser @@ -19,39 +22,39 @@ def getAllLayers( - tree: QgsLayerTree, parent: Union[QgsLayerTreeNode, None] = None -) -> List[Union[QgsVectorLayer, QgsRasterLayer]]: + tree: "QgsLayerTree", parent: Union["QgsLayerTreeNode", None] = None +) -> List[Union["QgsVectorLayer", "QgsRasterLayer"]]: try: layers = [] if parent is None: parent = tree - if isinstance(parent, QgsLayerTreeLayer): + if isinstance(parent, "QgsLayerTreeLayer"): return [parent.layer()] - elif isinstance(parent, QgsLayerTreeGroup): + elif isinstance(parent, "QgsLayerTreeGroup"): children = parent.children() for node in children: - if tree.isLayer(node) and isinstance(node, QgsLayerTreeLayer): - if isinstance(node.layer(), QgsVectorLayer) or isinstance( - node.layer(), QgsRasterLayer + if tree.isLayer(node) and isinstance(node, "QgsLayerTreeLayer"): + if isinstance(node.layer(), "QgsVectorLayer") or isinstance( + node.layer(), "QgsRasterLayer" ): layers.append(node.layer()) continue elif tree.isGroup(node): for lyr in getAllLayers(tree, node): - if isinstance(lyr, QgsVectorLayer) or isinstance( - lyr, QgsRasterLayer + if isinstance(lyr, "QgsVectorLayer") or isinstance( + lyr, "QgsRasterLayer" ): layers.append(lyr) - elif isinstance(node, QgsLayerTreeNode): + elif isinstance(node, "QgsLayerTreeNode"): try: visible = node.itemVisibilityChecked() node.setItemVisibilityChecked(True) for lyr in node.checkedLayers(): - if isinstance(lyr, QgsVectorLayer) or isinstance( - lyr, QgsRasterLayer + if isinstance(lyr, "QgsVectorLayer") or isinstance( + lyr, "QgsRasterLayer" ): layers.append(lyr) node.setItemVisibilityChecked(visible) @@ -65,7 +68,9 @@ def getAllLayers( def getAllLayersWithTree( - tree: QgsLayerTree, parent: QgsLayerTreeNode = None, existingStructure: str = "" + tree: "QgsLayerTree", + parent: Union["QgsLayerTreeNode", None] = None, + existingStructure: str = "", ): try: layers = [] @@ -153,9 +158,6 @@ def getAllLayersWithTree( def findAndClearLayerGroup(root, newGroupName: str = "", plugin=None): try: - # root = project_gis.layerTreeRoot() - - # print("clearGroup: " + str(newGroupName)) layerGroup = root.findGroup(newGroupName) if layerGroup is None: return @@ -163,7 +165,6 @@ def findAndClearLayerGroup(root, newGroupName: str = "", plugin=None): layersInGroup = getAllLayers(root, layerGroup) for lyr in layersInGroup: if isinstance(lyr, QgsVectorLayer): - # print(lyr.fields().names()) try: lyr.getFeature(0).geometry() # except: @@ -204,7 +205,7 @@ def findAndClearLayerGroup(root, newGroupName: str = "", plugin=None): return -def getSavedLayers(plugin) -> List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]]: +def getSavedLayers(plugin) -> List[Union["QgsLayerTreeLayer", "QgsLayerTreeNode"]]: """Gets a list of all layers in the given QgsLayerTree""" layers = [] @@ -244,14 +245,14 @@ def getSavedLayers(plugin) -> List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]]: return None, None -def getSelectedLayers(plugin) -> List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]]: +def getSelectedLayers(plugin) -> List[Union["QgsLayerTreeLayer", "QgsLayerTreeNode"]]: """Gets a list of all layers in the given QgsLayerTree""" return getSelectedLayersWithStructure(plugin)[0] def getSelectedLayersWithStructure( plugin, -) -> List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]]: +) -> List[Union["QgsLayerTreeLayer", "QgsLayerTreeNode"]]: """Gets a list of all layers in the given QgsLayerTree""" try: selected_layers = plugin.iface.layerTreeView().selectedNodes() @@ -264,8 +265,8 @@ def getSelectedLayersWithStructure( def getTreeFromLayers( - plugin, layers: List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]], layerTreeRoot -) -> List[Union[QgsLayerTreeLayer, QgsLayerTreeNode]]: + plugin, layers: List[Union["QgsLayerTreeLayer", "QgsLayerTreeNode"]], layerTreeRoot +) -> List[Union["QgsLayerTreeLayer", "QgsLayerTreeNode"]]: try: layers_list = [] tree_structure_list = [] diff --git a/speckle/converter/layers/feature.py b/speckle/converter/layers/feature.py index f7362099..fbd28e36 100644 --- a/speckle/converter/layers/feature.py +++ b/speckle/converter/layers/feature.py @@ -42,12 +42,11 @@ ) from speckle.converter.geometry.mesh import constructMeshFromRaster -from speckle.converter.geometry.utils import applyOffsetsRotation +from speckle.converter.geometry.utils import apply_pt_offsets_rotation_on_send -# from speckle.utils.panel_logging import logger +from speckle.converter.utils import get_scale_factor_to_meter from speckle.converter.layers.utils import ( get_raster_stats, - get_scale_factor_to_meter, getArrayIndicesFromXY, getElevationLayer, getHeightWithRemainderFromArray, @@ -435,7 +434,7 @@ def rasterFeatureToSpeckle( b.y_resolution = rasterResXY[1] b.x_size = rasterDimensions[0] b.y_size = rasterDimensions[1] - b.x_origin, b.y_origin = applyOffsetsRotation( + b.x_origin, b.y_origin = apply_pt_offsets_rotation_on_send( reprojectedPt.x(), reprojectedPt.y(), dataStorage ) b.band_count = rasterBandCount @@ -819,10 +818,18 @@ def rasterFeatureToSpeckle( row_z_bottom.append(z3) ######################################################## - x1, y1 = applyOffsetsRotation(pt1.x(), pt1.y(), dataStorage) - x2, y2 = applyOffsetsRotation(pt2.x(), pt2.y(), dataStorage) - x3, y3 = applyOffsetsRotation(pt3.x(), pt3.y(), dataStorage) - x4, y4 = applyOffsetsRotation(pt4.x(), pt4.y(), dataStorage) + x1, y1 = apply_pt_offsets_rotation_on_send( + pt1.x(), pt1.y(), dataStorage + ) + x2, y2 = apply_pt_offsets_rotation_on_send( + pt2.x(), pt2.y(), dataStorage + ) + x3, y3 = apply_pt_offsets_rotation_on_send( + pt3.x(), pt3.y(), dataStorage + ) + x4, y4 = apply_pt_offsets_rotation_on_send( + pt4.x(), pt4.y(), dataStorage + ) vertices.append( [x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4] diff --git a/speckle/converter/layers/layer_conversions.py b/speckle/converter/layers/layer_conversions.py index 6d633420..272c1ded 100644 --- a/speckle/converter/layers/layer_conversions.py +++ b/speckle/converter/layers/layer_conversions.py @@ -70,7 +70,7 @@ getAllLayersWithTree, getSavedLayers, getSelectedLayers, - getSelectedLayersWithStructure + getSelectedLayersWithStructure, ) from speckle.converter.layers.feature import ( featureToSpeckle, @@ -119,6 +119,7 @@ "Objects.Geometry.Polycurve", ] + def convertSelectedLayers( baseCollection: Collection, layers: List[Union[QgsVectorLayer, QgsRasterLayer]], @@ -2108,12 +2109,10 @@ def addRasterMainThread(obj: Tuple): return try: # if the CRS has offset props - # ptSpeckle = Point(x = pt.x(), y = pt.y(), z = 0, units = "m") dataStorage.current_layer_crs_offset_x = layer.crs.offset_x dataStorage.current_layer_crs_offset_y = layer.crs.offset_y dataStorage.current_layer_crs_rotation = layer.crs.rotation - # ptSpeckleTransformed = transformSpecklePt(ptSpeckle, dataStorage) pt = pointToNative( ptSpeckle, plugin.dataStorage ) # already transforms the offsets @@ -2123,8 +2122,6 @@ def addRasterMainThread(obj: Tuple): except Exception as e: print(e) - # print(crs) - # print(crsRaster) xform = QgsCoordinateTransform(crs, crsRaster, project) pt.transform(xform) try: diff --git a/speckle/converter/layers/symbology.py b/speckle/converter/layers/symbology.py index 1437b93e..b54444a7 100644 --- a/speckle/converter/layers/symbology.py +++ b/speckle/converter/layers/symbology.py @@ -1,459 +1,685 @@ - import inspect -from typing import Any, Union -from qgis.core import ( - QgsRasterRenderer, QgsFeatureRenderer, QgsFields, - QgsFeature, QgsVectorLayer, - QgsGradientColorRamp, - QgsGradientStop, QgsRendererRange, - QgsSingleBandGrayRenderer, - QgsPalettedRasterRenderer, QgsMultiBandColorRenderer, - QgsContrastEnhancement, - QgsSymbol, QgsWkbTypes, QgsRendererCategory, - QgsCategorizedSymbolRenderer, QgsSingleSymbolRenderer, - QgsGraduatedSymbolRenderer, QgsRasterDataProvider - -) +from typing import Any, Dict, Union + +try: + from qgis.core import ( + QgsRasterRenderer, + QgsFeatureRenderer, + QgsFields, + QgsFeature, + QgsVectorLayer, + QgsGradientColorRamp, + QgsGradientStop, + QgsRendererRange, + QgsSingleBandGrayRenderer, + QgsPalettedRasterRenderer, + QgsMultiBandColorRenderer, + QgsContrastEnhancement, + QgsSymbol, + QgsWkbTypes, + QgsRendererCategory, + QgsCategorizedSymbolRenderer, + QgsSingleSymbolRenderer, + QgsGraduatedSymbolRenderer, + QgsRasterDataProvider, + ) +except ModuleNotFoundError: + pass + from specklepy.objects.GIS.layers import Layer, RasterLayer, VectorLayer from PyQt5.QtGui import QColor from speckle.utils.panel_logging import logToUser -# TODO QML format: https://gis.stackexchange.com/questions/202230/loading-style-qml-file-to-layer-via-pyqgis +# TODO QML format: https://gis.stackexchange.com/questions/202230/loading-style-qml-file-to-layer-via-pyqgis + -def featureColorfromNativeRenderer(feature: QgsFeature, layer: QgsVectorLayer) -> int: +def featureColorfromNativeRenderer( + feature: "QgsFeature", layer: "QgsVectorLayer" +) -> int: # case with one color for the entire layer try: renderer = layer.renderer() - if renderer.type() == 'categorizedSymbol' or renderer.type() == '25dRenderer' or renderer.type() == 'invertedPolygonRenderer' or renderer.type() == 'mergedFeatureRenderer' or renderer.type() == 'RuleRenderer' or renderer.type() == 'nullSymbol' or renderer.type() == 'singleSymbol' or renderer.type() == 'graduatedSymbol': - #get color value - color = QColor.fromRgb(245,245,245) - if renderer.type() == 'singleSymbol': + if ( + renderer.type() == "categorizedSymbol" + or renderer.type() == "25dRenderer" + or renderer.type() == "invertedPolygonRenderer" + or renderer.type() == "mergedFeatureRenderer" + or renderer.type() == "RuleRenderer" + or renderer.type() == "nullSymbol" + or renderer.type() == "singleSymbol" + or renderer.type() == "graduatedSymbol" + ): + # get color value + color = QColor.fromRgb(245, 245, 245) + if renderer.type() == "singleSymbol": color = renderer.symbol().color() - elif renderer.type() == 'categorizedSymbol': + elif renderer.type() == "categorizedSymbol": sSymb = renderer.sourceSymbol() - if sSymb is not None: color = sSymb.color() - category = renderer.classAttribute() # get the name of attribute used for classification - try: - feature.attribute( category ) - renderer.categories() - except: - logToUser(f"Attribute '{category}' used for the layer '{layer.name()}' symbology is not found", level = 2, func = inspect.stack()[0][3]) - return (255<<24) + (245<<16) + (245<<8) + 245 - + if sSymb is not None: + color = sSymb.color() + category = ( + renderer.classAttribute() + ) # get the name of attribute used for classification + try: + feature.attribute(category) + renderer.categories() + except: + logToUser( + f"Attribute '{category}' used for the layer '{layer.name()}' symbology is not found", + level=2, + func=inspect.stack()[0][3], + ) + return (255 << 24) + (245 << 16) + (245 << 8) + 245 + for obj in renderer.categories(): - try: - if float(obj.value()) == float(feature.attribute( category )): + try: + if float(obj.value()) == float(feature.attribute(category)): color = obj.symbol().color() break except: - if str(obj.value()) == str(feature.attribute( category )): + if str(obj.value()) == str(feature.attribute(category)): color = obj.symbol().color() break - if str(obj.value()) == "None" or str(obj.value()) == "": # other category - color = obj.symbol().color() - elif renderer.type() == 'graduatedSymbol': + if ( + str(obj.value()) == "None" or str(obj.value()) == "" + ): # other category + color = obj.symbol().color() + elif renderer.type() == "graduatedSymbol": color = renderer.sourceSymbol().color() - category = renderer.legendClassificationAttribute() # get the name of attribute used for classification - if renderer.graduatedMethod() == 0: #if the styling is by color (not by size) + category = ( + renderer.legendClassificationAttribute() + ) # get the name of attribute used for classification + if ( + renderer.graduatedMethod() == 0 + ): # if the styling is by color (not by size) for obj in renderer.ranges(): - if feature.attribute( category ) >= obj.lowerValue() and feature.attribute( category ) <= obj.upperValue(): + if ( + feature.attribute(category) >= obj.lowerValue() + and feature.attribute(category) <= obj.upperValue() + ): color = obj.symbol().color() break # construct RGB color - try: r, g, b = color.getRgb()[:3] - except: r, g, b = [int(i) for i in color.replace(" ","").split(",")[:3] ] - col = (255<<24) + (r<<16) + (g<<8) + b + try: + r, g, b = color.getRgb()[:3] + except: + r, g, b = [int(i) for i in color.replace(" ", "").split(",")[:3]] + col = (255 << 24) + (r << 16) + (g << 8) + b return col - else: return (255<<24) + (245<<16) + (245<<8) + 245 + else: + return (255 << 24) + (245 << 16) + (245 << 8) + 245 except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return (255<<24) + (245<<16) + (245<<8) + 245 + logToUser(e, level=2, func=inspect.stack()[0][3]) + return (255 << 24) + (245 << 16) + (245 << 8) + 245 + -def gradientColorRampToSpeckle(rRamp: QgsGradientColorRamp) -> dict[str, Any]: +def gradientColorRampToSpeckle( + rRamp: "QgsGradientColorRamp", +) -> Union[Dict[str, Any], None]: sourceRamp = None try: - props = rRamp.properties() # {'color1': '255,255,255,255', 'color2': '255,0,0,255', 'discrete': '0', 'rampType': 'gradient'} - stops = rRamp.stops() #[] + props = ( + rRamp.properties() + ) # {'color1': '255,255,255,255', 'color2': '255,0,0,255', 'discrete': '0', 'rampType': 'gradient'} + stops = rRamp.stops() # [] stopsStr = [] for s in stops: - try: r, g, b = s.color.getRgb()[:3] - except: r, g, b = [int(i) for i in s.color.replace(" ","").split(',')[:3] ] - sColor = (255<<24) + (r<<16) + (g<<8) + b - stopsStr.append({'color':sColor, 'offset':s.offset}) - rampType = rRamp.type() #'gradient' + try: + r, g, b = s.color.getRgb()[:3] + except: + r, g, b = [int(i) for i in s.color.replace(" ", "").split(",")[:3]] + sColor = (255 << 24) + (r << 16) + (g << 8) + b + stopsStr.append({"color": sColor, "offset": s.offset}) + rampType = rRamp.type() #'gradient' sourceRamp = props - sourceRamp.update({'stops': stopsStr, 'rampType':rampType}) + sourceRamp.update({"stops": stopsStr, "rampType": rampType}) return sourceRamp except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return sourceRamp -def gradientColorRampToNative(renderer: dict[str, Any]) -> QgsGradientColorRamp: +def gradientColorRampToNative(renderer: dict[str, Any]) -> "QgsGradientColorRamp": newRamp = None - try: # if it's not a random color ramp - ramp = renderer['properties']['ramp'] # {discrete, rampType, stops} - oldStops = ramp['stops'] + try: # if it's not a random color ramp + ramp = renderer["properties"]["ramp"] # {discrete, rampType, stops} + oldStops = ramp["stops"] stops = [] for i in range(len(oldStops)): - rgb = oldStops[i]['color'] + rgb = oldStops[i]["color"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF sColor = QColor.fromRgb(r, g, b) - s = QgsGradientStop(oldStops[i]['offset'], sColor) + s = QgsGradientStop(oldStops[i]["offset"], sColor) stops.append(s) - c11,c12,c13,alpa1 = ramp['color1'].split(',') - color1 = QColor.fromRgb(int(c11),int(c12),int(c13)) - c21,c22,c23,alpha2 = ramp['color2'].split(',') - color2 = QColor.fromRgb(int(c21),int(c22),int(c23)) - discrete = int(ramp['discrete']) - newRamp = QgsGradientColorRamp(color1,color2,discrete,stops) + c11, c12, c13, alpa1 = ramp["color1"].split(",") + color1 = QColor.fromRgb(int(c11), int(c12), int(c13)) + c21, c22, c23, alpha2 = ramp["color2"].split(",") + color2 = QColor.fromRgb(int(c21), int(c22), int(c23)) + discrete = int(ramp["discrete"]) + newRamp = QgsGradientColorRamp(color1, color2, discrete, stops) return newRamp - + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return newRamp + def get_r_g_b(rgb: int) -> tuple[int, int, int]: r = g = b = 0 - try: + try: r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) - return 0,0,0 + logToUser(e, level=2, func=inspect.stack()[0][3]) + return 0, 0, 0 + -def vectorRendererToNative(layer: Union[Layer, VectorLayer], fields: QgsFields ) -> Union[QgsSingleSymbolRenderer,QgsCategorizedSymbolRenderer, QgsGraduatedSymbolRenderer]: - +def vectorRendererToNative( + layer: Union[Layer, VectorLayer], fields: "QgsFields" +) -> Union[ + "QgsSingleSymbolRenderer", + "QgsCategorizedSymbolRenderer", + "QgsGraduatedSymbolRenderer", +]: rendererNew = None try: - renderer = layer.renderer + renderer = layer.renderer existingAttrs = fields.names() geomType = layer.geomType - if geomType == 'MultiPatch': geomType = "Polygon" - - if "polyline" in geomType.lower(): geomType = 'LineString' - if renderer and renderer['type']: + if geomType == "MultiPatch": + geomType = "Polygon" - if renderer['type'] == 'categorizedSymbol': - try: r,g,b = get_r_g_b(renderer['properties']['sourceSymbColor']) - except: r = g = b = 100 + if "polyline" in geomType.lower(): + geomType = "LineString" + if renderer and renderer["type"]: + if renderer["type"] == "categorizedSymbol": + try: + r, g, b = get_r_g_b(renderer["properties"]["sourceSymbColor"]) + except: + r = g = b = 100 sourceSymbColor = QColor.fromRgb(r, g, b) - attribute = renderer['properties']['attribute'] - cats = renderer['properties']['categories'] - if attribute not in existingAttrs: + attribute = renderer["properties"]["attribute"] + cats = renderer["properties"]["categories"] + if attribute not in existingAttrs: rendererNew = makeDefaultRenderer(renderer, layer) return rendererNew categories = [] noneVal = 0 for i in range(len(cats)): - v = cats[i]['value'] - if v=="": v = None - if v is None or v=="": noneVal +=1 - rgb = cats[i]['symbColor'] + v = cats[i]["value"] + if v == "": + v = None + if v is None or v == "": + noneVal += 1 + rgb = cats[i]["symbColor"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF color = QColor.fromRgb(r, g, b) - symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) # create an extra category for possible future feature - #if len(categories)==0: + # if len(categories)==0: # symbol.setColor(QColor.fromRgb(0,0,0)) # categories.append(QgsRendererCategory()) # categories[0].setSymbol(symbol) # categories[0].setLabel('Other') symbol.setColor(color) - categories.append(QgsRendererCategory(v, symbol, cats[i]['label'], True) ) + categories.append( + QgsRendererCategory(v, symbol, cats[i]["label"], True) + ) # create empty category for all other values (if doesn't exist yet) if noneVal == 0: symbol2 = symbol.clone() - symbol2.setColor(QColor.fromRgb(0,0,0)) + symbol2.setColor(QColor.fromRgb(0, 0, 0)) cat = QgsRendererCategory() cat.setSymbol(symbol2) - cat.setLabel('Other') + cat.setLabel("Other") categories.append(cat) - + rendererNew = QgsCategorizedSymbolRenderer(attribute, categories) try: - sourceSymbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + sourceSymbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) sourceSymbol.setColor(sourceSymbColor) rendererNew.setSourceSymbol(sourceSymbol) - except: pass + except: + pass - elif renderer['type'] == 'singleSymbol': - rgb = renderer['properties']['symbol']['symbColor'] + elif renderer["type"] == "singleSymbol": + rgb = renderer["properties"]["symbol"]["symbColor"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF color = QColor.fromRgb(r, g, b) - symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) symbol.setColor(color) rendererNew = QgsSingleSymbolRenderer(symbol) - - elif renderer['type'] == 'graduatedSymbol': - attribute = renderer['properties']['attribute'] - gradMetod = renderer['properties']['gradMethod'] # by color or by size - if attribute not in existingAttrs: + + elif renderer["type"] == "graduatedSymbol": + attribute = renderer["properties"]["attribute"] + gradMetod = renderer["properties"]["gradMethod"] # by color or by size + if attribute not in existingAttrs: rendererNew = makeDefaultRenderer(renderer, layer) return rendererNew - rgb = renderer['properties']['sourceSymbColor'] + rgb = renderer["properties"]["sourceSymbColor"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF sourceSymbColor = QColor.fromRgb(r, g, b) - + if gradMetod == 0: - ramp = renderer['properties']['ramp'] # {discrete, rampType, stops} - ranges = renderer['properties']['ranges'] # [] - newRamp = gradientColorRampToNative(renderer) #QgsGradientColorRamp + ramp = renderer["properties"]["ramp"] # {discrete, rampType, stops} + ranges = renderer["properties"]["ranges"] # [] + newRamp = gradientColorRampToNative( + renderer + ) # QgsGradientColorRamp newRanges = [] for i in range(len(ranges)): - rgb = ranges[i]['symbColor'] + rgb = ranges[i]["symbColor"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF color = QColor.fromRgb(r, g, b) - width = ranges[i]['symbColor'] - symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + width = ranges[i]["symbColor"] + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) symbol.setColor(color) - newRanges.append(QgsRendererRange(ranges[i]['lower'],ranges[i]['upper'],symbol,ranges[i]['label'],True) ) - try: + newRanges.append( + QgsRendererRange( + ranges[i]["lower"], + ranges[i]["upper"], + symbol, + ranges[i]["label"], + True, + ) + ) + try: rendererNew = QgsGraduatedSymbolRenderer(attribute, newRanges) rendererNew.setSourceColorRamp(newRamp) - sourceSymbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + sourceSymbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) sourceSymbol.setColor(sourceSymbColor) rendererNew.setSourceSymbol(sourceSymbol) - except: rendererNew = QgsGraduatedSymbolRenderer() - try: rendererNew.setGraduatedMethod(gradMetod) - except: rendererNew.setGraduatedMethod(QgsGraduatedSymbolRenderer.GraduatedMethod(gradMetod)) + except: + rendererNew = QgsGraduatedSymbolRenderer() + try: + rendererNew.setGraduatedMethod(gradMetod) + except: + rendererNew.setGraduatedMethod( + QgsGraduatedSymbolRenderer.GraduatedMethod(gradMetod) + ) else: rendererNew = makeDefaultRenderer(renderer, layer) return rendererNew except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return rendererNew -def makeDefaultRenderer(renderer: dict[str, Any], layer: Union[Layer, VectorLayer]) -> QgsSingleSymbolRenderer: - rendererNew = None + +def makeDefaultRenderer( + renderer: dict[str, Any], layer: Union[Layer, VectorLayer] +) -> "QgsSingleSymbolRenderer": + rendererNew = None try: geomType = layer.geomType - try: rgb = renderer['properties']['sourceSymbColor'] - except: rgb = (255<<24) + (0<<16) + (0<<8) + 0 + try: + rgb = renderer["properties"]["sourceSymbColor"] + except: + rgb = (255 << 24) + (0 << 16) + (0 << 8) + 0 r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF color = QColor.fromRgb(r, g, b) - symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType))) + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) symbol.setColor(color) rendererNew = QgsSingleSymbolRenderer(symbol) return rendererNew except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return rendererNew -def rasterRendererToNative(layer: RasterLayer, rInterface: QgsRasterDataProvider) -> Union[QgsSingleBandGrayRenderer, QgsMultiBandColorRenderer, QgsPalettedRasterRenderer]: - + +def rasterRendererToNative( + layer: RasterLayer, rInterface: "QgsRasterDataProvider" +) -> Union[ + "QgsSingleBandGrayRenderer", + "QgsMultiBandColorRenderer", + "QgsPalettedRasterRenderer", +]: rendererNew = None try: renderer = layer.renderer - if renderer and renderer['type']: - if renderer['type'] == 'singlebandgray': - band = renderer['properties']['band'] + if renderer and renderer["type"]: + if renderer["type"] == "singlebandgray": + band = renderer["properties"]["band"] contrast = QgsContrastEnhancement() - contrast.setContrastEnhancementAlgorithm(int(renderer['properties']['contrast'])) - contrast.setMaximumValue(float(renderer['properties']['max'])) - contrast.setMinimumValue(float(renderer['properties']['min'])) - - rendererNew = QgsSingleBandGrayRenderer(rInterface,int(band)) + contrast.setContrastEnhancementAlgorithm( + int(renderer["properties"]["contrast"]) + ) + contrast.setMaximumValue(float(renderer["properties"]["max"])) + contrast.setMinimumValue(float(renderer["properties"]["min"])) + + rendererNew = QgsSingleBandGrayRenderer(rInterface, int(band)) rendererNew.setContrastEnhancement(contrast) - if renderer['type'] == 'multibandcolor': - redBand = renderer['properties']['redBand'] - greenBand = renderer['properties']['greenBand'] - blueBand = renderer['properties']['blueBand'] - rendererNew = QgsMultiBandColorRenderer(rInterface,int(redBand),int(greenBand),int(blueBand)) + if renderer["type"] == "multibandcolor": + redBand = renderer["properties"]["redBand"] + greenBand = renderer["properties"]["greenBand"] + blueBand = renderer["properties"]["blueBand"] + rendererNew = QgsMultiBandColorRenderer( + rInterface, int(redBand), int(greenBand), int(blueBand) + ) try: contrastR = QgsContrastEnhancement() - contrastR.setContrastEnhancementAlgorithm(int(renderer['properties']['redContrast'])) - contrastR.setMaximumValue(float(renderer['properties']['redMax'])) - contrastR.setMinimumValue(float(renderer['properties']['redMin'])) - #rendererNew.setRedContrastEnhancement(contrastR) - except: pass + contrastR.setContrastEnhancementAlgorithm( + int(renderer["properties"]["redContrast"]) + ) + contrastR.setMaximumValue(float(renderer["properties"]["redMax"])) + contrastR.setMinimumValue(float(renderer["properties"]["redMin"])) + # rendererNew.setRedContrastEnhancement(contrastR) + except: + pass try: contrastG = QgsContrastEnhancement() - contrastG.setContrastEnhancementAlgorithm(int(renderer['properties']['greenContrast'])) - contrastG.setMaximumValue(float(renderer['properties']['greenMax'])) - contrastG.setMinimumValue(float(renderer['properties']['greenMin'])) - #rendererNew.setGreenContrastEnhancement(contrastG) - except: pass + contrastG.setContrastEnhancementAlgorithm( + int(renderer["properties"]["greenContrast"]) + ) + contrastG.setMaximumValue(float(renderer["properties"]["greenMax"])) + contrastG.setMinimumValue(float(renderer["properties"]["greenMin"])) + # rendererNew.setGreenContrastEnhancement(contrastG) + except: + pass try: contrastB = QgsContrastEnhancement() - contrastB.setContrastEnhancementAlgorithm(int(renderer['properties']['blueContrast'])) - contrastB.setMaximumValue(float(renderer['properties']['blueMax'])) - contrastB.setMinimumValue(float(renderer['properties']['blueMin'])) - #rendererNew.setBlueContrastEnhancement(contrastB) - except: pass - - if renderer['type'] == 'paletted': - band = renderer['properties']['band'] - classes = renderer['properties']['classes'] - #newRamp = gradientColorRampToNative(renderer) #QgsGradientColorRamp + contrastB.setContrastEnhancementAlgorithm( + int(renderer["properties"]["blueContrast"]) + ) + contrastB.setMaximumValue(float(renderer["properties"]["blueMax"])) + contrastB.setMinimumValue(float(renderer["properties"]["blueMin"])) + # rendererNew.setBlueContrastEnhancement(contrastB) + except: + pass + + if renderer["type"] == "paletted": + band = renderer["properties"]["band"] + classes = renderer["properties"]["classes"] + # newRamp = gradientColorRampToNative(renderer) #QgsGradientColorRamp newClasses = [] for i in classes: - rgb = i['color'] + rgb = i["color"] r = (rgb & 0xFF0000) >> 16 g = (rgb & 0xFF00) >> 8 - b = rgb & 0xFF + b = rgb & 0xFF color = QColor.fromRgb(r, g, b) - newClasses.append(QgsPalettedRasterRenderer.Class(float(i['value']),color,i['label'])) - - rendererNew = QgsPalettedRasterRenderer(rInterface,int(band),newClasses) + newClasses.append( + QgsPalettedRasterRenderer.Class( + float(i["value"]), color, i["label"] + ) + ) + + rendererNew = QgsPalettedRasterRenderer( + rInterface, int(band), newClasses + ) return rendererNew except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return rendererNew - -def rendererToSpeckle(renderer: QgsFeatureRenderer or QgsRasterRenderer) -> dict[str, Any]: + +def rendererToSpeckle( + renderer: Union["QgsFeatureRenderer", "QgsRasterRenderer"], +) -> dict[str, Any]: layerRenderer: dict[str, Any] = {} - if renderer is None: return layerRenderer # e.g. for no-geom layers + if renderer is None: + return layerRenderer # e.g. for no-geom layers try: - #print("___RENDERER TO SPECKLE___") - rType = renderer.type() # 'singleSymbol','categorizedSymbol','graduatedSymbol', - layerRenderer['type'] = rType - - if rType == 'singleSymbol': - layerRenderer['properties'] = {'symbol':{}, 'symbType':""} - - symbol = renderer.symbol() #singleSymbol # QgsLineSymbol - #print(symbol) - symbType = symbol.symbolTypeToString(symbol.type()) #Line - try: rgb = symbol.color().getRgb() - except: [int(i) for i in symbol().color().replace(" ","").split(',')[:3] ] - symbolColor = (255<<24) + (rgb[0]<<16) + (rgb[1]<<8) + rgb[2] - layerRenderer['properties'].update({'symbol':{'symbColor': symbolColor}, 'symbType':symbType}) - - elif rType == 'categorizedSymbol': - layerRenderer['properties'] = {'attribute': "", 'symbType': ""} #{'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':"", 'legendClassificationAttribute': ""} - attribute = renderer.classAttribute() # 'id' - layerRenderer['properties']['attribute'] = attribute + # print("___RENDERER TO SPECKLE___") + rType = renderer.type() # 'singleSymbol','categorizedSymbol','graduatedSymbol', + layerRenderer["type"] = rType + + if rType == "singleSymbol": + layerRenderer["properties"] = {"symbol": {}, "symbType": ""} + + symbol = renderer.symbol() # singleSymbol # QgsLineSymbol + # print(symbol) + symbType = symbol.symbolTypeToString(symbol.type()) # Line + try: + rgb = symbol.color().getRgb() + except: + [int(i) for i in symbol().color().replace(" ", "").split(",")[:3]] + symbolColor = (255 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] + layerRenderer["properties"].update( + {"symbol": {"symbColor": symbolColor}, "symbType": symbType} + ) + + elif rType == "categorizedSymbol": + layerRenderer["properties"] = { + "attribute": "", + "symbType": "", + } # {'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':"", 'legendClassificationAttribute': ""} + attribute = renderer.classAttribute() # 'id' + layerRenderer["properties"]["attribute"] = attribute symbol = renderer.sourceSymbol() - sourceSymbColor = (255<<24) + (0<<16) + (0<<8) + 0 + sourceSymbColor = (255 << 24) + (0 << 16) + (0 << 8) + 0 try: - symbType = symbol.symbolTypeToString(symbol.type()) #Line - try: r, g, b = symbol.color().getRgb()[:3] - except: r,g,b = [int(i) for i in symbol.color().replace(" ","").split(',')[:3] ] - sourceSymbColor = (255<<24) + (r<<16) + (g<<8) + b - - layerRenderer['properties'].update( {'symbType': symbType, 'sourceSymbColor': sourceSymbColor} ) - except: pass - - categories = renderer.categories() # - layerRenderer['properties']['categories'] = [] + symbType = symbol.symbolTypeToString(symbol.type()) # Line + try: + r, g, b = symbol.color().getRgb()[:3] + except: + r, g, b = [ + int(i) for i in symbol.color().replace(" ", "").split(",")[:3] + ] + sourceSymbColor = (255 << 24) + (r << 16) + (g << 8) + b + + layerRenderer["properties"].update( + {"symbType": symbType, "sourceSymbColor": sourceSymbColor} + ) + except: + pass + + categories = ( + renderer.categories() + ) # + layerRenderer["properties"]["categories"] = [] for i in categories: value = i.value() - try: r, g, b = i.symbol().color().getRgb()[:3] - except: r,g,b = [int(i) for i in i.symbol().color().replace(" ","").split(',')[:3] ] - symbColor = (255<<24) + (r<<16) + (g<<8) + b - symbOpacity = i.symbol().opacity() # QgsSymbol.color() - label = i.label() - layerRenderer['properties']['categories'].append({'value':value,'symbColor':symbColor,'symbOpacity':symbOpacity, 'sourceSymbColor': sourceSymbColor,'label':label}) - - elif rType == 'graduatedSymbol': - layerRenderer['properties'] = {'symbol':{}, 'ramp':{}, 'ranges':{}, 'gradMethod':"", 'symbType':""} - - attribute = renderer.legendClassificationAttribute() # 'id' - symbol = renderer.sourceSymbol() # QgsLineSymbol - symbType = symbol.symbolTypeToString(symbol.type()) #Line - try: r, g, b = symbol.color().getRgb()[:3] - except: r, g, b = [int(i) for i in symbol.color().replace(" ","").split(',')[:3] ] - sourceSymbColor = (255<<24) + (r<<16) + (g<<8) + b - gradMethod = renderer.graduatedMethod() # 0 - layerRenderer['properties'].update( {'attribute': attribute, 'symbType': symbType, 'gradMethod': gradMethod, 'sourceSymbColor': sourceSymbColor} ) - - rRamp = renderer.sourceColorRamp() # QgsGradientColorRamp - if isinstance(rRamp,QgsGradientColorRamp) : - layerRenderer['properties']['ramp'] = gradientColorRampToSpeckle(rRamp) - - rRanges = renderer.ranges() # [QgsRendererRange,...] - layerRenderer['properties']['ranges'] = [] + try: + r, g, b = i.symbol().color().getRgb()[:3] + except: + r, g, b = [ + int(i) + for i in i.symbol().color().replace(" ", "").split(",")[:3] + ] + symbColor = (255 << 24) + (r << 16) + (g << 8) + b + symbOpacity = i.symbol().opacity() # QgsSymbol.color() + label = i.label() + layerRenderer["properties"]["categories"].append( + { + "value": value, + "symbColor": symbColor, + "symbOpacity": symbOpacity, + "sourceSymbColor": sourceSymbColor, + "label": label, + } + ) + + elif rType == "graduatedSymbol": + layerRenderer["properties"] = { + "symbol": {}, + "ramp": {}, + "ranges": {}, + "gradMethod": "", + "symbType": "", + } + + attribute = renderer.legendClassificationAttribute() # 'id' + symbol = renderer.sourceSymbol() # QgsLineSymbol + symbType = symbol.symbolTypeToString(symbol.type()) # Line + try: + r, g, b = symbol.color().getRgb()[:3] + except: + r, g, b = [ + int(i) for i in symbol.color().replace(" ", "").split(",")[:3] + ] + sourceSymbColor = (255 << 24) + (r << 16) + (g << 8) + b + gradMethod = renderer.graduatedMethod() # 0 + layerRenderer["properties"].update( + { + "attribute": attribute, + "symbType": symbType, + "gradMethod": gradMethod, + "sourceSymbColor": sourceSymbColor, + } + ) + + rRamp = renderer.sourceColorRamp() # QgsGradientColorRamp + if isinstance(rRamp, QgsGradientColorRamp): + layerRenderer["properties"]["ramp"] = gradientColorRampToSpeckle(rRamp) + + rRanges = renderer.ranges() # [QgsRendererRange,...] + layerRenderer["properties"]["ranges"] = [] for i in rRanges: if isinstance(i, QgsRendererRange): lower = i.lowerValue() upper = i.upperValue() - rgb = i.symbol().color().getRgb() # QgsSymbol.color() -> QColor - symbColor = (255<<24) + (rgb[0]<<16) + (rgb[1]<<8) + rgb[2] - symbOpacity = i.symbol().opacity() # QgsSymbol.color() - label = i.label() + rgb = i.symbol().color().getRgb() # QgsSymbol.color() -> QColor + symbColor = (255 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] + symbOpacity = i.symbol().opacity() # QgsSymbol.color() + label = i.label() width = 0.26 - try: width = i.width() - except: pass + try: + width = i.width() + except: + pass # {'label': '1 - 1.4', 'lower': 1.0, 'symbColor': , 'symbOpacity': 1.0, 'upper': 1.4} - layerRenderer['properties']['ranges'].append({'lower':lower,'upper':upper,'symbColor':symbColor,'symbOpacity':symbOpacity,'label':label,'width':width}) - - elif rType == "singlebandgray": + layerRenderer["properties"]["ranges"].append( + { + "lower": lower, + "upper": upper, + "symbColor": symbColor, + "symbOpacity": symbOpacity, + "label": label, + "width": width, + } + ) + + elif rType == "singlebandgray": band = renderer.grayBand() contrast = renderer.contrastEnhancement().contrastEnhancementAlgorithm() mmin = renderer.contrastEnhancement().minimumValue() mmax = renderer.contrastEnhancement().maximumValue() - layerRenderer.update({'properties': {'max':mmax,'min':mmin,'band':band,'contrast':contrast}}) - elif rType == "multibandcolor": + layerRenderer.update( + { + "properties": { + "max": mmax, + "min": mmin, + "band": band, + "contrast": contrast, + } + } + ) + elif rType == "multibandcolor": redBand = renderer.redBand() greenBand = renderer.greenBand() - blueBand = renderer.blueBand() - redContrast = redMin = redMax = greenContrast = greenMin = greenMax = blueContrast = blueMin = blueMax = None + blueBand = renderer.blueBand() + redContrast = ( + redMin + ) = ( + redMax + ) = ( + greenContrast + ) = greenMin = greenMax = blueContrast = blueMin = blueMax = None try: - redContrast = renderer.redContrastEnhancement().contrastEnhancementAlgorithm() + redContrast = ( + renderer.redContrastEnhancement().contrastEnhancementAlgorithm() + ) redMin = renderer.redContrastEnhancement().minimumValue() redMax = renderer.redContrastEnhancement().maximumValue() - except: pass + except: + pass try: - greenContrast = renderer.greenContrastEnhancement().contrastEnhancementAlgorithm() + greenContrast = ( + renderer.greenContrastEnhancement().contrastEnhancementAlgorithm() + ) greenMin = renderer.greenContrastEnhancement().minimumValue() greenMax = renderer.greenContrastEnhancement().maximumValue() - except: pass + except: + pass try: - blueContrast = renderer.blueContrastEnhancement().contrastEnhancementAlgorithm() + blueContrast = ( + renderer.blueContrastEnhancement().contrastEnhancementAlgorithm() + ) blueMin = renderer.blueContrastEnhancement().minimumValue() blueMax = renderer.blueContrastEnhancement().maximumValue() - except: pass - layerRenderer.update({'properties': {'greenBand':greenBand,'blueBand':blueBand,'redBand':redBand}}) - layerRenderer['properties'].update({'redContrast':redContrast,'redMin':redMin,'redMax':redMax}) - layerRenderer['properties'].update({'greenContrast':greenContrast,'greenMin':greenMin,'greenMax':greenMax}) - layerRenderer['properties'].update({'blueContrast':blueContrast,'blueMin':blueMin,'blueMax':blueMax}) - - elif rType == "paletted": + except: + pass + layerRenderer.update( + { + "properties": { + "greenBand": greenBand, + "blueBand": blueBand, + "redBand": redBand, + } + } + ) + layerRenderer["properties"].update( + {"redContrast": redContrast, "redMin": redMin, "redMax": redMax} + ) + layerRenderer["properties"].update( + { + "greenContrast": greenContrast, + "greenMin": greenMin, + "greenMax": greenMax, + } + ) + layerRenderer["properties"].update( + {"blueContrast": blueContrast, "blueMin": blueMin, "blueMax": blueMax} + ) + + elif rType == "paletted": band = renderer.band() rendererClasses = renderer.classes() classes = [] rRamp = renderer.sourceColorRamp() sourceRamp = {} - if isinstance(rRamp,QgsGradientColorRamp) : # or QgsRandomColorRamp - sourceRamp = gradientColorRampToSpeckle(rRamp) #rampType, stops,props(e.g.color) + if isinstance(rRamp, QgsGradientColorRamp): # or QgsRandomColorRamp + sourceRamp = gradientColorRampToSpeckle( + rRamp + ) # rampType, stops,props(e.g.color) for i in rendererClasses: - value = i.value + value = i.value rgb = i.color.getRgb() - color = (255<<24) + (rgb[0]<<16) + (rgb[1]<<8) + rgb[2] - classes.append({'color':color,'value':value,'label':i.label}) - layerRenderer.update({'properties': {'classes':classes,'ramp':sourceRamp,'band':band}}) - - else: - layerRenderer = {'type': 'Other', 'properties': {}} + color = (255 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] + classes.append({"color": color, "value": value, "label": i.label}) + layerRenderer.update( + {"properties": {"classes": classes, "ramp": sourceRamp, "band": band}} + ) + + else: + layerRenderer = {"type": "Other", "properties": {}} return layerRenderer except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return layerRenderer - - diff --git a/speckle/converter/layers/utils.py b/speckle/converter/layers/utils.py index 93efc419..954a5375 100644 --- a/speckle/converter/layers/utils.py +++ b/speckle/converter/layers/utils.py @@ -16,21 +16,27 @@ ) from PyQt5.QtCore import QVariant, QDate, QDateTime -from qgis._core import ( - Qgis, - QgsProject, - QgsCoordinateReferenceSystem, - QgsLayerTreeLayer, - QgsVectorLayer, - QgsRasterLayer, - QgsWkbTypes, - QgsField, - QgsFields, - QgsLayerTreeGroup, -) + +try: + from qgis._core import ( + Qgis, + QgsProject, + QgsCoordinateReferenceSystem, + QgsLayerTreeLayer, + QgsVectorLayer, + QgsRasterLayer, + QgsWkbTypes, + QgsField, + QgsFields, + QgsLayerTreeGroup, + ) + + from osgeo import gdal, ogr, osr +except ModuleNotFoundError: + pass + from PyQt5.QtGui import QColor -from osgeo import gdal, ogr, osr import math import numpy as np @@ -53,7 +59,7 @@ def getLayerGeomType( - layer: QgsVectorLayer, + layer: "QgsVectorLayer", ): # https://qgis.org/pyqgis/3.0/core/Wkb/QgsWkbTypes.html # print(layer.wkbType()) try: @@ -223,7 +229,7 @@ def colorFromSpeckle(rgb): return QColor.fromRgb(245, 245, 245) -def getLayerAttributes(features: List[Base]) -> QgsFields: +def getLayerAttributes(features: List[Base]) -> "QgsFields": try: # print("___________getLayerAttributes") fields = QgsFields() @@ -389,52 +395,6 @@ def traverseDict( return -def get_scale_factor(units: str, dataStorage) -> float: - scale_to_meter = get_scale_factor_to_meter(units) - if dataStorage is not None: - scale_back = scale_to_meter / get_scale_factor_to_meter( - dataStorage.currentUnits - ) - return scale_back - else: - return scale_to_meter - - -def get_scale_factor_to_meter(units: str) -> float: - try: - unit_scale = { - "meters": 1.0, - "centimeters": 0.01, - "millimeters": 0.001, - "inches": 0.0254, - "feet": 0.3048, - "kilometers": 1000.0, - "mm": 0.001, - "cm": 0.01, - "m": 1.0, - "km": 1000.0, - "in": 0.0254, - "ft": 0.3048, - "yd": 0.9144, - "mi": 1609.340, - } - if ( - units is not None - and isinstance(units, str) - and units.lower() in unit_scale.keys() - ): - return unit_scale[units] - logToUser( - f"Units {units} are not supported. Meters will be applied by default.", - level=1, - func=inspect.stack()[0][3], - ) - return 1.0 - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def validateAttributeName(name: str, fieldnames: List[str]) -> str: try: new_list = [x for x in fieldnames if x != name] diff --git a/speckle/converter/utils.py b/speckle/converter/utils.py new file mode 100644 index 00000000..7f5a1c6a --- /dev/null +++ b/speckle/converter/utils.py @@ -0,0 +1,52 @@ +import inspect +from speckle.utils.panel_logging import logToUser + + +def get_scale_factor(units: str, dataStorage) -> float: + scale_to_meter = get_scale_factor_to_meter(units) + if dataStorage is not None: + scale_back = scale_to_meter / get_scale_factor_to_meter( + dataStorage.currentUnits + ) + return scale_back + else: + return scale_to_meter + + +def get_scale_factor_to_meter(units: str) -> float: + try: + unit_scale = { + "meters": 1.0, + "centimeters": 0.01, + "millimeters": 0.001, + "inches": 0.0254, + "feet": 0.3048, + "kilometers": 1000.0, + "mm": 0.001, + "cm": 0.01, + "m": 1.0, + "km": 1000.0, + "in": 0.0254, + "ft": 0.3048, + "yd": 0.9144, + "mi": 1609.340, + } + if ( + units is not None + and isinstance(units, str) + and units.lower() in unit_scale.keys() + ): + return unit_scale[units] + logToUser( + f"Units {units} are not supported. Meters will be applied by default.", + level=1, + func=inspect.stack()[0][3], + ) + return 1.0 + except Exception as e: + logToUser( + f"{e}. Meters will be applied by default.", + level=2, + func=inspect.stack()[0][3], + ) + return 1.0 diff --git a/tests/unit/geometry_tests/conftest.py b/tests/unit/geometry_tests/conftest.py index bc7d0136..657cee5c 100644 --- a/tests/unit/geometry_tests/conftest.py +++ b/tests/unit/geometry_tests/conftest.py @@ -4,7 +4,7 @@ from specklepy_qt_ui.qt_ui.DataStorage import DataStorage from specklepy.objects.encoding import CurveTypeEncoding -from specklepy.objects.geometry import Arc, Line, Point, Plane, Polycurve, Vector +from specklepy.objects.geometry import Arc, Line, Mesh, Point, Plane, Polycurve, Vector @pytest.fixture() @@ -34,7 +34,7 @@ def arc(): @pytest.fixture() def polycurve(): - polycurve = Polycurve() + poly = Polycurve() segm1 = Line.from_list( [CurveTypeEncoding.Line.value, -10, 0, 0, -5, 0, 0, -5, 0, 0, 3] ) @@ -43,5 +43,14 @@ def polycurve(): ) # segm2 = Polyline() # segm3 = Arc() - polycurve.segments = [segm1, segm2] # , segm3] - return polycurve + poly.segments = [segm1, segm2] # , segm3] + return poly + + +@pytest.fixture() +def mesh(): + mesh_obj = Mesh().create( + vertices=[0, 0, 0, 100, 0, 0, 0, 100, 0], faces=[3, 0, 1, 2] + ) + mesh.units = "m" + return mesh_obj diff --git a/tests/unit/geometry_tests/test_mesh.py b/tests/unit/geometry_tests/test_mesh.py index e69de29b..c64ccd66 100644 --- a/tests/unit/geometry_tests/test_mesh.py +++ b/tests/unit/geometry_tests/test_mesh.py @@ -0,0 +1,23 @@ +from speckle.converter.geometry.mesh import ( + deconstructSpeckleMesh, + fill_multi_mesh_parts, + writeMeshToShp, + fill_mesh_parts, + constructMeshFromRaster, + constructMesh, +) +import inspect +from typing import List, Tuple +import pathlib + +import shapefile +from specklepy.objects.geometry import Mesh, Point +from specklepy.objects.other import RenderMaterial + + +def test_deconstructSpeckleMesh(mesh, data_storage): + result = deconstructSpeckleMesh(mesh, data_storage) + assert isinstance(result, Tuple) + assert len(result) == 2 + assert isinstance(result[0], list) and isinstance(result[1], list) + diff --git a/tests/unit/geometry_tests/test_point.py b/tests/unit/geometry_tests/test_point.py index afa92cc0..e5478986 100644 --- a/tests/unit/geometry_tests/test_point.py +++ b/tests/unit/geometry_tests/test_point.py @@ -1,21 +1,11 @@ -r""" from speckle.converter.geometry.point import ( - pointToSpeckle, - transformSpecklePt, - pointToNativeWithoutTransforms, - pointToNative, - applyTransformMatrix, scalePointToNative, ) +from specklepy.objects.geometry import Point -def test_applyOffsetsRotation(): - x = 0 - y = 0 - dataStorage = None - assert applyOffsetsRotation(x, y, dataStorage) == (None, None) -""" - - -def test(): - assert 0 == 0 +def test_scalePointToNative(data_storage): + pt = Point.from_list([0, 4, 0]) + pt.units = "m" + result = scalePointToNative(pt, pt.units, data_storage) + assert isinstance(result, Point) diff --git a/tests/unit/geometry_tests/test_utils.py b/tests/unit/geometry_tests/test_utils.py index ce2055b5..0383cdb8 100644 --- a/tests/unit/geometry_tests/test_utils.py +++ b/tests/unit/geometry_tests/test_utils.py @@ -1,4 +1,5 @@ import math +import numpy as np from typing import List, Tuple import pytest @@ -22,7 +23,9 @@ getArcRadianAngle, getArcAngles, getArcNormal, - applyOffsetsRotation, + apply_pt_offsets_rotation_on_send, + transform_speckle_pt_on_receive, + apply_pt_transform_matrix, ) from specklepy.objects import Base @@ -284,22 +287,39 @@ def test_getArcNormal_basic(arc, data_storage): assert isinstance(result, Vector) -def test_applyOffsetsRotation_basic(data_storage): +def test_apply_pt_offsets_rotation_on_send_basic(data_storage): x = 0.0 y = 10.0 - result = applyOffsetsRotation(x, y, data_storage) + result = apply_pt_offsets_rotation_on_send(x, y, data_storage) assert isinstance(result, Tuple) assert len(result) == 2 assert isinstance(result[0], float) and isinstance(result[1], float) assert result[0] == x and result[1] == y -def test_applyOffsetsRotation_rotate(data_storage): +def test_apply_pt_offsets_rotation_on_send_rotate(data_storage): x = 0.0 y = 10.0 data_storage.crs_rotation = 180 - result = applyOffsetsRotation(x, y, data_storage) + result = apply_pt_offsets_rotation_on_send(x, y, data_storage) assert isinstance(result, Tuple) assert len(result) == 2 assert isinstance(result[0], float) and isinstance(result[1], float) assert (result[0] - x) < 0.0000001 and (result[1] + y) < 0.0000001 + + +def test_transform_speckle_pt_on_receive_rotate(data_storage): + pt = Point.from_list([0, 4, 0]) + data_storage.crs_rotation = 180 + result = transform_speckle_pt_on_receive(pt, data_storage) + assert isinstance(result, Point) + assert (result.x - pt.x) < 0.0000001 and (result.y + pt.y) < 0.0000001 + + +def test_apply_pt_transform_matrix(data_storage): + pt = Point.from_list([0, 4, 0]) + matrixList = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) + matrix = np.matrix(matrixList).reshape(4, 4) + data_storage.matrix = matrix + result = apply_pt_transform_matrix(pt, data_storage) + assert isinstance(result, Point)