diff --git a/requirements.txt b/requirements.txt index 0e02a46..473dc36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -specklepy==2.9.1 +specklepy==2.17.17 panda3d==1.10.11 diff --git a/scripts/debugging_panel.py b/scripts/debugging_panel.py index 4681545..d80bc32 100644 --- a/scripts/debugging_panel.py +++ b/scripts/debugging_panel.py @@ -6,7 +6,7 @@ import os try: from speckle.speckle.converter.layers.CRS import CRS - from speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer + from specklepy.objects.GIS.layers import Layer, VectorLayer, RasterLayer except: from speckle_toolbox.esri.toolboxes.speckle.converter.layers.CRS import CRS from speckle_toolbox.esri.toolboxes.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/__init__.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/feature_conversions.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/feature_conversions.py new file mode 100644 index 0000000..25d829e --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/feature_conversions.py @@ -0,0 +1,1138 @@ +from datetime import datetime +import inspect +import math +import os +from typing import List, Union + +import numpy as np + +import scipy as sp +from speckle.speckle.plugin_utils.helpers import ( + findOrCreatePath, + get_scale_factor_to_meter, +) + +# from speckle.speckle.converter.geometry.conversions import transform +from speckle.speckle.converter.geometry.conversions import ( + convertToNative, + convertToNativeMulti, + convertToSpeckle, +) +from speckle.speckle.converter.geometry.mesh import constructMeshFromRaster +from speckle.speckle.converter.geometry.utils import apply_pt_offsets_rotation_on_send +from speckle.speckle.utils.panel_logging import logToUser +from speckle.speckle.converter.features.utils import updateFeat +from specklepy.objects.GIS.geometry import ( + GisRasterElement, + GisPolygonGeometry, + GisNonGeometryElement, + GisTopography, +) + +from specklepy.objects import Base + +try: + from qgis._core import ( + QgsCoordinateTransform, + Qgis, + QgsPointXY, + QgsGeometry, + QgsRasterBandStats, + QgsFeature, + QgsFields, + QgsField, + QgsVectorLayer, + QgsRasterLayer, + QgsCoordinateReferenceSystem, + QgsProject, + QgsUnitTypes, + ) + from osgeo import ( # # C:\Program Files\QGIS 3.20.2\apps\Python39\Lib\site-packages\osgeo + gdal, + osr, + ) + from PyQt5.QtCore import QVariant, QDate, QDateTime +except ModuleNotFoundError: + pass + + +def featureToSpeckle( + fieldnames: List[str], + f: "QgsFeature", + geomType, + selectedLayer: Union["QgsVectorLayer", "QgsRasterLayer"], + dataStorage, +): + if dataStorage is None: + return + units = dataStorage.currentUnits + new_report = {"obj_type": "", "errors": ""} + iterations = 0 + try: + geom = None + + if geomType == "None": + geom = GisNonGeometryElement() + new_report = {"obj_type": geom.speckle_type, "errors": ""} + else: + # Try to extract geometry + skipped_msg = f"'{geomType}' feature skipped due to invalid geometry" + try: + geom, iterations = convertToSpeckle(f, selectedLayer, dataStorage) + if geom is not None and geom != "None": + if not isinstance(geom.geometry, List): + logToUser( + "Geometry not in list format", + level=2, + func=inspect.stack()[0][3], + ) + return None + + all_errors = "" + for g in geom.geometry: + if g is None or g == "None": + all_errors += skipped_msg + ", " + logToUser(skipped_msg, level=2, func=inspect.stack()[0][3]) + elif isinstance(g, GisPolygonGeometry): + if len(g.displayValue) == 0: + all_errors += ( + "Polygon converted, but display mesh not generated" + + ", " + ) + logToUser( + "Polygon converted, but display mesh not generated", + level=1, + func=inspect.stack()[0][3], + ) + elif iterations is not None and iterations > 0: + all_errors += ( + "Polygon display mesh is simplified" + ", " + ) + logToUser( + "Polygon display mesh is simplified", + level=1, + func=inspect.stack()[0][3], + ) + + if len(geom.geometry) == 0: + all_errors = "No geometry converted" + new_report.update( + {"obj_type": geom.speckle_type, "errors": all_errors} + ) + + else: # geom is None + new_report = {"obj_type": "", "errors": skipped_msg} + logToUser(skipped_msg, level=2, func=inspect.stack()[0][3]) + geom = GisNonGeometryElement() + except Exception as error: + new_report = { + "obj_type": "", + "errors": "Error converting geometry: " + str(error), + } + logToUser( + "Error converting geometry: " + str(error), + level=2, + func=inspect.stack()[0][3], + ) + + attributes = Base() + for name in fieldnames: + corrected = validateAttributeName(name, fieldnames) + f_val = f[name] + if f_val == "NULL" or f_val is None or str(f_val) == "NULL": + f_val = None + if isinstance(f[name], list): + x = "" + for i, attr in enumerate(f[name]): + if i == 0: + x += str(attr) + else: + x += ", " + str(attr) + f_val = x + attributes[corrected] = f_val + + # if geom is not None and geom!="None": + geom.attributes = attributes + + dataStorage.latestActionFeaturesReport[ + len(dataStorage.latestActionFeaturesReport) - 1 + ].update(new_report) + return geom + + except Exception as e: + new_report.update({"errors": e}) + dataStorage.latestActionFeaturesReport[ + len(dataStorage.latestActionFeaturesReport) - 1 + ].update(new_report) + logToUser(e, level=2, func=inspect.stack()[0][3]) + return geom + + +def rasterFeatureToSpeckle( + selectedLayer: "QgsRasterLayer", + projectCRS: "QgsCoordinateReferenceSystem", + project: "QgsProject", + plugin, +) -> Base: + dataStorage = plugin.dataStorage + if dataStorage is None: + return + + b = GisRasterElement(units=dataStorage.currentUnits) + try: + + from speckle.speckle.converter.layers.utils import ( + get_raster_stats, + getArrayIndicesFromXY, + getElevationLayer, + getHeightWithRemainderFromArray, + getRasterArrays, + getVariantFromValue, + getXYofArrayPoint, + isAppliedLayerTransformByKeywords, + validateAttributeName, + ) + + terrain_transform = False + texture_transform = False + # height_list = rasterBandVals[0] + terrain_transform = isAppliedLayerTransformByKeywords( + selectedLayer, ["elevation", "mesh"], ["texture"], dataStorage + ) + texture_transform = isAppliedLayerTransformByKeywords( + selectedLayer, ["texture"], [], dataStorage + ) + if terrain_transform is True or texture_transform is True: + b = GisTopography(units=dataStorage.currentUnits) + + rasterBandCount = selectedLayer.bandCount() + rasterBandNames = [] + rasterDimensions = [selectedLayer.width(), selectedLayer.height()] + # if rasterDimensions[0]*rasterDimensions[1] > 1000000 : + # logToUser("Large layer: ", level = 1, func = inspect.stack()[0][3]) + + ds = gdal.Open(selectedLayer.source(), gdal.GA_ReadOnly) + if ds is None: + return None + + originX = ds.GetGeoTransform()[0] + originY = ds.GetGeoTransform()[3] + rasterOriginPoint = QgsPointXY(originX, originY) + rasterResXY = [float(ds.GetGeoTransform()[1]), float(ds.GetGeoTransform()[5])] + rasterWkt = ds.GetProjection() + rasterProj = ( + QgsCoordinateReferenceSystem.fromWkt(rasterWkt) + .toProj() + .replace(" +type=crs", "") + ) + rasterBandNoDataVal = [] + rasterBandMinVal = [] + rasterBandMaxVal = [] + rasterBandVals = [] + + # Try to extract geometry + reprojectedPt = QgsGeometry.fromPointXY(QgsPointXY()) + try: + reprojectedPt = rasterOriginPoint + if selectedLayer.crs() != projectCRS: + reprojectedPt = transform.transform( + project, rasterOriginPoint, selectedLayer.crs(), projectCRS + ) + except Exception as error: + # logToUser("Error converting point geometry: " + str(error), level = 2, func = inspect.stack()[0][3]) + logToUser("Error converting point geometry: " + str(error), level=2) + + for index in range(rasterBandCount): + rasterBandNames.append(selectedLayer.bandName(index + 1)) + rb = ds.GetRasterBand(index + 1) + valMin = ( + selectedLayer.dataProvider() + .bandStatistics(index + 1, QgsRasterBandStats.All) + .minimumValue + ) + valMax = ( + selectedLayer.dataProvider() + .bandStatistics(index + 1, QgsRasterBandStats.All) + .maximumValue + ) + bandVals = rb.ReadAsArray().tolist() + + bandValsFlat = [] + [bandValsFlat.extend(item) for item in bandVals] + # look at mesh chunking + + const = float(-1 * math.pow(10, 30)) + defaultNoData = rb.GetNoDataValue() + # print(type(rb.GetNoDataValue())) + + # check whether NA value is too small or raster has too small values + # assign min value of an actual list; re-assign NA val; replace list items to new NA val + try: + # create "safe" fake NA value; replace extreme values with it + fakeNA = max(bandValsFlat) + 1 + bandValsFlatFake = [ + fakeNA if val <= const else val for val in bandValsFlat + ] # replace all values corresponding to NoData value + + # if default NA value is too small + if ( + isinstance(defaultNoData, float) or isinstance(defaultNoData, int) + ) and defaultNoData < const: + # find and rewrite min of actual band values; create new NA value + valMin = min(bandValsFlatFake) + noDataValNew = valMin - 1000 # use new adequate value + rasterBandNoDataVal.append(noDataValNew) + # replace fake NA with new NA + bandValsFlat = [ + noDataValNew if val == fakeNA else val + for val in bandValsFlatFake + ] # replace all values corresponding to NoData value + + # if default val unaccessible and minimum val is too small + elif ( + isinstance(defaultNoData, str) or defaultNoData is None + ) and valMin < const: # if there are extremely small values but default NA unaccessible + noDataValNew = valMin + rasterBandNoDataVal.append(noDataValNew) + # replace fake NA with new NA + bandValsFlat = [ + noDataValNew if val == fakeNA else val + for val in bandValsFlatFake + ] # replace all values corresponding to NoData value + # last, change minValto actual one + valMin = min(bandValsFlatFake) + + else: + rasterBandNoDataVal.append(rb.GetNoDataValue()) + + except: + rasterBandNoDataVal.append(rb.GetNoDataValue()) + + rasterBandVals.append(bandValsFlat) + rasterBandMinVal.append(valMin) + rasterBandMaxVal.append(valMax) + b["@(10000)" + selectedLayer.bandName(index + 1) + "_values"] = ( + bandValsFlat # [0:int(max_values/rasterBandCount)] + ) + + b.x_resolution = rasterResXY[0] + b.y_resolution = rasterResXY[1] + b.x_size = rasterDimensions[0] + b.y_size = rasterDimensions[1] + b.x_origin, b.y_origin = apply_pt_offsets_rotation_on_send( + reprojectedPt.x(), reprojectedPt.y(), dataStorage + ) + b.band_count = rasterBandCount + b.band_names = rasterBandNames + b.noDataValue = rasterBandNoDataVal + # creating a mesh + count = 0 + rendererType = selectedLayer.renderer().type() + + xy_list = [] + z_list = [] + # print(rendererType) + # identify symbology type and if Multiband, which band is which color + + ############################################################# + + elevationLayer = None + elevationProj = None + if texture_transform is True: + elevationLayer = getElevationLayer(dataStorage) + elif terrain_transform is True: + elevationLayer = selectedLayer + + if elevationLayer is not None: + settings_elevation_layer = get_raster_stats(elevationLayer) + ( + elevationResX, + elevationResY, + elevationOriginX, + elevationOriginY, + elevationSizeX, + elevationSizeY, + elevationWkt, + elevationProj, + ) = settings_elevation_layer + + # reproject the elevation layer + if ( + elevationProj is not None + and rasterProj is not None + and elevationProj != rasterProj + ): + try: + p = ( + os.path.expandvars(r"%LOCALAPPDATA%") + + "\\Temp\\Speckle_QGIS_temp\\" + + datetime.now().strftime("%Y-%m-%d_%H-%M") + ) + findOrCreatePath(p) + path = p + out = p + "\\out.tif" + gdal.Warp( + out, + elevationLayer.source(), + dstSRS=selectedLayer.crs().authid(), + xRes=elevationResX, + yRes=elevationResY, + ) + + elevationLayer = QgsRasterLayer(out, "", "gdal") + settings_elevation_layer = get_raster_stats(elevationLayer) + ( + elevationResX, + elevationResY, + elevationOriginX, + elevationOriginY, + elevationSizeX, + elevationSizeY, + elevationWkt, + elevationProj, + ) = settings_elevation_layer + except Exception as e: + logToUser(f"Reprojection did not succeed: {e}", level=0) + elevation_arrays, all_mins, all_maxs, all_na = getRasterArrays( + elevationLayer + ) + array_band = elevation_arrays[0] + + height_array = np.where( + (array_band < const) + | (array_band > -1 * const) + | (array_band == all_na[0]), + np.nan, + array_band, + ) + try: + height_array = height_array.astype(float) + except: + try: + arr = [] + for row in height_array: + new_row = [] + for item in row: + try: + new_row.append(float(item)) + except: + new_row.append(np.nan) + arr.append(new_row) + height_array = np.array(arr).astype(float) + except: + height_array = height_array[ + [isinstance(i, float) for i in height_array] + ] + else: + elevation_arrays = all_mins = all_maxs = all_na = None + elevationResX = elevationResY = elevationOriginX = elevationOriginY = ( + elevationSizeX + ) = elevationSizeY = elevationWkt = None + height_array = None + + largeTransform = False + if texture_transform is True and elevationLayer is None: + logToUser( + f"Elevation layer is not found. Texture transformation for layer '{selectedLayer.name()}' will not be applied", + level=1, + plugin=plugin.dockwidget, + ) + elif ( + texture_transform is True + and rasterDimensions[1] * rasterDimensions[0] >= 10000 + and elevationProj is not None + and rasterProj is not None + and elevationProj != rasterProj + ): + # warning if >= 100x100 raster is being projected to an elevation with different CRS + logToUser( + f"Texture transformation for the layer '{selectedLayer.name()}' might take a while 🕒\nTip: reproject one of the layers (texture or elevation) to the other layer's CRS. When both layers have the same CRS, texture transformation will be much faster.", + level=0, + plugin=plugin.dockwidget, + ) + largeTransform = True + elif ( + texture_transform is True + and rasterDimensions[1] * rasterDimensions[0] >= 250000 + ): + # warning if >= 500x500 raster is being projected to any elevation + logToUser( + f"Texture transformation for the layer '{selectedLayer.name()}' might take a while 🕒", + level=0, + plugin=plugin.dockwidget, + ) + largeTransform = True + ############################################################ + faces_array = [] + colors_array = [] + vertices_array = [] + array_z = [] # size is large by 1 than the raster size, in both dimensions + time0 = datetime.now() + for v in range(rasterDimensions[1]): # each row, Y + if largeTransform is True: + if v == int(rasterDimensions[1] / 20): + logToUser( + f"Converting layer '{selectedLayer.name()}': 5%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] / 10): + logToUser( + f"Converting layer '{selectedLayer.name()}': 10%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] / 5): + logToUser( + f"Converting layer '{selectedLayer.name()}': 20%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] * 2 / 5): + logToUser( + f"Converting layer '{selectedLayer.name()}': 40%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] * 3 / 5): + logToUser( + f"Converting layer '{selectedLayer.name()}': 60%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] * 4 / 5): + logToUser( + f"Converting layer '{selectedLayer.name()}': 80%...", + level=0, + plugin=plugin.dockwidget, + ) + elif v == int(rasterDimensions[1] * 9 / 10): + logToUser( + f"Converting layer '{selectedLayer.name()}': 90%...", + level=0, + plugin=plugin.dockwidget, + ) + vertices = [] + faces = [] + colors = [] + row_z = [] + row_z_bottom = [] + for h in range(rasterDimensions[0]): # item in a row, X + pt1 = QgsPointXY( + rasterOriginPoint.x() + h * rasterResXY[0], + rasterOriginPoint.y() + v * rasterResXY[1], + ) + pt2 = QgsPointXY( + rasterOriginPoint.x() + h * rasterResXY[0], + rasterOriginPoint.y() + (v + 1) * rasterResXY[1], + ) + pt3 = QgsPointXY( + rasterOriginPoint.x() + (h + 1) * rasterResXY[0], + rasterOriginPoint.y() + (v + 1) * rasterResXY[1], + ) + pt4 = QgsPointXY( + rasterOriginPoint.x() + (h + 1) * rasterResXY[0], + rasterOriginPoint.y() + v * rasterResXY[1], + ) + # first, get point coordinates with correct position and resolution, then reproject each: + if selectedLayer.crs() != projectCRS: + pt1 = transform.transform( + project, src=pt1, crsSrc=selectedLayer.crs(), crsDest=projectCRS + ) + pt2 = transform.transform( + project, src=pt2, crsSrc=selectedLayer.crs(), crsDest=projectCRS + ) + pt3 = transform.transform( + project, src=pt3, crsSrc=selectedLayer.crs(), crsDest=projectCRS + ) + pt4 = transform.transform( + project, src=pt4, crsSrc=selectedLayer.crs(), crsDest=projectCRS + ) + + z1 = z2 = z3 = z4 = 0 + index1 = index1_0 = None + + ############################################################# + if ( + terrain_transform is True or texture_transform is True + ) and height_array is not None: + if texture_transform is True: # texture + # index1: index on y-scale + posX, posY = getXYofArrayPoint( + ( + rasterResXY[0], + rasterResXY[1], + originX, + originY, + ), + h, + v, + selectedLayer, + elevationLayer, + dataStorage, + ) + + index1, index2, remainder1, remainder2 = getArrayIndicesFromXY( + ( + elevationResX, + elevationResY, + elevationOriginX, + elevationOriginY, + elevationSizeX, + elevationSizeY, + elevationWkt, + elevationProj, + ), + posX, + posY, + ) + ( + index1_0, + index2_0, + remainder1_0, + remainder2_0, + ) = getArrayIndicesFromXY( + ( + elevationResX, + elevationResY, + elevationOriginX, + elevationOriginY, + elevationSizeX, + elevationSizeY, + elevationWkt, + elevationProj, + ), + posX - rasterResXY[0], + posY - rasterResXY[1], + ) + else: # elevation + index1 = v + index1_0 = v - 1 + index2 = h + index2_0 = h - 1 + + if index1 is None or index1_0 is None: + # count += 4 + # continue # skip the pixel + z1 = z2 = z3 = z4 = np.nan + else: + # top vertices ###################################### + try: + z1 = z_list[xy_list.index((pt1.x(), pt1.y()))] + except: + if index1 > 0 and index2 > 0: + z1 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1_0, index2_0 + ) + elif index1 > 0: + z1 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1_0, index2 + ) + elif index2 > 0: + z1 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2_0 + ) + else: + z1 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2 + ) + + if z1 is not None: + z_list.append(z1) + xy_list.append((pt1.x(), pt1.y())) + + #################### z4 + try: + z4 = z_list[xy_list.index((pt4.x(), pt4.y()))] + except: + if index1 > 0: + z4 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1_0, index2 + ) + else: + z4 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2 + ) + + if z4 is not None: + z_list.append(z4) + xy_list.append((pt4.x(), pt4.y())) + + # bottom vertices ###################################### + z3 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2 + ) + if z3 is not None: + z_list.append(z3) + xy_list.append((pt3.x(), pt3.y())) + + try: + z2 = z_list[xy_list.index((pt2.x(), pt2.y()))] + except: + if index2 > 0: + z2 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2_0 + ) + else: + z2 = getHeightWithRemainderFromArray( + height_array, texture_transform, index1, index2 + ) + if z2 is not None: + z_list.append(z2) + xy_list.append((pt2.x(), pt2.y())) + + ############################################## + + max_len = rasterDimensions[0] * 4 + 4 + if len(z_list) > max_len: + z_list = z_list[len(z_list) - max_len :] + xy_list = xy_list[len(xy_list) - max_len :] + + ### list to smoothen later: + if h == 0: + row_z.append(z1) + row_z_bottom.append(z2) + row_z.append(z4) + row_z_bottom.append(z3) + + ######################################################## + 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] + ) ## add 4 points + current_vertices = ( + v * rasterDimensions[0] * 4 + h * 4 + ) # len(np.array(faces_array).flatten()) * 4 / 5 + faces.append( + [ + 4, + current_vertices, + current_vertices + 1, + current_vertices + 2, + current_vertices + 3, + ] + ) + + # color vertices according to QGIS renderer + alpha = 255 + color = (alpha << 24) + (0 << 16) + (0 << 8) + 0 + noValColor: tuple[int] = ( + selectedLayer.renderer().nodataColor().getRgb() + ) # RGB or RGBA, 3 or 4 values + + colorLayer = selectedLayer + currentRasterBandCount = rasterBandCount + + if ( + (terrain_transform is True or texture_transform is True) + and height_array is not None + and (index1 is None or index1_0 is None) + ): # transparent color + alpha = 0 + color = (alpha << 24) + (0 << 16) + (0 << 8) + 0 + elif rendererType == "multibandcolor": + valR = 0 + valG = 0 + valB = 0 + bandRed = int(colorLayer.renderer().redBand()) + bandGreen = int(colorLayer.renderer().greenBand()) + bandBlue = int(colorLayer.renderer().blueBand()) + + for k in range(currentRasterBandCount): + valRange = rasterBandMaxVal[k] - rasterBandMinVal[k] + if valRange == 0: + colorVal = 0 + elif ( + rasterBandVals[k][int(count / 4)] == rasterBandNoDataVal[k] + ): + colorVal = 0 + alpha = 0 + # break + else: + colorVal = int( + ( + rasterBandVals[k][int(count / 4)] + - rasterBandMinVal[k] + ) + / valRange + * 255 + ) + + if k + 1 == bandRed: + valR = colorVal + if k + 1 == bandGreen: + valG = colorVal + if k + 1 == bandBlue: + valB = colorVal + + color = (alpha << 24) + (valR << 16) + (valG << 8) + valB + + elif rendererType == "paletted": + bandIndex = colorLayer.renderer().band() - 1 # int + # if textureLayer is not None: + # value = texture_arrays[bandIndex][index1][index2] + # else: + value = rasterBandVals[bandIndex][ + int(count / 4) + ] # find in the list and match with color + + rendererClasses = colorLayer.renderer().classes() + for c in range(len(rendererClasses) - 1): + if ( + value >= rendererClasses[c].value + and value <= rendererClasses[c + 1].value + ): + rgb = rendererClasses[c].color.getRgb() + color = ( + (255 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] + ) + break + if value == rasterBandNoDataVal[bandIndex]: + alpha = 0 + color = (alpha << 24) + (0 << 16) + (0 << 8) + 0 + + elif rendererType == "singlebandpseudocolor": + bandIndex = colorLayer.renderer().band() - 1 # int + # if textureLayer is not None: + # value = texture_arrays[bandIndex][index1][index2] + # else: + value = rasterBandVals[bandIndex][ + int(count / 4) + ] # find in the list and match with color + + rendererClasses = colorLayer.renderer().legendSymbologyItems() + for c in range(len(rendererClasses) - 1): + if value >= float(rendererClasses[c][0]) and value <= float( + rendererClasses[c + 1][0] + ): + rgb = rendererClasses[c][1].getRgb() + color = ( + (255 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2] + ) + break + if value == rasterBandNoDataVal[bandIndex]: + alpha = 0 + color = (alpha << 24) + (0 << 16) + (0 << 8) + 0 + + else: + if rendererType == "singlebandgray": + bandIndex = colorLayer.renderer().grayBand() - 1 + elif rendererType == "hillshade": + bandIndex = colorLayer.renderer().band() - 1 + elif rendererType == "contour": + try: + bandIndex = colorLayer.renderer().inputBand() - 1 + except: + try: + bandIndex = colorLayer.renderer().band() - 1 + except: + bandIndex = 0 + else: # e.g. single band data + bandIndex = 0 + + value = rasterBandVals[bandIndex][int(count / 4)] + if ( + rasterBandMinVal[bandIndex] + <= value + <= rasterBandMaxVal[bandIndex] + ): + # REMAP band values to (0,255) range + valRange = ( + rasterBandMaxVal[bandIndex] - rasterBandMinVal[bandIndex] + ) + if valRange == 0: + colorVal = 0 + else: + colorVal = int( + ( + rasterBandVals[bandIndex][int(count / 4)] + - rasterBandMinVal[bandIndex] + ) + / valRange + * 255 + ) + color = ( + (alpha << 24) + + (colorVal << 16) + + (colorVal << 8) + + colorVal + ) + elif value == rasterBandNoDataVal[bandIndex]: + alpha = 0 + color = (alpha << 24) + (0 << 16) + (0 << 8) + 0 + + colors.append([color, color, color, color]) + count += 4 + + # after each row + vertices_array.append(vertices) + faces_array.append(faces) + colors_array.append(colors) + + if v == 0: + array_z.append(row_z) + array_z.append(row_z_bottom) + + time1 = datetime.now() + # print(f"Time to get Raster: {(time1-time0).total_seconds()} sec") + # after the entire loop + faces_filtered = [] + colors_filtered = [] + vertices_filtered = [] + + ## end of the the table + smooth = False + if terrain_transform is True or texture_transform is True: + smooth = True + if smooth is True and len(row_z) > 2 and len(array_z) > 2: + array_z_nans = np.array(array_z) + + array_z_filled = np.array(array_z) + mask = np.isnan(array_z_filled) + array_z_filled[mask] = np.interp( + np.flatnonzero(mask), np.flatnonzero(~mask), array_z_filled[~mask] + ) + + sigma = 0.8 # for elevation + if texture_transform is True: + sigma = 1 # for texture + + # increase sigma if needed + try: + unitsRaster = QgsUnitTypes.encodeUnit( + selectedLayer.crs().mapUnits() + ) + unitsElevation = QgsUnitTypes.encodeUnit( + elevationLayer.crs().mapUnits() + ) + # print(unitsRaster) + # print(unitsElevation) + resRasterX = get_scale_factor_to_meter(unitsRaster) * rasterResXY[0] + resElevX = get_scale_factor_to_meter(unitsElevation) * elevationResX + # print(resRasterX) + # print(resElevX) + if resRasterX / resElevX >= 2 or resElevX / resRasterX >= 2: + sigma = math.sqrt( + max(resRasterX / resElevX, resElevX / resRasterX) + ) + # print(sigma) + except: + pass + + gaussian_array = sp.ndimage.filters.gaussian_filter( + array_z_filled, sigma, mode="nearest" + ) + + for v in range(rasterDimensions[1]): # each row, Y + for h in range(rasterDimensions[0]): # item in a row, X + if not np.isnan(array_z_nans[v][h]): + vertices_item = vertices_array[v][h] + # print(vertices_item) + vertices_item[2] = gaussian_array[v][h] + vertices_item[5] = gaussian_array[v + 1][h] + vertices_item[8] = gaussian_array[v + 1][h + 1] + vertices_item[11] = gaussian_array[v][h + 1] + vertices_filtered.extend(vertices_item) + + currentFaces = len(faces_filtered) / 5 * 4 + faces_filtered.extend( + [ + 4, + currentFaces, + currentFaces + 1, + currentFaces + 2, + currentFaces + 3, + ] + ) + # print(faces_filtered) + colors_filtered.extend(colors_array[v][h]) + # print(colors_array[v][h]) + else: + faces_filtered = np.array(faces_array).flatten().tolist() + colors_filtered = np.array(colors_array).flatten().tolist() + vertices_filtered = np.array(vertices_array).flatten().tolist() + + # if len(colors)/4*5 == len(faces) and len(colors)*3 == len(vertices): + mesh = constructMeshFromRaster( + vertices_filtered, faces_filtered, colors_filtered, dataStorage + ) + if mesh is not None: + mesh.units = dataStorage.currentUnits + b.displayValue = [mesh] + else: + logToUser( + "Something went wrong. Mesh cannot be created, only raster data will be sent. ", + level=2, + plugin=plugin.dockwidget, + ) + + return b + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None + + +def featureToNative(feature: Base, fields: "QgsFields", dataStorage): + feat = QgsFeature() + # print("___featureToNative") + try: + qgsGeom = None + + if isinstance(feature, GisNonGeometryElement): + pass + else: + try: + speckle_geom = ( + feature.geometry + ) # for QGIS / ArcGIS Layer type from 2.14 + except: + try: + speckle_geom = feature[ + "geometry" + ] # for QGIS / ArcGIS Layer type before 2.14 + except: + speckle_geom = feature # for created in other software + + if not isinstance(speckle_geom, list): + qgsGeom = convertToNative(speckle_geom, dataStorage) + + elif isinstance(speckle_geom, list): + if len(speckle_geom) == 1: + qgsGeom = convertToNative(speckle_geom[0], dataStorage) + elif len(speckle_geom) > 1: + qgsGeom = convertToNativeMulti(speckle_geom, dataStorage) + else: + logToUser( + f"Feature '{feature.id}' does not contain geometry", + level=2, + func=inspect.stack()[0][3], + ) + + if qgsGeom is not None: + feat.setGeometry(qgsGeom) + else: + return None + + feat.setFields(fields) + for field in fields: + name = str(field.name()) + variant = field.type() + # if name == "id": feat[name] = str(feature["applicationId"]) + + try: + value = feature.attributes[name] # fro 2.14 onwards + except: + try: + value = feature[name] + except: + if name == "Speckle_ID": + try: + value = str( + feature["Speckle_ID"] + ) # if GIS already generated this field + except: + try: + value = str(feature["speckle_id"]) + except: + value = str(feature["id"]) + else: + value = None + # logger.logToUser(f"Field {name} not found", Qgis.Warning) + # return None + + if variant == QVariant.String: + value = str(value) + + if isinstance(value, str) and variant == QVariant.Date: # 14 + y, m, d = value.split("(")[1].split(")")[0].split(",")[:3] + value = QDate(int(y), int(m), int(d)) + elif isinstance(value, str) and variant == QVariant.DateTime: + y, m, d, t1, t2 = value.split("(")[1].split(")")[0].split(",")[:5] + value = QDateTime(int(y), int(m), int(d), int(t1), int(t2)) + + if ( + variant == getVariantFromValue(value) + and value != "NULL" + and value != "None" + ): + feat[name] = value + + return feat + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return feat + + +def bimFeatureToNative( + exist_feat: "QgsFeature", + feature: Base, + fields: "QgsFields", + crs, + path: str, + dataStorage, +): + # print("04_________BIM Feature To Native____________") + try: + exist_feat.setFields(fields) + + feat_updated = updateFeat(exist_feat, fields, feature) + # print(fields.toList()) + # print(feature) + # print(feat_updated) + + return feat_updated + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def nonGeomFeatureToNative(feature: Base, fields: "QgsFields", dataStorage): + try: + exist_feat = QgsFeature() + exist_feat.setFields(fields) + feat_updated = updateFeat(exist_feat, fields, feature) + return feat_updated + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def cadFeatureToNative(feature: Base, fields: "QgsFields", dataStorage): + try: + exist_feat = QgsFeature() + try: + speckle_geom = feature["geometry"] # for created in QGIS Layer type + except: + speckle_geom = feature # for created in other software + + if isinstance(speckle_geom, list): + qgsGeom = convertToNativeMulti(speckle_geom, dataStorage) + else: + qgsGeom = convertToNative(speckle_geom, dataStorage) + + if qgsGeom is not None: + exist_feat.setGeometry(qgsGeom) + else: + return + + exist_feat.setFields(fields) + feat_updated = updateFeat(exist_feat, fields, feature) + + return feat_updated + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/utils.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/utils.py new file mode 100644 index 0000000..59584e6 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/features/utils.py @@ -0,0 +1,180 @@ +import inspect +import random +from typing import Any, Union +from speckle.speckle.converter.layers.utils import getVariantFromValue, traverseDict + +from speckle.speckle.utils.panel_logging import logToUser + +from specklepy.objects import Base + + +def addFeatVariant(key, variant, value, f: "QgsFeature") -> "QgsFeature": + try: + feat = f + if variant == 10: + value = str(value) # string + + if value != "NULL" and value != "None": + if variant == getVariantFromValue(value): + feat[key] = value + elif ( + isinstance(value, float) and variant == 4 + ): # float, but expecting Long (integer) + feat[key] = int(value) + elif ( + isinstance(value, int) and variant == 6 + ): # int (longlong), but expecting float + feat[key] = float(value) + else: + feat[key] = None + # print(key); print(value); print(type(value)); print(variant); print(getVariantFromValue(value)) + elif isinstance(variant, int): + feat[key] = None + return feat + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return feat + + +def updateFeat(feat: "QgsFeature", fields: "QgsFields", feature: Base) -> "QgsFeature": + try: + # print("__updateFeat") + all_field_names = fields.names() + for i, key in enumerate(all_field_names): + variant = fields.at(i).type() + try: + if key == "Speckle_ID": + value = str(feature["id"]) + # if key != "parameters": print(value) + feat[key] = value + + feat = addFeatVariant(key, variant, value, feat) + + else: + try: + value = feature[key] + feat = addFeatVariant(key, variant, value, feat) + + except: + value = None + rootName = key.split("_")[0] + # try: # if the root category exists + # if its'a list + if isinstance(feature[rootName], list): + for i in range(len(feature[rootName])): + try: + newF, newVals = traverseDict( + {}, + {}, + rootName + "_" + str(i), + feature[rootName][i], + 1, + ) + for i, (key, value) in enumerate(newVals.items()): + for k, (x, y) in enumerate(newF.items()): + if key == x: + variant = y + break + feat = addFeatVariant(key, variant, value, feat) + except Exception as e: + print(e) + # except: # if not a list + else: + try: + newF, newVals = traverseDict( + {}, {}, rootName, feature[rootName], 1 + ) + for i, (key, value) in enumerate(newVals.items()): + for k, (x, y) in enumerate(newF.items()): + if key == x: + variant = y + break + feat = addFeatVariant(key, variant, value, feat) + except Exception as e: + feat.update({key: None}) + except Exception as e: + feat[key] = None + # feat_sorted = {k: v for k, v in sorted(feat.items(), key=lambda item: item[0])} + # print("_________________end of updating a feature_________________________") + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + return feat + + +def getPolygonFeatureHeight( + feature: "QgsFeature", layer: "QgsVectorLayer", dataStorage: "DataStorage" +) -> Union[int, float, None]: + height = None + ignore = False + if dataStorage.savedTransforms is not None: + for item in dataStorage.savedTransforms: + layer_name = item.split(" -> ")[0].split(" ('")[0] + transform_name = item.split(" -> ")[1].lower() + if "ignore" in transform_name: + ignore = True + + if layer_name == layer.name(): + attribute = None + if " ('" in item: + attribute = item.split(" ('")[1].split("') ")[0] + + if attribute is None and ignore is False: + logToUser( + "Attribute for extrusion not selected", + level=1, + func=inspect.stack()[0][3], + ) + return None + + # print("Apply transform: " + transform_name) + if "extrude" in transform_name and "polygon" in transform_name: + # additional check: + try: + if dataStorage.project.crs().isGeographic(): + return None + except: + return None + + try: + existing_height = float(feature[attribute]) + if ( + existing_height is None or str(feature[attribute]) == "NULL" + ): # if attribute value invalid + if ignore is True: + return None + else: # find approximate value + all_existing_vals = [ + f[attribute] + for f in layer.getFeatures() + if ( + f[attribute] is not None + and ( + isinstance(f[attribute], float) + or isinstance(f[attribute], int) + ) + ) + ] + try: + if len(all_existing_vals) > 5: + height_average = all_existing_vals[ + int(len(all_existing_vals) / 2) + ] + height = random.randint( + height_average - 5, height_average + 5 + ) + else: + height = random.randint(10, 20) + except: + height = random.randint(10, 20) + else: # if acceptable value: reading from existing attribute + height = existing_height + + except: # if no Height attribute + if ignore is True: + height = None + else: + height = random.randint(10, 20) + + return height diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/__init__.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/__init__.py index 6983b75..e69de29 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/__init__.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/__init__.py @@ -1,222 +0,0 @@ - -from regex import F -from specklepy.objects import Base -from specklepy.objects.geometry import Line, Mesh, Point, Polyline, Curve, Arc, Circle, Polycurve, Ellipse - -import arcpy -from typing import Any, List, Union, Sequence - -import inspect - -try: - from speckle.speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle, multiPolygonToSpeckle, polygonToSpeckleMesh - from speckle.speckle.converter.geometry.polyline import arcToNative, ellipseToNative, circleToNative, curveToNative, lineToNative, polycurveToNative, polylineFromVerticesToSpeckle, polylineToNative, polylineToSpeckle - from speckle.speckle.converter.geometry.point import pointToCoord, pointToNative, pointToSpeckle, multiPointToSpeckle - from speckle.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints, multiPolylineToSpeckle - from speckle.speckle.ui.logger import logToUser - from speckle.speckle.converter.geometry.mesh import meshToNative -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.polygon import polygonToNative, polygonToSpeckle, multiPolygonToSpeckle, polygonToSpeckleMesh - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.polyline import arcToNative, ellipseToNative, circleToNative, curveToNative, lineToNative, polycurveToNative, polylineFromVerticesToSpeckle, polylineToNative, polylineToSpeckle - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.point import pointToCoord, pointToNative, pointToSpeckle, multiPointToSpeckle - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints, multiPolylineToSpeckle - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.mesh import meshToNative - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -import numpy as np - -def convertToSpeckle(feature, index: str, layer, geomType, featureType) -> Union[Base, Sequence[Base], None]: - """Converts the provided layer feature to Speckle objects""" - print("___convertToSpeckle____________") - try: - geom = feature - print(geom.isMultipart) # e.g. False - print(geom.partCount) - geomMultiType = geom.isMultipart - hasCurves = feature.hasCurves - - # feature is - - #print(featureType) # e.g. Simple - #print(geomType) # e.g. Polygon - #geomSingleType = (featureType=="Simple") # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem - - if geomType == "Point": #Polygon, Point, Polyline, Multipoint, MultiPatch - for pt in geom: - return pointToSpeckle(pt, feature, layer) - elif geomType == "Polyline": - #if geom.hasCurves: - # geom, feature = curvesToSegments(geom, feature, layer, geomMultiType) - # geomMultiType = geom.isMultipart - # return polylineToSpeckle(geom, feature, layer, geomMultiType) - #else: - if geom.partCount > 1: return multiPolylineToSpeckle(geom, feature, layer, geomMultiType) - else: return polylineToSpeckle(geom, feature, layer, geomMultiType) - elif geomType == "Polygon": - if geom.partCount > 1: return multiPolygonToSpeckle(geom, index, layer, geomMultiType) - else: return polygonToSpeckle(geom, index, layer, geomMultiType) - elif geomType == "Multipoint": - return multiPointToSpeckle(geom, feature, layer, geomMultiType) - elif geomType == "MultiPatch": - return polygonToSpeckleMesh(geom, index, layer, False) - else: - logToUser("Unsupported or invalid geometry in layer " + layer.name, level=1, func = inspect.stack()[0][3]) - return None - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None - - -def convertToNative(base: Base, sr: arcpy.SpatialReference) -> Union[Any, None]: - """Converts any given base object to QgsGeometry.""" - print("___Convert to Native SingleType___") - converted = None - try: - #print(base) - conversions = [ - (Point, pointToNative), - (Line, lineToNative), - (Polyline, polylineToNative), - (Curve, curveToNative), - (Arc, arcToNative), - (Circle, circleToNative), - (Ellipse, ellipseToNative), - #(Mesh, meshToNative), - (Polycurve, polycurveToNative), - (Base, multiPolygonToNative), # temporary solution for polygons (Speckle has no type Polygon yet) - ] - - for conversion in conversions: - if isinstance(base, conversion[0]): - #print(conversion[0]) - converted = conversion[1](base, sr) - break - #print(converted) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return converted - -def multiPointToNative(items: List[Point], sr: arcpy.SpatialReference): - print("___Create MultiPoint") - features = None - try: - all_pts = [] - # example https://pro.arcgis.com/en/pro-app/2.8/arcpy/classes/multipoint.htm - for item in items: - pt = pointToCoord(item) # [x, y, z] - all_pts.append( arcpy.Point(pt[0], pt[1], pt[2]) ) - #print(all_pts) - features = arcpy.Multipoint( arcpy.Array(all_pts) ) - #if len(features)==0: features = None - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return features - -def multiPolylineToNative(items: List[Polyline], sr: arcpy.SpatialReference): - print("_______Drawing Multipolylines____") - poly = None - try: - #print(items) - poly = None - full_array_list = [] - for item in items: # will be 1 item - pointsSpeckle = [] - try: pointsSpeckle = item.as_points() - except: continue - pts = [pointToCoord(pt) for pt in pointsSpeckle] - - if item.closed is True: - pts.append( pointToCoord(item.as_points()[0]) ) - - arr = [arcpy.Point(*coords) for coords in pts] - full_array_list.append(arr) - - poly = arcpy.Polyline( arcpy.Array(full_array_list), sr, has_z=True ) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return poly - -def multiPolygonToNative(items: List[Base], sr: arcpy.SpatialReference): #TODO fix multi features - - print("_______Drawing Multipolygons____") - polygon = None - if not isinstance(items, List): items = [items] - try: - print(items) - full_array_list = [] - - for item_geom in items: # will be 1 item - print(item_geom) - try: item_geom = item_geom["geometry"] - except: item_geom = [item_geom] - for item in item_geom: - #print(item) - #pts = [pointToCoord(pt) for pt in item["boundary"].as_points()] - pointsSpeckle = [] - if isinstance(item["boundary"], Circle) or isinstance(item["boundary"], Arc): - pointsSpeckle = speckleArcCircleToPoints(item["boundary"]) - elif isinstance(item["boundary"], Polycurve): - pointsSpeckle = specklePolycurveToPoints(item["boundary"]) - elif isinstance(item["boundary"], Line): pass - else: - try: pointsSpeckle = item["boundary"].as_points() - except Exception as e: print(e) # if Line - #print(pointsSpeckle) - pts = [pointToCoord(pt) for pt in pointsSpeckle] - #print(pts) - - outer_arr = [arcpy.Point(*coords) for coords in pts] - outer_arr.append(outer_arr[0]) - #print(outer_arr) - geomPart = [] - try: - for void in item["voids"]: - #print(void) - #pts = [pointToCoord(pt) for pt in void.as_points()] - pointsSpeckle = [] - if isinstance(void, Circle) or isinstance(void, Arc): - pointsSpeckle = speckleArcCircleToPoints(void) - elif isinstance(void, Polycurve): - pointsSpeckle = specklePolycurveToPoints(void) - elif isinstance(void, Line): pass - else: - try: pointsSpeckle = void.as_points() - except: pass # if Line - pts = [pointToCoord(pt) for pt in pointsSpeckle] - - inner_arr = [arcpy.Point(*coords) for coords in pts] - inner_arr.append(inner_arr[0]) - geomPart.append(arcpy.Array(inner_arr)) - except Exception as e: print(e) - geomPart.insert(0, arcpy.Array(outer_arr)) - - #print(geomPart) - full_array_list.extend(geomPart) # outlines are written one by one, with no separation to "parts" - #print("end of loop1") - print("end of loop2") - geomPartArray = arcpy.Array(full_array_list) - polygon = arcpy.Polygon(geomPartArray, sr, has_z=True) - - print(polygon) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - return polygon - -def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference): - print("___Convert to Native MultiType___") - try: - first = items[0] - if isinstance(first, Point): - return multiPointToNative(items, sr) - elif isinstance(first, Line) or isinstance(first, Polyline): - return multiPolylineToNative(items, sr) - elif isinstance(first, Base): - try: - if first["boundary"] is not None and first["voids"] is not None: - return multiPolygonToNative(items, sr) - except: return None - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/conversions.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/conversions.py new file mode 100644 index 0000000..8d89870 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/conversions.py @@ -0,0 +1,279 @@ +from regex import F +from specklepy.objects import Base +from specklepy.objects.geometry import ( + Line, + Mesh, + Point, + Polyline, + Curve, + Arc, + Circle, + Polycurve, + Ellipse, +) + +import arcpy +from typing import Any, List, Union, Sequence + +import inspect + +from speckle.speckle.converter.geometry.polygon import ( + polygonToNative, + polygonToSpeckle, + multiPolygonToSpeckle, + polygonToSpeckleMesh, +) +from speckle.speckle.converter.geometry.utils import specklePolycurveToPoints +from speckle.speckle.converter.geometry.polyline import ( + arcToNative, + ellipseToNative, + circleToNative, + curveToNative, + lineToNative, + polycurveToNative, + polylineToNative, + polylineToSpeckle, + speckleArcCircleToPoints, + multiPolylineToSpeckle, +) +from speckle.speckle.converter.geometry.point import ( + pointToCoord, + pointToNative, + pointToSpeckle, + multiPointToSpeckle, +) +from speckle.speckle.utils.panel_logging import logToUser + +import numpy as np + + +def convertToSpeckle( + feature, index: str, layer, geomType, featureType +) -> Union[Base, Sequence[Base], None]: + """Converts the provided layer feature to Speckle objects""" + print("___convertToSpeckle____________") + try: + geom = feature + print(geom.isMultipart) # e.g. False + print(geom.partCount) + geomMultiType = geom.isMultipart + hasCurves = feature.hasCurves + + # feature is + + # print(featureType) # e.g. Simple + # print(geomType) # e.g. Polygon + # geomSingleType = (featureType=="Simple") # Simple,SimpleJunction,SimpleJunction,ComplexEdge,Annotation,CoverageAnnotation,Dimension,RasterCatalogItem + + if geomType == "Point": # Polygon, Point, Polyline, Multipoint, MultiPatch + for pt in geom: + return pointToSpeckle(pt, feature, layer) + elif geomType == "Polyline": + # if geom.hasCurves: + # geom, feature = curvesToSegments(geom, feature, layer, geomMultiType) + # geomMultiType = geom.isMultipart + # return polylineToSpeckle(geom, feature, layer, geomMultiType) + # else: + if geom.partCount > 1: + return multiPolylineToSpeckle(geom, feature, layer, geomMultiType) + else: + return polylineToSpeckle(geom, feature, layer, geomMultiType) + elif geomType == "Polygon": + if geom.partCount > 1: + return multiPolygonToSpeckle(geom, index, layer, geomMultiType) + else: + return polygonToSpeckle(geom, index, layer, geomMultiType) + elif geomType == "Multipoint": + return multiPointToSpeckle(geom, feature, layer, geomMultiType) + elif geomType == "MultiPatch": + return polygonToSpeckleMesh(geom, index, layer, False) + else: + logToUser( + "Unsupported or invalid geometry in layer " + layer.name, + level=1, + func=inspect.stack()[0][3], + ) + return None + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None + + +def convertToNative(base: Base, sr: arcpy.SpatialReference) -> Union[Any, None]: + """Converts any given base object to QgsGeometry.""" + print("___Convert to Native SingleType___") + converted = None + try: + # print(base) + conversions = [ + (Point, pointToNative), + (Line, lineToNative), + (Polyline, polylineToNative), + (Curve, curveToNative), + (Arc, arcToNative), + (Circle, circleToNative), + (Ellipse, ellipseToNative), + # (Mesh, meshToNative), + (Polycurve, polycurveToNative), + ( + Base, + multiPolygonToNative, + ), # temporary solution for polygons (Speckle has no type Polygon yet) + ] + + for conversion in conversions: + if isinstance(base, conversion[0]): + # print(conversion[0]) + converted = conversion[1](base, sr) + break + # print(converted) + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return converted + + +def multiPointToNative(items: List[Point], sr: arcpy.SpatialReference): + print("___Create MultiPoint") + features = None + try: + all_pts = [] + # example https://pro.arcgis.com/en/pro-app/2.8/arcpy/classes/multipoint.htm + for item in items: + pt = pointToCoord(item) # [x, y, z] + all_pts.append(arcpy.Point(pt[0], pt[1], pt[2])) + # print(all_pts) + features = arcpy.Multipoint(arcpy.Array(all_pts)) + # if len(features)==0: features = None + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return features + + +def multiPolylineToNative(items: List[Polyline], sr: arcpy.SpatialReference): + print("_______Drawing Multipolylines____") + poly = None + try: + # print(items) + poly = None + full_array_list = [] + for item in items: # will be 1 item + pointsSpeckle = [] + try: + pointsSpeckle = item.as_points() + except: + continue + pts = [pointToCoord(pt) for pt in pointsSpeckle] + + if item.closed is True: + pts.append(pointToCoord(item.as_points()[0])) + + arr = [arcpy.Point(*coords) for coords in pts] + full_array_list.append(arr) + + poly = arcpy.Polyline(arcpy.Array(full_array_list), sr, has_z=True) + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return poly + + +def multiPolygonToNative( + items: List[Base], sr: arcpy.SpatialReference +): # TODO fix multi features + + print("_______Drawing Multipolygons____") + polygon = None + if not isinstance(items, List): + items = [items] + try: + print(items) + full_array_list = [] + + for item_geom in items: # will be 1 item + print(item_geom) + try: + item_geom = item_geom["geometry"] + except: + item_geom = [item_geom] + for item in item_geom: + # print(item) + # pts = [pointToCoord(pt) for pt in item["boundary"].as_points()] + pointsSpeckle = [] + if isinstance(item["boundary"], Circle) or isinstance( + item["boundary"], Arc + ): + pointsSpeckle = speckleArcCircleToPoints(item["boundary"]) + elif isinstance(item["boundary"], Polycurve): + pointsSpeckle = specklePolycurveToPoints(item["boundary"]) + elif isinstance(item["boundary"], Line): + pass + else: + try: + pointsSpeckle = item["boundary"].as_points() + except Exception as e: + print(e) # if Line + # print(pointsSpeckle) + pts = [pointToCoord(pt) for pt in pointsSpeckle] + # print(pts) + + outer_arr = [arcpy.Point(*coords) for coords in pts] + outer_arr.append(outer_arr[0]) + # print(outer_arr) + geomPart = [] + try: + for void in item["voids"]: + # print(void) + # pts = [pointToCoord(pt) for pt in void.as_points()] + pointsSpeckle = [] + if isinstance(void, Circle) or isinstance(void, Arc): + pointsSpeckle = speckleArcCircleToPoints(void) + elif isinstance(void, Polycurve): + pointsSpeckle = specklePolycurveToPoints(void) + elif isinstance(void, Line): + pass + else: + try: + pointsSpeckle = void.as_points() + except: + pass # if Line + pts = [pointToCoord(pt) for pt in pointsSpeckle] + + inner_arr = [arcpy.Point(*coords) for coords in pts] + inner_arr.append(inner_arr[0]) + geomPart.append(arcpy.Array(inner_arr)) + except Exception as e: + print(e) + geomPart.insert(0, arcpy.Array(outer_arr)) + + # print(geomPart) + full_array_list.extend( + geomPart + ) # outlines are written one by one, with no separation to "parts" + # print("end of loop1") + print("end of loop2") + geomPartArray = arcpy.Array(full_array_list) + polygon = arcpy.Polygon(geomPartArray, sr, has_z=True) + + print(polygon) + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + return polygon + + +def convertToNativeMulti(items: List[Base], sr: arcpy.SpatialReference): + print("___Convert to Native MultiType___") + try: + first = items[0] + if isinstance(first, Point): + return multiPointToNative(items, sr) + elif isinstance(first, Line) or isinstance(first, Polyline): + return multiPolylineToNative(items, sr) + elif isinstance(first, Base): + try: + if first["boundary"] is not None and first["voids"] is not None: + return multiPolygonToNative(items, sr) + except: + return None + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/mesh.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/mesh.py index 5af2386..55214a9 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/mesh.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/mesh.py @@ -17,14 +17,14 @@ try: from speckle.speckle.converter.geometry.point import pointToNative from speckle.speckle.converter.layers.symbology import featureColorfromNativeRenderer from speckle.speckle.converter.layers.utils import get_scale_factor - from speckle.speckle.ui.logger import logToUser + from speckle.speckle.utils.panel_logging import logToUser from speckle.speckle.plugin_utils.helpers import findOrCreatePath except: from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import get_scale_factor from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.point import pointToNative from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.symbology import featureColorfromNativeRenderer from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import get_scale_factor - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.panel_logging import logToUser from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import findOrCreatePath from panda3d.core import Triangulator diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/point.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/point.py index 7aa84bd..5b7b3a2 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/point.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/point.py @@ -4,13 +4,13 @@ from specklepy.objects.geometry import Point import arcpy import inspect +from speckle.speckle.converter.geometry.utils import ( + transform_speckle_pt_on_receive, + apply_pt_transform_matrix, +) -try: - from speckle.speckle.converter.layers.utils import get_scale_factor - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import get_scale_factor - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser +from speckle.speckle.converter.layers.utils import get_scale_factor +from speckle.speckle.utils.panel_logging import logToUser def multiPointToSpeckle(geom, feature, layer, multiType: bool): @@ -56,10 +56,13 @@ def pointToSpeckle(pt, feature, layer): logToUser(str(e), level=2, func = inspect.stack()[0][3]) return None -def pointToNative(pt: Point, sr: arcpy.SpatialReference) -> arcpy.PointGeometry: +def pointToNative(pt: Point, sr: arcpy.SpatialReference, dataStorage) -> arcpy.PointGeometry: """Converts a Speckle Point to QgsPoint""" try: - pt = scalePointToNative(pt, pt.units) + pt = scalePointToNative(pt, pt.units) + new_pt = apply_pt_transform_matrix(new_pt, dataStorage) + newPt = transform_speckle_pt_on_receive(new_pt, dataStorage) + geom = arcpy.PointGeometry(arcpy.Point(pt.x, pt.y, pt.z), sr, has_z = True) #print(geom) return geom @@ -68,6 +71,20 @@ def pointToNative(pt: Point, sr: arcpy.SpatialReference) -> arcpy.PointGeometry: logToUser(str(e), level=2, func = inspect.stack()[0][3]) return None +def pointToNativeWithoutTransforms(pt: Point, sr: arcpy.SpatialReference, dataStorage): + """Converts a Speckle Point to QgsPoint""" + try: + new_pt = scalePointToNative(pt, pt.units, dataStorage) + new_pt = apply_pt_transform_matrix(new_pt, dataStorage) + + geom = arcpy.PointGeometry(arcpy.Point(pt.x, pt.y, pt.z), sr, has_z = True) + #print(geom) + return geom + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return None + def pointToCoord(point: Point) -> List[float]: """Converts a Speckle Point to QgsPoint""" try: diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polygon.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polygon.py index e49242a..701cf99 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polygon.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polygon.py @@ -1,39 +1,30 @@ from typing import List, Sequence, Union -import arcpy +import arcpy import json -from arcpy.arcobjects.arcobjects import SpatialReference +from arcpy.arcobjects.arcobjects import SpatialReference from specklepy.objects import Base from specklepy.objects.geometry import Point, Arc, Circle, Polycurve, Polyline, Line -import inspect - -try: - from speckle.speckle.converter.geometry.mesh import constructMesh, constructMeshFromRaster, meshPartsFromPolygon - from speckle.speckle.converter.geometry.point import pointToCoord, pointToNative - from speckle.speckle.converter.layers.symbology import featureColorfromNativeRenderer - from speckle.speckle.converter.geometry.polyline import (polylineFromVerticesToSpeckle, - circleToSpeckle, - speckleArcCircleToPoints, - curveToSpeckle, - specklePolycurveToPoints - ) - from speckle.speckle.converter.geometry.utils import speckleBoundaryToSpecklePts - from speckle.speckle.ui.logger import logToUser - -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, constructMesh, meshPartsFromPolygon - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.point import pointToCoord, pointToNative - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.symbology import featureColorfromNativeRenderer - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.polyline import (polylineFromVerticesToSpeckle, - circleToSpeckle, - speckleArcCircleToPoints, - curveToSpeckle, - specklePolycurveToPoints - ) - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.utils import speckleBoundaryToSpecklePts - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser +import inspect +from speckle.speckle.converter.geometry.mesh import ( + constructMesh, + constructMeshFromRaster, + meshPartsFromPolygon, +) +from speckle.speckle.converter.geometry.point import pointToCoord, pointToNative +from speckle.speckle.converter.layers.symbology import featureColorfromNativeRenderer +from speckle.speckle.converter.geometry.polyline import ( + polylineFromVerticesToSpeckle, + speckleArcCircleToPoints, + curveToSpeckle, +) +from speckle.speckle.converter.geometry.utils import ( + speckleBoundaryToSpecklePts, + specklePolycurveToPoints, +) +from speckle.speckle.utils.panel_logging import logToUser import math from panda3d.core import Triangulator @@ -42,146 +33,166 @@ from panda3d.core import Triangulator def polygonToSpeckleMesh(geom, index: int, layer, multitype: bool): print("________polygonToSpeckleMesh_____") print(geom) - polygon = Base(units = "m") + polygon = Base(units="m") try: vertices = [] - faces = [] + faces = [] colors = [] existing_vert = 0 - - for i, p in enumerate(geom): - #print("____start enumerate feature") - #print(p) # + + for i, p in enumerate(geom): + # print("____start enumerate feature") + # print(p) # print(p) boundary, voids = getPolyBoundaryVoids(p, layer, multitype) - #print(boundary) - #print(voids) + # print(boundary) + # print(voids) polyBorder = speckleBoundaryToSpecklePts(boundary) - #print(polyBorder) + # print(polyBorder) voidsAsPts = [] for v in voids: pts = speckleBoundaryToSpecklePts(v) voidsAsPts.append(pts) - #print(voidsAsPts) - #print("__to start meshPartsFromPolygon") - total_vert, vertices_x, faces_x, colors_x = meshPartsFromPolygon(polyBorder, voidsAsPts, existing_vert, index, layer) + # print(voidsAsPts) + # print("__to start meshPartsFromPolygon") + total_vert, vertices_x, faces_x, colors_x = meshPartsFromPolygon( + polyBorder, voidsAsPts, existing_vert, index, layer + ) existing_vert += total_vert vertices.extend(vertices_x) faces.extend(faces_x) colors.extend(colors_x) - - #print("Colors: ") - #print(colors) + + # print("Colors: ") + # print(colors) mesh = constructMesh(vertices, faces, colors) - if mesh is not None: - polygon.displayValue = [ mesh ] - else: - logToUser("Mesh creation from Polygon failed. Boundaries will be used as displayValue", level = 1, func = inspect.stack()[0][3]) - return polygon + if mesh is not None: + polygon.displayValue = [mesh] + else: + 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(str(e), level=2, func = inspect.stack()[0][3]) - return None + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None + def getPolyBoundaryVoids(geom, layer, multiType: bool): - #print("__getPolyBoundaryVoids__") + # print("__getPolyBoundaryVoids__") voids: List[Union[None, Polyline, Arc, Line, Polycurve]] = [] - #print(voids) + # print(voids) boundary = None pointList = [] try: - #partsBoundaries = [] - #partsVoids = [] - if multiType is False: # Multipolygon - try: # might be no property "has curves" - if geom.hasCurves: + # partsBoundaries = [] + # partsVoids = [] + if multiType is False: # Multipolygon + try: # might be no property "has curves" + if geom.hasCurves: print("has curves") # geometry SHAPE@ tokens: https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/reading-geometries.htm - print(geom.JSON) + print(geom.JSON) boundary = curveToSpeckle(geom, "Polygon", geom, layer) - else: + else: print("no curves") for p in geom: - for pt in p: - #print(pt) - if pt != None: pointList.append(pt) - boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer) + for pt in p: + # print(pt) + if pt != None: + pointList.append(pt) + boundary = polylineFromVerticesToSpeckle( + pointList, True, geom, layer + ) print(boundary) - except: # for multipatches, no property "has curves" - #print("multipatch") - for pt in geom: - #print(pt) - if pt != None: pointList.append(pt) - boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer) - #print(boundary) - #partsBoundaries.append(boundary) - #partsVoids.append([]) - - else: + except: # for multipatches, no property "has curves" + # print("multipatch") + for pt in geom: + # print(pt) + if pt != None: + pointList.append(pt) + boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer) + # print(boundary) + # partsBoundaries.append(boundary) + # partsVoids.append([]) + + else: print("multi type") for i, p in enumerate(geom): print(p) - for pt in p: - #print(pt) # 284394.58100903 5710688.11602606 NaN NaN - if pt == None and boundary == None: # first break - boundary = polylineFromVerticesToSpeckle(pointList, True, geom, layer) + for pt in p: + # print(pt) # 284394.58100903 5710688.11602606 NaN NaN + if pt == None and boundary == None: # first break + boundary = polylineFromVerticesToSpeckle( + pointList, True, geom, layer + ) pointList = [] - elif pt == None and boundary != None: # breaks btw voids - void = polylineFromVerticesToSpeckle(pointList, True, geom, layer) + elif pt == None and boundary != None: # breaks btw voids + void = polylineFromVerticesToSpeckle( + pointList, True, geom, layer + ) voids.append(void) pointList = [] - elif pt != None: # add points to whatever list (boundary or void) + elif pt != None: # add points to whatever list (boundary or void) pointList.append(pt) - if boundary != None and len(pointList)>0: # remaining polyline + if boundary != None and len(pointList) > 0: # remaining polyline void = polylineFromVerticesToSpeckle(pointList, True, geom, layer) voids.append(void) - + except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return boundary, voids + def multiPolygonToSpeckle(geom, index: str, layer, multiType: bool): print("___MultiPolygon to Speckle____") polygon = [] - try: - #print(enumerate(geom.getPart())) # this method ignores curvature and voids - #print(json.loads(geom.JSON)) - #js = json.loads(geom.JSON)['rings'] - #https://desktop.arcgis.com/en/arcmap/latest/analyze/python/reading-geometries.htm - for i,x in enumerate(geom): # [[x,x,x] - print("Part # " + str(i+1)) + try: + # print(enumerate(geom.getPart())) # this method ignores curvature and voids + # print(json.loads(geom.JSON)) + # js = json.loads(geom.JSON)['rings'] + # https://desktop.arcgis.com/en/arcmap/latest/analyze/python/reading-geometries.htm + for i, x in enumerate(geom): # [[x,x,x] + print("Part # " + str(i + 1)) print(x) boundaryFinished = 0 arrBoundary = [] arrInnerRings = [] - for ptn in x: # arcpy.Point - if ptn is None: + for ptn in x: # arcpy.Point + if ptn is None: boundaryFinished += 1 - arrInnerRings.append([]) # start of new Inner Ring - elif boundaryFinished == 0 and ptn is not None: + arrInnerRings.append([]) # start of new Inner Ring + elif boundaryFinished == 0 and ptn is not None: arrBoundary.append(ptn) - elif boundaryFinished == 1 and ptn is not None: - arrInnerRings[len(arrInnerRings)-1].append(ptn) + elif boundaryFinished == 1 and ptn is not None: + arrInnerRings[len(arrInnerRings) - 1].append(ptn) full_arr = [arrBoundary] + arrInnerRings - #print(full_arr) - poly = arcpy.Polygon(arcpy.Array(full_arr), arcpy.Describe(layer.dataSource).SpatialReference, has_z = True) - #print(poly) # + # print(full_arr) + poly = arcpy.Polygon( + arcpy.Array(full_arr), + arcpy.Describe(layer.dataSource).SpatialReference, + has_z=True, + ) + # print(poly) # polygon.append(polygonToSpeckle(poly, index, layer, poly.isMultipart)) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polygon def polygonToSpeckle(geom, index: int, layer, multitype: bool): """Converts a Polygon to Speckle""" - polygon = Base(units = "m") - try: + polygon = Base(units="m") + try: print("___Polygon to Speckle____") print(geom) @@ -190,87 +201,98 @@ def polygonToSpeckle(geom, index: int, layer, multitype: bool): data = arcpy.Describe(layer.dataSource) sr = data.spatialReference - if boundary is None: return None + if boundary is None: + return None polygon.boundary = boundary polygon.voids = voids - polygon.displayValue = [ boundary ] + voids - #print(boundary) + polygon.displayValue = [boundary] + voids + # print(boundary) - ############# mesh + ############# mesh vertices = [] polyBorder = [] total_vertices = 0 - if isinstance(boundary, Circle) or isinstance(boundary, Arc): - polyBorder = speckleArcCircleToPoints(boundary) - elif isinstance(boundary, Polycurve): - polyBorder = specklePolycurveToPoints(boundary) - #polygon.boundary.displayValue.closed = True - elif isinstance(boundary, Line): pass - elif isinstance(boundary, Polyline): - try: polyBorder = boundary.as_points() - except: pass # if Line - #print(polyBorder) - + if isinstance(boundary, Circle) or isinstance(boundary, Arc): + polyBorder = speckleArcCircleToPoints(boundary) + elif isinstance(boundary, Polycurve): + polyBorder = specklePolycurveToPoints(boundary) + # polygon.boundary.displayValue.closed = True + elif isinstance(boundary, Line): + pass + elif isinstance(boundary, Polyline): + try: + polyBorder = boundary.as_points() + except: + pass # if Line + # print(polyBorder) - if len(polyBorder)>2: # at least 3 points + if len(polyBorder) > 2: # at least 3 points print("make meshes from polygons") - if len(voids) == 0: # if there is a mesh with no voids + if len(voids) == 0: # if there is a mesh with no voids for pt in polyBorder: - if isinstance(pt, Point): pt = pointToNative(pt, sr).getPart() # SR unknown + if isinstance(pt, Point): + pt = pointToNative(pt, sr).getPart() # SR unknown x = pt.X y = pt.Y z = 0 if math.isnan(pt.Z) else pt.Z vertices.extend([x, y, z]) total_vertices += 1 - #print(vertices) + # print(vertices) ran = range(0, total_vertices) faces = [total_vertices] faces.extend([i for i in ran]) - #print(faces) + # print(faces) # else: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Triangulator else: trianglator = Triangulator() faces = [] # add boundary points - #polyBorder = boundary.as_points() + # polyBorder = boundary.as_points() pt_count = 0 # add extra middle point for border for pt in polyBorder: - if pt_count < len(polyBorder)-1: - pt2 = polyBorder[pt_count+1] - else: pt2 = polyBorder[0] - + if pt_count < len(polyBorder) - 1: + pt2 = polyBorder[pt_count + 1] + else: + pt2 = polyBorder[0] + trianglator.addPolygonVertex(trianglator.addVertex(pt.x, pt.y)) vertices.extend([pt.x, pt.y, pt.z]) - #trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3)) - #vertices.extend([(pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3, (pt.z+pt2.z)/4*3]) + # trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3)) + # vertices.extend([(pt.x+pt2.x)/4*3, (pt.y+pt2.y)/4*3, (pt.z+pt2.z)/4*3]) - trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/2, (pt.y+pt2.y)/2)) - vertices.extend([(pt.x+pt2.x)/2, (pt.y+pt2.y)/2, (pt.z+pt2.z)/2]) - - #trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4, (pt.y+pt2.y)/4)) - #vertices.extend([(pt.x+pt2.x)/4, (pt.y+pt2.y)/4, (pt.z+pt2.z)/4]) + trianglator.addPolygonVertex( + trianglator.addVertex((pt.x + pt2.x) / 2, (pt.y + pt2.y) / 2) + ) + vertices.extend( + [(pt.x + pt2.x) / 2, (pt.y + pt2.y) / 2, (pt.z + pt2.z) / 2] + ) + + # trianglator.addPolygonVertex(trianglator.addVertex((pt.x+pt2.x)/4, (pt.y+pt2.y)/4)) + # vertices.extend([(pt.x+pt2.x)/4, (pt.y+pt2.y)/4, (pt.z+pt2.z)/4]) total_vertices += 2 pt_count += 1 - #add void points + # add void points for i in range(len(voids)): trianglator.beginHole() - pts = [] - if isinstance(voids[i], Circle) or isinstance(voids[i], Arc): - pts = speckleArcCircleToPoints(voids[i]) - elif isinstance(voids[i], Polycurve): - pts = specklePolycurveToPoints(voids[i]) - elif isinstance(voids[i], Line): pass - else: - try: pts = voids[i].as_points() - except: pass # if Line - #pts = voids[i].as_points() + if isinstance(voids[i], Circle) or isinstance(voids[i], Arc): + pts = speckleArcCircleToPoints(voids[i]) + elif isinstance(voids[i], Polycurve): + pts = specklePolycurveToPoints(voids[i]) + elif isinstance(voids[i], Line): + pass + else: + try: + pts = voids[i].as_points() + except: + pass # if Line + # pts = voids[i].as_points() for pt in pts: trianglator.addHoleVertex(trianglator.addVertex(pt.x, pt.y)) vertices.extend([pt.x, pt.y, pt.z]) @@ -279,29 +301,42 @@ def polygonToSpeckle(geom, index: int, layer, multitype: bool): trianglator.triangulate() i = 0 while i < trianglator.getNumTriangles(): - tr = [trianglator.getTriangleV0(i),trianglator.getTriangleV1(i),trianglator.getTriangleV2(i)] + tr = [ + trianglator.getTriangleV0(i), + trianglator.getTriangleV1(i), + trianglator.getTriangleV2(i), + ] faces.extend([3, tr[0], tr[1], tr[2]]) - i+=1 + i += 1 ran = range(0, total_vertices) - - #print(polygon) + + # print(polygon) col = featureColorfromNativeRenderer(index, layer) - colors = [col for i in ran] # apply same color for all vertices + colors = [col for i in ran] # apply same color for all vertices mesh = constructMesh(vertices, faces, colors) - - if mesh is not None: - polygon.displayValue = [ mesh ] - else: - logToUser("Mesh creation from Polygon failed. Boundaries will be used as displayValue", level = 1, func = inspect.stack()[0][3]) + + if mesh is not None: + polygon.displayValue = [mesh] + else: + logToUser( + "Mesh creation from Polygon failed. Boundaries will be used as displayValue", + level=1, + func=inspect.stack()[0][3], + ) return polygon else: - logToUser("Not enough points for Polygon boundary", level = 1, func = inspect.stack()[0][3]) + logToUser( + "Not enough points for Polygon boundary", + level=1, + func=inspect.stack()[0][3], + ) return None except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None + def polygonToNative(poly: Base, sr: arcpy.SpatialReference) -> arcpy.Polygon: """Converts a Speckle Polygon base object to QgsPolygon. This object must have a 'boundary' and 'voids' properties. @@ -309,50 +344,58 @@ def polygonToNative(poly: Base, sr: arcpy.SpatialReference) -> arcpy.Polygon: print("_______Drawing polygons____") polygon = None - try: + try: try: poly = poly["geometry"] - except: pass - #pts = [pointToCoord(pt) for pt in poly["boundary"].as_points()] + except: + pass + # pts = [pointToCoord(pt) for pt in poly["boundary"].as_points()] pointsSpeckle = [] - if isinstance(poly["boundary"], Circle) or isinstance(poly["boundary"], Arc): - pointsSpeckle = speckleArcCircleToPoints(poly["boundary"]) - elif isinstance(poly["boundary"], Polycurve): - pointsSpeckle = specklePolycurveToPoints(poly["boundary"]) - elif isinstance(poly["boundary"], Line): pass - else: - try: pointsSpeckle = poly["boundary"].as_points() - except: pass # if Line + if isinstance(poly["boundary"], Circle) or isinstance(poly["boundary"], Arc): + pointsSpeckle = speckleArcCircleToPoints(poly["boundary"]) + elif isinstance(poly["boundary"], Polycurve): + pointsSpeckle = specklePolycurveToPoints(poly["boundary"]) + elif isinstance(poly["boundary"], Line): + pass + else: + try: + pointsSpeckle = poly["boundary"].as_points() + except: + pass # if Line pts = [pointToCoord(pt) for pt in pointsSpeckle] - #print(pts) + # print(pts) outer_arr = [arcpy.Point(*coords) for coords in pts] outer_arr.append(outer_arr[0]) geomPart = [] try: - for void in poly["voids"]: - #print(void) - #pts = [pointToCoord(pt) for pt in void.as_points()] + for void in poly["voids"]: + # print(void) + # pts = [pointToCoord(pt) for pt in void.as_points()] pointsSpeckle = [] - if isinstance(void, Circle) or isinstance(void, Arc): - pointsSpeckle = speckleArcCircleToPoints(void) - elif isinstance(void, Polycurve): - pointsSpeckle = specklePolycurveToPoints(void) - elif isinstance(void, Line): pass - else: - try: pointsSpeckle = void.as_points() - except: pass # if Line + if isinstance(void, Circle) or isinstance(void, Arc): + pointsSpeckle = speckleArcCircleToPoints(void) + elif isinstance(void, Polycurve): + pointsSpeckle = specklePolycurveToPoints(void) + elif isinstance(void, Line): + pass + else: + try: + pointsSpeckle = void.as_points() + except: + pass # if Line pts = [pointToCoord(pt) for pt in pointsSpeckle] inner_arr = [arcpy.Point(*coords) for coords in pts] inner_arr.append(inner_arr[0]) geomPart.append(arcpy.Array(inner_arr)) - except:pass + except: + pass geomPart.insert(0, outer_arr) geomPartArray = arcpy.Array(geomPart) polygon = arcpy.Polygon(geomPartArray, sr, has_z=True) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polygon diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polyline.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polyline.py index ff59d15..250181a 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polyline.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/polyline.py @@ -1,105 +1,135 @@ - from math import atan, cos, sin import math -import json +import json from typing import List, Union, Tuple -from specklepy.objects import Base -from specklepy.objects.geometry import Box, Vector, Point, Line, Polyline, Curve, Ellipse, Arc, Circle, Polycurve, Plane, Interval -import arcpy +from specklepy.objects import Base +from specklepy.objects.geometry import ( + Box, + Vector, + Point, + Line, + Polyline, + Curve, + Ellipse, + Arc, + Circle, + Polycurve, + Plane, + Interval, +) +import arcpy import numpy as np -import inspect +import inspect -try: - from speckle.speckle.converter.geometry.point import pointToCoord, pointToSpeckle, addZtoPoint - from speckle.speckle.converter.layers.utils import get_scale_factor - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.point import pointToCoord, pointToSpeckle, addZtoPoint - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import get_scale_factor - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser +from speckle.speckle.converter.geometry.point import ( + pointToCoord, + pointToSpeckle, + addZtoPoint, +) +from speckle.speckle.converter.geometry.utils import speckleArcCircleToPoints +from speckle.speckle.converter.layers.utils import get_scale_factor +from speckle.speckle.utils.panel_logging import logToUser def circleToSpeckle(center, point): print("___Circle to Speckle____") try: - rad = math.sqrt(math.pow((center[0] - point[0]),2) + math.pow((center[1] - point[1]),2) ) - #print(rad) - if len(center)>2: center_z = center[2] - else: center_z = 0 - length = rad*2*math.pi - domain = [0, length] - plane = [center[0], center[1], center_z, 0,0,1, 1,0,0, 0,1,0] - units = 3 #"m" + rad = math.sqrt( + math.pow((center[0] - point[0]), 2) + math.pow((center[1] - point[1]), 2) + ) + # print(rad) + if len(center) > 2: + center_z = center[2] + else: + center_z = 0 + length = rad * 2 * math.pi + domain = [0, length] + plane = [center[0], center[1], center_z, 0, 0, 1, 1, 0, 0, 0, 1, 0] + units = 3 # "m" - args = [0] + [rad] + domain + plane + [units] - #print(args) + args = [0] + [rad] + domain + plane + [units] + # print(args) c = Circle.from_list(args) c.plane.origin.units = "m" c.units = "m" - #print(c) - return c + # print(c) + return c except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None + def multiPolylineToSpeckle(geom, feature, layer, multiType: bool): print("___MultiPolyline to Speckle____") polyline = [] try: print(enumerate(geom.getPart())) - for i,x in enumerate(geom.getPart()): - poly = arcpy.Polyline(x, arcpy.Describe(layer.dataSource).SpatialReference, has_z = True) + for i, x in enumerate(geom.getPart()): + poly = arcpy.Polyline( + x, arcpy.Describe(layer.dataSource).SpatialReference, has_z=True + ) print(poly) polyline.append(polylineToSpeckle(poly, feature, layer, poly.isMultipart)) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polyline + def polylineToSpeckle(geom, feature, layer, multiType: bool): print("___Polyline to Speckle____") polyline = None - try: + try: pointList = [] - print(geom.hasCurves) + print(geom.hasCurves) - if multiType is False: - if geom.hasCurves: + if multiType is False: + if geom.hasCurves: print("has curves") # geometry SHAPE@ tokens: https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/reading-geometries.htm - print(geom.JSON) + print(geom.JSON) polyline = curveToSpeckle(geom, "Polyline", feature, layer) else: - for p in geom: - for pt in p: - if pt != None: pointList.append(pt)#; print(pt.Z) + for p in geom: + for pt in p: + if pt != None: + pointList.append(pt) # ; print(pt.Z) closed = False - if pointList[0] == pointList[len(pointList)-1]: + if pointList[0] == pointList[len(pointList) - 1]: closed = True pointList = pointList[:-1] - polyline = polylineFromVerticesToSpeckle(pointList, closed, feature, layer) + polyline = polylineFromVerticesToSpeckle( + pointList, closed, feature, layer + ) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polyline -def polylineFromVerticesToSpeckle(vertices: List[Point], closed: bool, feature, layer) -> Polyline: + +def polylineFromVerticesToSpeckle( + vertices: List[Point], closed: bool, feature, layer +) -> Polyline: """Converts a Polyline to Speckle""" - polyline = Polyline(units = "m") + polyline = Polyline(units="m") try: - - if isinstance(vertices, list): + + if isinstance(vertices, list): if len(vertices) > 0 and isinstance(vertices[0], Point): specklePts = vertices - else: specklePts = [pointToSpeckle(pt, feature, layer) for pt in vertices] #breaks unexplainably - #elif isinstance(vertices, QgsVertexIterator): + else: + specklePts = [ + pointToSpeckle(pt, feature, layer) for pt in vertices + ] # breaks unexplainably + # elif isinstance(vertices, QgsVertexIterator): # specklePts = [pointToSpeckle(pt, feature, layer) for pt in vertices] - else: return None + else: + return None specklePts = [] for pt in vertices: - newPt = pointToSpeckle(pt, feature, layer) + newPt = pointToSpeckle(pt, feature, layer) specklePts.append(newPt) # TODO: Replace with `from_points` function when fix is pushed. @@ -107,15 +137,16 @@ def polylineFromVerticesToSpeckle(vertices: List[Point], closed: bool, feature, polyline.closed = closed polyline.units = specklePts[0].units for i, point in enumerate(specklePts): - if closed and i == len(specklePts) - 1 and specklePts[0] == point: - continue # if we consider the last pt, do not add is coincides with the first (and type is Closed) + if closed and i == len(specklePts) - 1 and specklePts[0] == point: + continue # if we consider the last pt, do not add is coincides with the first (and type is Closed) polyline.value.extend([point.x, point.y, point.z]) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polyline -def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc: + +def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc: print("____arc 3pt to Speckle___") arc = Arc() try: @@ -125,53 +156,61 @@ def arc3ptToSpeckle(p0: List, p1: List, p2: List, feature, layer) -> Arc: arc.startPoint = pointToSpeckle(arcpy.Point(*p0), feature, layer) arc.midPoint = pointToSpeckle(arcpy.Point(*p1), feature, layer) arc.endPoint = pointToSpeckle(arcpy.Point(*p2), feature, layer) - center, radius = getArcCenter(Point.from_list(p0), Point.from_list(p1), Point.from_list(p2)) - arc.plane = Plane() #.from_list(Point(), Vector(Point(0, 0, 1)), Vector(Point(0,1,0)), Vector(Point(-1,0,0))) + center, radius = getArcCenter( + Point.from_list(p0), Point.from_list(p1), Point.from_list(p2) + ) + arc.plane = ( + Plane() + ) # .from_list(Point(), Vector(Point(0, 0, 1)), Vector(Point(0,1,0)), Vector(Point(-1,0,0))) arc.plane.origin = Point.from_list(center) - arc.plane.origin.units = "m" + arc.plane.origin.units = "m" arc.units = "m" arc.angleRadians, startAngle, endAngle = getArcRadianAngle(arc) arc.radius = radius - + arc.plane.normal = getArcNormal(arc, arc.midPoint) - #arc.angleRadians = abs(angle1 + angle2) - #print(arc.angleRadians) + # arc.angleRadians = abs(angle1 + angle2) + # print(arc.angleRadians) - #col = featureColorfromNativeRenderer(feature, layer) - #arc['displayStyle'] = {} - #arc['displayStyle']['color'] = col + # col = featureColorfromNativeRenderer(feature, layer) + # arc['displayStyle'] = {} + # arc['displayStyle']['color'] = col except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return arc -def curveBezierToSpeckle(segmStartCoord, segmEndCoord, knots, feature, layer): + +def curveBezierToSpeckle(segmStartCoord, segmEndCoord, knots, feature, layer): print("____bezier curve to Speckle____") try: degree = 3 points = [ - tuple(knots[0]), tuple(segmStartCoord), tuple(knots[1]), tuple(segmEndCoord) - ] #[segmStartCoord, *coords] + tuple(knots[0]), + tuple(segmStartCoord), + tuple(knots[1]), + tuple(segmEndCoord), + ] # [segmStartCoord, *coords] print(points) - num_points = len(points) #2 + num_points = len(points) # 2 - knot_count = num_points + degree - 1 #4 + knot_count = num_points + degree - 1 # 4 knots = [0] * knot_count print(knots) for i in range(1, len(knots)): knots[i] = i // 3 print(knots[i]) - length = 1 #spline.calc_length() + length = 1 # spline.calc_length() domain = Interval(start=0, end=length, totalChildrenCount=0) points = [tuple(pt) for pt in points] curve = Curve( - degree = degree, - closed = False, - periodic= True if (segmStartCoord == segmEndCoord) else False, - points= list(sum(points, ())), # magic (flatten list of tuples) + degree=degree, + closed=False, + periodic=True if (segmStartCoord == segmEndCoord) else False, + points=list(sum(points, ())), # magic (flatten list of tuples) weights=[1] * num_points, knots=knots, rational=False, @@ -182,43 +221,49 @@ def curveBezierToSpeckle(segmStartCoord, segmEndCoord, knots, feature, layer): units="m", bbox=Box(area=0.0, volume=0.0), ) - print(curve) + print(curve) return curve except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None -def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polyline, Polycurve]: +def curveToSpeckle( + geom, geomType, feature, layer +) -> Union[Circle, Arc, Polyline, Polycurve]: print("____curve to Speckle____") - boundary = Polycurve(units = "m") + boundary = Polycurve(units="m") print(geomType) try: - # look for "curvePaths" or "curveRings"[[ (startPt, {arcs, beziers etc}, optional(endPt))],[],...], "rings" + # look for "curvePaths" or "curveRings"[[ (startPt, {arcs, beziers etc}, optional(endPt))],[],...], "rings" # examples: https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm # e.g. {"hasZ":true, # "curveRings":[[[631307.05960000027,5803698.4477999993,0],{"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]], # "spatialReference":{"wkid":32631,"latestWkid":32631}} - - # b - bezier curve (endPt, controlPts) + + # b - bezier curve (endPt, controlPts) # a - elliptical arc (endPt, centralPt) e.g. for circle: [[[631307.05960000027,5803698.4477999993,0],{"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}]] # c - circular arc (endPt, throughPt) e.g. [[[633242.45179999992,5803058.0354999993,0],{"c":[[633718.26040000003,5803496.4210000001,0],[633337.75764975848,5803431.9997026781]]},[633242.45179999992,5803058.0354999993,0]]] - - if geomType == "Polyline": boundary.closed = False - else: boundary.closed = True - segments = [] - for key, val in json.loads(geom.JSON).items(): + if geomType == "Polyline": + boundary.closed = False + else: + boundary.closed = True + segments = [] + + for key, val in json.loads(geom.JSON).items(): print(key) - if key == "curveRings" or key == "curvePaths": - - #boundary.closed = True + if key == "curveRings" or key == "curvePaths": + + # boundary.closed = True includesLines = 0 - for segm in val: # segm: List - print(segm) #e.g. [[631307.05960000027,5803698.4477999993,0], {"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}] + for segm in val: # segm: List + print( + segm + ) # e.g. [[631307.05960000027,5803698.4477999993,0], {"a":[[631307.05960000027,5803698.4477999993,0],[631307.05960000027,5803414.92656173],0,1]}] segmStartCoord: List = addZtoPoint(segm[0]) - + # go through all elements (points, a, c, ...) for k in range(1, len(segm)): # e.g. one from the list: "curveRings":[[[631750.87200000044,5803159.6126000006,0], @@ -226,61 +271,93 @@ def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polylin # {"c":[[632590.21970000025,5803127.5355999991,0],[633018.51899157302,5803532.1801161235]]}, # [631750.87200000044,5803159.6126000006,0]]] - # if previous segments exist - if len(segments) > 0: - segmOldData = segm[k-1] - if isinstance(segmOldData, dict): # get "end point" of previous segment - for key3, val3 in segmOldData.items(): - segmStartCoord: List = addZtoPoint(val2[0]) - elif isinstance(segmOldData, list) and isinstance(segmOldData[0], float): - segmStartCoord: List = segmOldData + # if previous segments exist + if len(segments) > 0: + segmOldData = segm[k - 1] + if isinstance( + segmOldData, dict + ): # get "end point" of previous segment + for key3, val3 in segmOldData.items(): + segmStartCoord: List = addZtoPoint(val2[0]) + elif isinstance(segmOldData, list) and isinstance( + segmOldData[0], float + ): + segmStartCoord: List = segmOldData segmStartCoord = addZtoPoint(segmStartCoord) - if isinstance(segm[k], dict): + if isinstance(segm[k], dict): for key2, val2 in segm[k].items(): - if key2 == "a": # elliptical arc (endPt, centralPt) + if key2 == "a": # elliptical arc (endPt, centralPt) # e.g. {'a': [[633883.1035000002, 5802972.5812, 0], [634028.3379278888, 5802908.342895357], 0, 1, 1.1543577096027686, 473.59966687227444, 0.33531864204900685]} - segmEndCoord = addZtoPoint(val2[0]) # [631307.05960000027,5803698.4477999993,0] - segmCenter = addZtoPoint(val2[1]) # [631307.05960000027,5803414.92656173] - - if segmStartCoord == segmEndCoord: - if len(val2) == 4: - print("full circle") - #boundary.closed = True - segmentLocal = circleToSpeckle(segmCenter, segmEndCoord) + segmEndCoord = addZtoPoint( + val2[0] + ) # [631307.05960000027,5803698.4477999993,0] + segmCenter = addZtoPoint( + val2[1] + ) # [631307.05960000027,5803414.92656173] + + if segmStartCoord == segmEndCoord: + if len(val2) == 4: + print("full circle") + # boundary.closed = True + segmentLocal = circleToSpeckle( + segmCenter, segmEndCoord + ) segments.append(segmentLocal) - lastPt = segmEndCoord + lastPt = segmEndCoord print("segmentLocal:") print(segmentLocal) print(segmStartCoord) print(segmEndCoord) - else: # ellipse - logToUser("SpeckleWarning: ellipse geometry not supported yet", level=1, func = inspect.stack()[0][3]) + else: # ellipse + logToUser( + "SpeckleWarning: ellipse geometry not supported yet", + level=1, + func=inspect.stack()[0][3], + ) segments = [] break - else: # elliptical curve - logToUser("SpeckleWarning: ellipse geometry not supported yet", level=1, func = inspect.stack()[0][3]) + else: # elliptical curve + logToUser( + "SpeckleWarning: ellipse geometry not supported yet", + level=1, + func=inspect.stack()[0][3], + ) segments = [] break - if key2 == "c": # circular arc (endPt, throughPt) - - segmEndCoord: List = addZtoPoint(val2[0]) # [633718.26040000003,5803496.4210000001,0] - segmThrough: List = addZtoPoint(val2[1]) # [633337.7576497585, 5803431.999702678] + if key2 == "c": # circular arc (endPt, throughPt) - segmentLocal = arc3ptToSpeckle(segmStartCoord, segmThrough, segmEndCoord, geom, layer) + segmEndCoord: List = addZtoPoint( + val2[0] + ) # [633718.26040000003,5803496.4210000001,0] + segmThrough: List = addZtoPoint( + val2[1] + ) # [633337.7576497585, 5803431.999702678] + + segmentLocal = arc3ptToSpeckle( + segmStartCoord, + segmThrough, + segmEndCoord, + geom, + layer, + ) segments.append(segmentLocal) print("segmentLocal:") print(segmentLocal) print(segmStartCoord) print(segmEndCoord) - lastPt = segmEndCoord + lastPt = segmEndCoord - if key2 == "b": # bezier curve (endPt, controlPts) - logToUser("SpeckleWarning: bezier curve geometry not supported yet", level=1, func = inspect.stack()[0][3]) + if key2 == "b": # bezier curve (endPt, controlPts) + logToUser( + "SpeckleWarning: bezier curve geometry not supported yet", + level=1, + func=inspect.stack()[0][3], + ) segments = [] break - r''' + r""" segmEndCoord: List = addZtoPoint(val2[0]) # [633718.26040000003,5803496.4210000001,0] #segmThrough: List = val2[1] # [633337.7576497585, 5803431.999702678] coords = val2[1:] @@ -292,14 +369,16 @@ def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polylin print(segmEndCoord) lastPt = segmEndCoord - ''' - - elif isinstance(segm[k], list) and isinstance(segm[k][0], float): # add line to point + """ + + elif isinstance(segm[k], list) and isinstance( + segm[k][0], float + ): # add line to point print("add line") - segm[k] = addZtoPoint(segm[k]) + segm[k] = addZtoPoint(segm[k]) segmentLocal = lineFrom2pt(segmStartCoord, segm[k]) includesLines = 1 - segments.append(segmentLocal) + segments.append(segmentLocal) lastPt = segm[k] print("segmentLocal:") @@ -308,88 +387,97 @@ def curveToSpeckle(geom, geomType, feature, layer) -> Union[Circle, Arc, Polylin print(segmentLocal.end) # for the last point - if k == len(segm)-1 and isinstance(segm[k], list): + if k == len(segm) - 1 and isinstance(segm[k], list): print("last element is a point (adding line)") lastPt = addZtoPoint(lastPt) - if lastPt != segm[0]: - #segmentLocal = lineFrom2pt(lastPt, segm[0]) - #segments.append(segmentLocal) - #includesLines = 1 - #print("segmentLocal:") - #print(segmentLocal) - #print(segmentLocal.start) - #print(segmentLocal.end) - boundary.closed = True - #pts = speckleArcCircleToPoints(segmentLocal) - #pts.append(segm[k]) - #arcgisPts = [arcpy.Point(pt[0], pt[1], pt[2]) for pt in pts] - #segmentLocal = polylineFromVerticesToSpeckle(arcgisPts, True, feature, layer) - + if lastPt != segm[0]: + # segmentLocal = lineFrom2pt(lastPt, segm[0]) + # segments.append(segmentLocal) + # includesLines = 1 + # print("segmentLocal:") + # print(segmentLocal) + # print(segmentLocal.start) + # print(segmentLocal.end) + boundary.closed = True + # pts = speckleArcCircleToPoints(segmentLocal) + # pts.append(segm[k]) + # arcgisPts = [arcpy.Point(pt[0], pt[1], pt[2]) for pt in pts] + # segmentLocal = polylineFromVerticesToSpeckle(arcgisPts, True, feature, layer) + boundary.segments = segments print(segments) if len(segments) == 1: boundary = segments[0] - #if isinstance(boundary, Arc) or isinstance(boundary, Circle): - # boundary.displayValue = Polyline.from_points(speckleArcCircleToPoints(boundary)) - - elif len(segments) > 1: # and includesLines == 0: - #boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary)) - pass - #boundary.closed = True - #elif len(segments) > 1 and includesLines == 1: - # print("includes lines!") - # points = specklePolycurveToPoints(boundary) - # boundary = Polyline.from_points(points) - else: return None + # if isinstance(boundary, Arc) or isinstance(boundary, Circle): + # boundary.displayValue = Polyline.from_points(speckleArcCircleToPoints(boundary)) + + elif len(segments) > 1: # and includesLines == 0: + # boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary)) + pass + # boundary.closed = True + # elif len(segments) > 1 and includesLines == 1: + # print("includes lines!") + # points = specklePolycurveToPoints(boundary) + # boundary = Polyline.from_points(points) + else: + return None + + # boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary)) - #boundary.displayValue = Polyline.from_points(specklePolycurveToPoints(boundary)) - print(boundary) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return boundary + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return boundary - -def lineFrom2pt(pt1: List[float], pt2: List[float]): - line = Line(units = "m" )#.from_list([*pt1, *pt2, *domain]) +def lineFrom2pt(pt1: List[float], pt2: List[float]): + line = Line(units="m") # .from_list([*pt1, *pt2, *domain]) try: pt1 = addZtoPoint(pt1) pt2 = addZtoPoint(pt2) - dist = math.sqrt( math.pow((pt2[0] - pt1[0]), 2) + math.pow((pt2[1] - pt1[1]), 2) + math.pow((pt2[2] - pt1[2]), 2) ) - print(dist) - domain = [0, dist, 0, 0] + dist = math.sqrt( + math.pow((pt2[0] - pt1[0]), 2) + + math.pow((pt2[1] - pt1[1]), 2) + + math.pow((pt2[2] - pt1[2]), 2) + ) + print(dist) + domain = [0, dist, 0, 0] line.start = Point.from_list(pt1) line.end = Point.from_list(pt2) - line.start.units = line.end.units = "m" + line.start.units = line.end.units = "m" except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return line + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return line + def polylineToNative(poly: Polyline, sr: arcpy.SpatialReference) -> arcpy.Polyline: """Converts a Speckle Polyline to QgsLineString""" print("__ convert polyline to native __") - polyline = None + polyline = None try: - if isinstance(poly, Polycurve): + if isinstance(poly, Polycurve): poly = specklePolycurveToPoints(poly) - if isinstance(poly, Arc) or isinstance(poly, Circle): - try: poly = poly["displayValue"] - except: poly = speckleArcCircleToPoints(poly) - - if isinstance(poly, list): pts = [pointToCoord(pt) for pt in poly] - else: pts = [pointToCoord(pt) for pt in poly.as_points()] + if isinstance(poly, Arc) or isinstance(poly, Circle): + try: + poly = poly["displayValue"] + except: + poly = speckleArcCircleToPoints(poly) - if poly.closed is True: - pts.append( pointToCoord(poly.as_points()[0]) ) + if isinstance(poly, list): + pts = [pointToCoord(pt) for pt in poly] + else: + pts = [pointToCoord(pt) for pt in poly.as_points()] + + if poly.closed is True: + pts.append(pointToCoord(poly.as_points()[0])) pts_coord_list = [arcpy.Point(*coords) for coords in pts] - polyline = arcpy.Polyline( arcpy.Array(pts_coord_list), sr, has_z=True ) - #print(polyline.JSON) + polyline = arcpy.Polyline(arcpy.Array(pts_coord_list), sr, has_z=True) + # print(polyline.JSON) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return polyline @@ -398,284 +486,172 @@ def lineToNative(line: Line, sr: arcpy.SpatialReference) -> arcpy.Polyline: print("___Line to Native___") try: pts = [pointToCoord(pt) for pt in [line.start, line.end]] - line = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in pts]), sr , has_z=True) + line = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in pts]), sr, has_z=True + ) return line - + except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None + def curveToNative(poly: Curve, sr: arcpy.SpatialReference) -> arcpy.Polyline: """Converts a Speckle Curve to Native""" - try: + try: display = poly.displayValue - curve = polylineToNative(display, sr) + curve = polylineToNative(display, sr) return curve except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None + def arcToNative(poly: Arc, sr: arcpy.SpatialReference) -> arcpy.Polyline: """Converts a Speckle Arc to Native""" try: - arc = arcToNativePolyline(poly, sr) #QgsCircularString(pointToNative(poly.startPoint), pointToNative(poly.midPoint), pointToNative(poly.endPoint)) + arc = arcToNativePolyline( + poly, sr + ) # QgsCircularString(pointToNative(poly.startPoint), pointToNative(poly.midPoint), pointToNative(poly.endPoint)) return arc except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None + def ellipseToNative(poly: Ellipse, sr: arcpy.SpatialReference): logToUser("Ellipse geometry is not supported yet", level=1) return + def circleToNative(poly: Circle, sr: arcpy.SpatialReference) -> arcpy.Polyline: """Converts a Speckle Circle to QgsLineString""" print("___Convert Circle to Native___") curve = None - try: + try: points = [] - angle1 = math.pi/2 - - pointsNum = math.floor(math.pi*2) * 12 - if pointsNum <4: pointsNum = 4 + angle1 = math.pi / 2 + + pointsNum = math.floor(math.pi * 2) * 12 + if pointsNum < 4: + pointsNum = 4 points.append(pointToCoord(poly.plane.origin)) radScaled = poly.radius * get_scale_factor(poly.units) points[0][1] += radScaled - for i in range(1, pointsNum + 1): - k = i/pointsNum # to reset values from 1/10 to 1 - if poly.plane.normal.z == 0: normal = 1 - else: normal = poly.plane.normal.z - angle = angle1 + k * math.pi*2 * normal - pt = Point( x = poly.plane.origin.x * get_scale_factor(poly.units) + radScaled * cos(angle), y = poly.plane.origin.y * get_scale_factor(poly.units) + radScaled * sin(angle), z = 0) + for i in range(1, pointsNum + 1): + k = i / pointsNum # to reset values from 1/10 to 1 + if poly.plane.normal.z == 0: + normal = 1 + else: + normal = poly.plane.normal.z + angle = angle1 + k * math.pi * 2 * normal + pt = Point( + x=poly.plane.origin.x * get_scale_factor(poly.units) + + radScaled * cos(angle), + y=poly.plane.origin.y * get_scale_factor(poly.units) + + radScaled * sin(angle), + z=0, + ) print(pt) pt.units = "m" points.append(pointToCoord(pt)) points.append(points[0]) - curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr , has_z=True) - + curve = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True + ) + except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return curve + def polycurveToNative(poly: Polycurve, sr: arcpy.SpatialReference) -> arcpy.Polyline: points = [] curve = None print("___Polycurve to native___") - try: - for i, segm in enumerate(poly.segments): # Line, Polyline, Curve, Arc, Circle - print("___start segment") - if isinstance(segm,Line): converted = lineToNative(segm, sr) # QgsLineString - elif isinstance(segm,Polyline): converted = polylineToNative(segm, sr) # QgsLineString - elif isinstance(segm,Curve): converted = curveToNative(segm, sr) # QgsLineString - elif isinstance(segm,Circle): converted = circleToNative(segm, sr) # QgsLineString - elif isinstance(segm,Arc): converted = arcToNativePolyline(segm, sr) # QgsLineString - else: # either return a part of the curve, of skip this segment and try next - logToUser(f"Part of the polycurve cannot be converted", level=1, func = inspect.stack()[0][3]) - curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr , has_z=True) + try: + for i, segm in enumerate(poly.segments): # Line, Polyline, Curve, Arc, Circle + print("___start segment") + if isinstance(segm, Line): + converted = lineToNative(segm, sr) # QgsLineString + elif isinstance(segm, Polyline): + converted = polylineToNative(segm, sr) # QgsLineString + elif isinstance(segm, Curve): + converted = curveToNative(segm, sr) # QgsLineString + elif isinstance(segm, Circle): + converted = circleToNative(segm, sr) # QgsLineString + elif isinstance(segm, Arc): + converted = arcToNativePolyline(segm, sr) # QgsLineString + else: # either return a part of the curve, of skip this segment and try next + logToUser( + f"Part of the polycurve cannot be converted", + level=1, + func=inspect.stack()[0][3], + ) + curve = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in points]), + sr, + has_z=True, + ) return curve - if converted is not None: - #print(converted) # + if converted is not None: + # print(converted) # for part in converted: - #print("Part: ") - #print(part) # - for pt in part: - #print(pt) # 64.4584221540162 5.5 NaN NaN - if pt.Z != None: pt_z = pt.Z - else: pt_z = 0 - #print(pt_z) - #print(len(points)) - if len(points)>0 and pt.X == points[len(points)-1][0] and pt.Y == points[len(points)-1][1] and pt_z == points[len(points)-1][2]: pass - else: points.append(pointToCoord(Point(x=pt.X, y = pt.Y, z = pt_z, units = "m"))) # e.g. [[64.4584221540162, 5.499999999999999, 0.0], [64.45461685210796, 5.587155742747657, 0.0]] + # print("Part: ") + # print(part) # + for pt in part: + # print(pt) # 64.4584221540162 5.5 NaN NaN + if pt.Z != None: + pt_z = pt.Z + else: + pt_z = 0 + # print(pt_z) + # print(len(points)) + if ( + len(points) > 0 + and pt.X == points[len(points) - 1][0] + and pt.Y == points[len(points) - 1][1] + and pt_z == points[len(points) - 1][2] + ): + pass + else: + points.append( + pointToCoord(Point(x=pt.X, y=pt.Y, z=pt_z, units="m")) + ) # e.g. [[64.4584221540162, 5.499999999999999, 0.0], [64.45461685210796, 5.587155742747657, 0.0]] else: - logToUser(f"Part of the polycurve cannot be converted", level=1, func = inspect.stack()[0][3]) - curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True ) + logToUser( + f"Part of the polycurve cannot be converted", + level=1, + func=inspect.stack()[0][3], + ) + curve = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in points]), + sr, + has_z=True, + ) return curve - #print(curve) - - curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True ) + # print(curve) + + curve = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True + ) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return curve + def arcToNativePolyline(poly: Union[Arc, Circle], sr: arcpy.SpatialReference): print("__Arc/Circle to native polyline__") curve = None try: pointsSpeckle = speckleArcCircleToPoints(poly) points = [pointToCoord(p) for p in pointsSpeckle] - curve = arcpy.Polyline( arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True ) + curve = arcpy.Polyline( + arcpy.Array([arcpy.Point(*coords) for coords in points]), sr, has_z=True + ) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return curve - - -def specklePolycurveToPoints(poly: Polycurve) -> List[Point]: - print("_____Speckle Polycurve to points____") - points = [] - try: - for segm in poly.segments: - #print(segm) - pts = [] - if isinstance(segm, Arc) or isinstance(segm, Circle): # or isinstance(segm, Curve): - print("Arc or Curve") - pts: List[Point] = speckleArcCircleToPoints(segm) - elif isinstance(segm, Line): - print("Line") - pts: List[Point] = [segm.start, segm.end] - elif isinstance(segm, Polyline): - print("Polyline") - pts: List[Point] = segm.as_points() - - points.extend(pts) - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return points - -def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]: - print("__Arc or Circle to Points___") - points = [] - try: - #print(poly.plane) - #print(poly.plane.normal) - if poly.plane is None or poly.plane.normal.z == 0: normal = 1 - else: normal = poly.plane.normal.z - #print(poly.plane.origin) - if isinstance(poly, Circle): - interval = 2*math.pi - range_start = 0 - angle1 = 0 - - else: # if Arc - points.append(poly.startPoint) - range_start = 0 - - #angle1, angle2 = getArcAngles(poly) - - interval, angle1, angle2 = getArcRadianAngle(poly) - interval = abs(angle2 - angle1) - - #print(angle1) - #print(angle2) - - if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass - if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2) - if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1) - - #print(interval) - #print(normal) - - pointsNum = math.floor( abs(interval)) * 12 - if pointsNum <4: pointsNum = 4 - - for i in range(range_start, pointsNum + 1): - k = i/pointsNum # to reset values from 1/10 to 1 - angle = angle1 + k * interval * normal - #print(k) - #print(angle) - pt = Point( x = poly.plane.origin.x + poly.radius * cos(angle), y = poly.plane.origin.y + poly.radius * sin(angle), z = 0) - - pt.units = poly.plane.origin.units - points.append(pt) - if isinstance(poly, Arc): points.append(poly.endPoint) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return points - - -def getArcRadianAngle(arc: Arc) -> List[float]: - try: - interval = None - normal = arc.plane.normal.z - angle1, angle2 = getArcAngles(arc) - if angle1 is None or angle2 is None: return None - interval = abs(angle2 - angle1) - - if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass - if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2) - if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1) - return interval, angle1, angle2 - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None, None, None - -def getArcAngles(poly: Arc) -> Tuple[float]: - try: - if poly.startPoint.x == poly.plane.origin.x: angle1 = math.pi/2 - else: angle1 = atan( abs ((poly.startPoint.y - poly.plane.origin.y) / (poly.startPoint.x - poly.plane.origin.x) )) # between 0 and pi/2 - - if poly.plane.origin.x < poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = 2*math.pi - angle1 - if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = math.pi + angle1 - if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y < poly.startPoint.y: angle1 = math.pi - angle1 - - if poly.endPoint.x == poly.plane.origin.x: angle2 = math.pi/2 - else: angle2 = atan( abs ((poly.endPoint.y - poly.plane.origin.y) / (poly.endPoint.x - poly.plane.origin.x) )) # between 0 and pi/2 - - if poly.plane.origin.x < poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = 2*math.pi - angle2 - if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = math.pi + angle2 - if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y < poly.endPoint.y: angle2 = math.pi - angle2 - - return angle1, angle2 - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None, None - -def getArcNormal(poly: Arc, midPt: Point): - print("____getArcNormal___") - try: - angle1, angle2 = getArcAngles(poly) - - if midPt.x == poly.plane.origin.x: angle = math.pi/2 - else: angle = atan( abs ((midPt.y - poly.plane.origin.y) / (midPt.x - poly.plane.origin.x) )) # between 0 and pi/2 - - if poly.plane.origin.x < midPt.x and poly.plane.origin.y > midPt.y: angle = 2*math.pi - angle - if poly.plane.origin.x > midPt.x and poly.plane.origin.y > midPt.y: angle = math.pi + angle - if poly.plane.origin.x > midPt.x and poly.plane.origin.y < midPt.y: angle = math.pi - angle - - normal = Vector() - normal.x = normal.y = 0 - - if angle1 > angle > angle2: normal.z = -1 - if angle1 > angle2 > angle: normal.z = 1 - - if angle2 > angle1 > angle: normal.z = -1 - if angle > angle1 > angle2: normal.z = 1 - - if angle2 > angle > angle1: normal.z = 1 - if angle > angle2 > angle1: normal.z = -1 - - print(angle1) - print(angle) - print(angle2) - print(normal) - return normal - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None - - -def getArcCenter(p1: Point, p2: Point, p3: Point) -> Tuple[List, float]: - #print(p1) - try: - p1 = np.array(p1.to_list()) - p2 = np.array(p2.to_list()) - p3 = np.array(p3.to_list()) - a = np.linalg.norm(p3 - p2) - b = np.linalg.norm(p3 - p1) - c = np.linalg.norm(p2 - p1) - s = (a + b + c) / 2 - radius = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c)) - b1 = a*a * (b*b + c*c - a*a) - b2 = b*b * (a*a + c*c - b*b) - b3 = c*c * (a*a + b*b - c*c) - center = np.column_stack((p1, p2, p3)).dot(np.hstack((b1, b2, b3))) - center /= b1 + b2 + b3 - center = center.tolist() - return center, radius - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None, None diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/utils.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/utils.py index da10cf3..7f8dff1 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/utils.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/geometry/utils.py @@ -1,16 +1,132 @@ -from specklepy.objects.geometry import Point, Line, Polyline, Circle, Arc, Polycurve +import math +from math import cos, sin, atan +import numpy as np +from specklepy.objects.geometry import Point, Line, Polyline, Circle, Arc, Polycurve, Vector from specklepy.objects import Base -from typing import List, Union +from typing import List, Tuple, Union import inspect -try: - from speckle.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.polyline import speckleArcCircleToPoints, specklePolycurveToPoints - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser +from speckle.speckle.utils.panel_logging import logToUser + + +def apply_pt_offsets_rotation_on_send( + x: float, y: float, dataStorage +) -> Tuple[Union[float, None]]: # on Send + try: + offset_x = dataStorage.crs_offset_x + offset_y = dataStorage.crs_offset_y + rotation = dataStorage.crs_rotation + if offset_x is not None and isinstance(offset_x, float): + x -= offset_x + if offset_y is not None and isinstance(offset_y, float): + y -= offset_y + if ( + rotation is not None + and (isinstance(rotation, float) or isinstance(rotation, int)) + and -360 < rotation < 360 + ): + a = rotation * math.pi / 180 + x2 = x * math.cos(a) + y * math.sin(a) + y2 = -x * math.sin(a) + y * math.cos(a) + x = x2 + y = y2 + return x, y + 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 def speckleBoundaryToSpecklePts(boundary: Union[None, Polyline, Arc, Line, Polycurve]) -> List[Point]: @@ -28,4 +144,176 @@ def speckleBoundaryToSpecklePts(boundary: Union[None, Polyline, Arc, Line, Polyc except: pass # if Line or None except Exception as e: logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return polyBorder \ No newline at end of file + return polyBorder + + +def speckleArcCircleToPoints(poly: Union[Arc, Circle]) -> List[Point]: + print("__Arc or Circle to Points___") + points = [] + try: + #print(poly.plane) + #print(poly.plane.normal) + if poly.plane is None or poly.plane.normal.z == 0: normal = 1 + else: normal = poly.plane.normal.z + #print(poly.plane.origin) + if isinstance(poly, Circle): + interval = 2*math.pi + range_start = 0 + angle1 = 0 + + else: # if Arc + points.append(poly.startPoint) + range_start = 0 + + #angle1, angle2 = getArcAngles(poly) + + interval, angle1, angle2 = getArcRadianAngle(poly) + interval = abs(angle2 - angle1) + + #print(angle1) + #print(angle2) + + if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass + if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2) + if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1) + + #print(interval) + #print(normal) + + pointsNum = math.floor( abs(interval)) * 12 + if pointsNum <4: pointsNum = 4 + + for i in range(range_start, pointsNum + 1): + k = i/pointsNum # to reset values from 1/10 to 1 + angle = angle1 + k * interval * normal + #print(k) + #print(angle) + pt = Point( x = poly.plane.origin.x + poly.radius * cos(angle), y = poly.plane.origin.y + poly.radius * sin(angle), z = 0) + + pt.units = poly.plane.origin.units + points.append(pt) + if isinstance(poly, Arc): points.append(poly.endPoint) + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return points + + + +def getArcRadianAngle(arc: Arc) -> List[float]: + try: + interval = None + normal = arc.plane.normal.z + angle1, angle2 = getArcAngles(arc) + if angle1 is None or angle2 is None: return None + interval = abs(angle2 - angle1) + + if (angle1 > angle2 and normal == -1) or (angle2 > angle1 and normal == 1): pass + if angle1 > angle2 and normal == 1: interval = abs( (2*math.pi-angle1) + angle2) + if angle2 > angle1 and normal == -1: interval = abs( (2*math.pi-angle2) + angle1) + return interval, angle1, angle2 + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return None, None, None + +def getArcAngles(poly: Arc) -> Tuple[float]: + try: + if poly.startPoint.x == poly.plane.origin.x: angle1 = math.pi/2 + else: angle1 = atan( abs ((poly.startPoint.y - poly.plane.origin.y) / (poly.startPoint.x - poly.plane.origin.x) )) # between 0 and pi/2 + + if poly.plane.origin.x < poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = 2*math.pi - angle1 + if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y > poly.startPoint.y: angle1 = math.pi + angle1 + if poly.plane.origin.x > poly.startPoint.x and poly.plane.origin.y < poly.startPoint.y: angle1 = math.pi - angle1 + + if poly.endPoint.x == poly.plane.origin.x: angle2 = math.pi/2 + else: angle2 = atan( abs ((poly.endPoint.y - poly.plane.origin.y) / (poly.endPoint.x - poly.plane.origin.x) )) # between 0 and pi/2 + + if poly.plane.origin.x < poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = 2*math.pi - angle2 + if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y > poly.endPoint.y: angle2 = math.pi + angle2 + if poly.plane.origin.x > poly.endPoint.x and poly.plane.origin.y < poly.endPoint.y: angle2 = math.pi - angle2 + + return angle1, angle2 + + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return None, None + + +def specklePolycurveToPoints(poly: Polycurve) -> List[Point]: + print("_____Speckle Polycurve to points____") + points = [] + try: + for segm in poly.segments: + #print(segm) + pts = [] + if isinstance(segm, Arc) or isinstance(segm, Circle): # or isinstance(segm, Curve): + print("Arc or Curve") + pts: List[Point] = speckleArcCircleToPoints(segm) + elif isinstance(segm, Line): + print("Line") + pts: List[Point] = [segm.start, segm.end] + elif isinstance(segm, Polyline): + print("Polyline") + pts: List[Point] = segm.as_points() + + points.extend(pts) + + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return points + + +def getArcNormal(poly: Arc, midPt: Point): + print("____getArcNormal___") + try: + angle1, angle2 = getArcAngles(poly) + + if midPt.x == poly.plane.origin.x: angle = math.pi/2 + else: angle = atan( abs ((midPt.y - poly.plane.origin.y) / (midPt.x - poly.plane.origin.x) )) # between 0 and pi/2 + + if poly.plane.origin.x < midPt.x and poly.plane.origin.y > midPt.y: angle = 2*math.pi - angle + if poly.plane.origin.x > midPt.x and poly.plane.origin.y > midPt.y: angle = math.pi + angle + if poly.plane.origin.x > midPt.x and poly.plane.origin.y < midPt.y: angle = math.pi - angle + + normal = Vector() + normal.x = normal.y = 0 + + if angle1 > angle > angle2: normal.z = -1 + if angle1 > angle2 > angle: normal.z = 1 + + if angle2 > angle1 > angle: normal.z = -1 + if angle > angle1 > angle2: normal.z = 1 + + if angle2 > angle > angle1: normal.z = 1 + if angle > angle2 > angle1: normal.z = -1 + + print(angle1) + print(angle) + print(angle2) + print(normal) + return normal + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return None + + +def getArcCenter(p1: Point, p2: Point, p3: Point) -> Tuple[List, float]: + #print(p1) + try: + p1 = np.array(p1.to_list()) + p2 = np.array(p2.to_list()) + p3 = np.array(p3.to_list()) + a = np.linalg.norm(p3 - p2) + b = np.linalg.norm(p3 - p1) + c = np.linalg.norm(p2 - p1) + s = (a + b + c) / 2 + radius = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c)) + b1 = a*a * (b*b + c*c - a*a) + b2 = b*b * (a*a + c*c - b*b) + b3 = c*c * (a*a + b*b - c*c) + center = np.column_stack((p1, p2, p3)).dot(np.hstack((b1, b2, b3))) + center /= b1 + b2 + b3 + center = center.tolist() + return center, radius + except Exception as e: + logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return None, None diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/__init__.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/__init__.py index 9a81c3b..8529de7 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/__init__.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/__init__.py @@ -11,32 +11,19 @@ from typing import Any, List, Tuple, Union #from regex import D import inspect +from specklepy.objects.GIS.CRS import CRS +from specklepy.objects.GIS.layers import VectorLayer, RasterLayer, Layer -try: - from speckle.speckle.converter.layers.CRS import CRS - from speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer - from speckle.speckle.converter.layers.symbology import vectorRendererToNative, rasterRendererToNative, rendererToSpeckle, cadBimRendererToNative - from speckle.speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative, bimFeatureToNative, rasterFeatureToSpeckle - from speckle.speckle.plugin_utils.helpers import findOrCreatePath, findFeatColors +from speckle.speckle.converter.layers.symbology import vectorRendererToNative, rasterRendererToNative, rendererToSpeckle, cadBimRendererToNative +from speckle.speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative, bimFeatureToNative, rasterFeatureToSpeckle +from speckle.speckle.plugin_utils.helpers import findOrCreatePath, findFeatColors - from speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative, writeMeshToShp - from speckle.speckle.converter.layers.utils import findTransformation - from speckle.speckle.converter.layers.utils import getLayerAttributes, newLayerGroupAndName, validate_path - from speckle.speckle.plugin_utils.helpers import validateNewFclassName, removeSpecialCharacters - from speckle.speckle.ui.logger import logToUser +from speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative, writeMeshToShp +from speckle.speckle.converter.layers.utils import findTransformation +from speckle.speckle.converter.layers.utils import getLayerAttributes, newLayerGroupAndName, validate_path +from speckle.speckle.plugin_utils.helpers import validateNewFclassName, removeSpecialCharacters +from speckle.speckle.utils.panel_logging import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.CRS import CRS - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.symbology import vectorRendererToNative, rasterRendererToNative, rendererToSpeckle, cadBimRendererToNative - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.feature import featureToNative, featureToSpeckle, cadFeatureToNative, bimFeatureToNative, rasterFeatureToSpeckle - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import findOrCreatePath, findFeatColors - - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative, writeMeshToShp - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import findTransformation - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import getLayerAttributes, newLayerGroupAndName, validate_path - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import validateNewFclassName, removeSpecialCharacters - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser from specklepy.objects import Base from specklepy.objects.geometry import Mesh @@ -84,7 +71,7 @@ def getLayers(plugin, bySelection = False ) -> List[arcLayer]: # issue with getting selected layers: https://community.esri.com/t5/python-questions/determining-selected-layers-in-the-table-of/td-p/252098 self = plugin.dockwidget - project = plugin.gis_project + project = plugin.project all_layers = getAllProjLayers(project) if bySelection is True: # by selection @@ -167,6 +154,7 @@ def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Union[VectorLayer try: projectCRS = project.activeMap.spatialReference + logToUser(str(projectCRS.xy_units()), level=2, func = inspect.stack()[0][3]) try: data = arcpy.Describe(layer.dataSource) except OSError as e: logToUser(str(e.args[0]), level=2, func = inspect.stack()[0][3]) @@ -192,8 +180,6 @@ def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Union[VectorLayer speckleLayer.name = layerName speckleLayer.crs = speckleReprojectedCrs speckleLayer.renderer = rendererToSpeckle(project, project.activeMap, layer, None) - #speckleLayer.datum = datum - try: # https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/the-spatial-reference-object.htm @@ -283,7 +269,7 @@ def layerToSpeckle(layer: arcLayer, project: ArcGISProject) -> Union[VectorLayer def layerToNative(layer: Any, streamBranch: str, plugin=None) -> arcLayer: print("________________________________________Layer to Native") try: - project = plugin.gis_project + project = plugin.project layer_elements = layer.elements if layer_elements is None or len(layer_elements)==0: @@ -379,7 +365,7 @@ def bimLayerToNative(layerContentList: List[Base], layerName: str, streamBranch: layerName = removeSpecialCharacters(layerName) - project = plugin.gis_project + project = plugin.project geom_meshes = [] layer_meshes = None #filter speckle objects by type within each layer, create sub-layer for each type (points, lines, polygons, mesh?) @@ -391,7 +377,6 @@ def bimLayerToNative(layerContentList: List[Base], layerName: str, streamBranch: for p in geom_old.get_dynamic_member_names(): if p not in fields_to_ignore: geom[p] = geom_old[p] - except: geom = geom_old if isinstance(geom, List): @@ -682,7 +667,7 @@ def cadLayerToNative(layerContentList: List[Base], layerName: str, streamBranch: try: geom_points = [] geom_polylines = [] - project = plugin.gis_project + project = plugin.project print(layerName) geom_polygones = [] geom_meshes = [] diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/feature.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/feature.py index c108465..b216388 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/feature.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/feature.py @@ -14,22 +14,22 @@ from arcpy._mp import ArcGISProject, Map, Layer as arcLayer import inspect try: - from speckle.speckle.converter.geometry import convertToSpeckle, convertToNative, convertToNativeMulti + from speckle.speckle.converter.geometry.conversions import convertToSpeckle, convertToNative, convertToNativeMulti from speckle.speckle.converter.layers.utils import (findTransformation, getVariantFromValue, traverseDict, traverseDictByKey, hsv_to_rgb) from speckle.speckle.converter.geometry.point import pointToSpeckle from speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative from speckle.speckle.converter.layers.symbology import jsonFromLayerStyle - from speckle.speckle.ui.logger import logToUser + from speckle.speckle.utils.panel_logging import logToUser from speckle.speckle.plugin_utils.helpers import findOrCreatePath except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry import convertToSpeckle, convertToNative, convertToNativeMulti + from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.conversions import convertToSpeckle, convertToNative, convertToNativeMulti from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import (findTransformation, getVariantFromValue, traverseDict, traverseDictByKey, hsv_to_rgb) from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.point import pointToSpeckle from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.geometry.mesh import constructMeshFromRaster, meshToNative from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.symbology import jsonFromLayerStyle - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.panel_logging import logToUser from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import findOrCreatePath import numpy as np diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/layer_conversions.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/layer_conversions.py new file mode 100644 index 0000000..a19b47d --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/layer_conversions.py @@ -0,0 +1,2080 @@ +""" +Contains all Layer related classes and methods. +""" + +import enum +import inspect +import hashlib +import math +from typing import List, Tuple, Union +from specklepy.objects import Base +from specklepy.objects.geometry import ( + Mesh, + Point, + Line, + Curve, + Circle, + Ellipse, + Polycurve, + Arc, + Polyline, +) +import os +import time +from datetime import datetime + +from speckle.speckle.plugin_utils.helpers import ( + findFeatColors, + findOrCreatePath, + jsonFromList, + removeSpecialCharacters, + SYMBOL, + UNSUPPORTED_PROVIDERS, +) + + +from specklepy.objects.GIS.geometry import GisPolygonElement, GisNonGeometryElement +from speckle.speckle.converter.geometry.point import ( + pointToNative, + pointToNativeWithoutTransforms, +) +from specklepy.objects.GIS.CRS import CRS +from specklepy.objects.GIS.layers import VectorLayer, RasterLayer, Layer +from specklepy.objects.other import Collection + +from speckle.speckle.converter.features.feature_conversions import ( + featureToSpeckle, + rasterFeatureToSpeckle, + featureToNative, + nonGeomFeatureToNative, + cadFeatureToNative, + bimFeatureToNative, +) +from speckle.speckle.converter.layers.utils import ( + collectionsFromJson, + colorFromSpeckle, + colorFromSpeckle, + generate_qgis_app_id, + generate_qgis_raster_app_id, + getDisplayValueList, + getLayerGeomType, + getLayerAttributes, + tryCreateGroupTree, + trySaveCRS, + validateAttributeName, +) +from speckle.speckle.converter.geometry.mesh import writeMeshToShp + +from speckle.speckle.converter.layers.symbology import ( + vectorRendererToNative, + rasterRendererToNative, + rendererToSpeckle, +) + + +import numpy as np + +from speckle.speckle.utils.panel_logging import logToUser + + +GEOM_LINE_TYPES = [ + "Objects.Geometry.Line", + "Objects.Geometry.Polyline", + "Objects.Geometry.Curve", + "Objects.Geometry.Arc", + "Objects.Geometry.Circle", + "Objects.Geometry.Ellipse", + "Objects.Geometry.Polycurve", +] + + +def convertSelectedLayersToSpeckle( + baseCollection: Collection, + layers: List[Union["QgsVectorLayer", "QgsRasterLayer"]], + tree_structure: List[str], + projectCRS: "QgsCoordinateReferenceSystem", + plugin, +) -> List[Union[VectorLayer, RasterLayer]]: + """Converts the current selected layers to Speckle""" + dataStorage = plugin.dataStorage + result = [] + try: + project: QgsProject = plugin.project + + ## Generate dictionnary from the list of layers to send + jsonTree = {} + for i, layer in enumerate(layers): + structure = tree_structure[i] + + if structure.startswith(SYMBOL): + structure = structure[len(SYMBOL) :] + + levels = structure.split(SYMBOL) + while "" in levels: + levels.remove("") + + jsonTree = jsonFromList(jsonTree, levels) + + for i, layer in enumerate(layers): + data_provider_type = ( + layer.providerType() + ) # == ogr, memory, gdal, delimitedtext + if data_provider_type in UNSUPPORTED_PROVIDERS: + logToUser( + f"Layer '{layer.name()}' has unsupported provider type '{data_provider_type}' and cannot be sent", + level=2, + plugin=plugin.dockwidget, + ) + return None + + logToUser( + f"Converting layer '{layer.name()}'...", + level=0, + plugin=plugin.dockwidget, + ) + try: + for item in plugin.dataStorage.savedTransforms: + layer_name = item.split(" -> ")[0].split(" ('")[0] + transform_name = item.split(" -> ")[1] + if layer_name == layer.name(): + logToUser( + f"Applying transformation to layer '{layer_name}': '{transform_name}'", + level=0, + plugin=plugin.dockwidget, + ) + except Exception as e: + print(e) + + if plugin.dataStorage.savedTransforms is not None: + for item in plugin.dataStorage.savedTransforms: + layer_name = item.split(" -> ")[0].split(" ('")[0] + transform_name = item.split(" -> ")[1].lower() + + # check all the conditions for transform + if ( + isinstance(layer, QgsVectorLayer) + and layer.name() == layer_name + and "extrude" in transform_name + and "polygon" in transform_name + ): + if plugin.dataStorage.project.crs().isGeographic(): + logToUser( + "Extrusion cannot be applied when the project CRS is set to Geographic type", + level=2, + plugin=plugin.dockwidget, + ) + return None + + attribute = None + if " ('" in item: + attribute = item.split(" ('")[1].split("') ")[0] + if ( + attribute is None + or str(attribute) not in layer.fields().names() + ) and "ignore" in transform_name: + logToUser( + "Attribute for extrusion not found", + level=2, + plugin=plugin.dockwidget, + ) + return None + + elif ( + isinstance(layer, QgsRasterLayer) + and layer.name() == layer_name + and "elevation" in transform_name + ): + if plugin.dataStorage.project.crs().isGeographic(): + logToUser( + "Raster layer transformation cannot be applied when the project CRS is set to Geographic type", + level=2, + plugin=plugin.dockwidget, + ) + return None + + converted = layerToSpeckle(layer, projectCRS, plugin) + # print(converted) + if converted is not None: + structure = tree_structure[i] + if structure.startswith(SYMBOL): + structure = structure[len(SYMBOL) :] + levels = structure.split(SYMBOL) + while "" in levels: + levels.remove("") + + baseCollection = collectionsFromJson( + jsonTree, levels, converted, baseCollection + ) + else: + logToUser( + f"Layer '{layer.name()}' conversion failed", + level=2, + plugin=plugin.dockwidget, + ) + + return baseCollection + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return baseCollection + + +def layerToSpeckle( + selectedLayer: Union["QgsVectorLayer", "QgsRasterLayer"], + projectCRS: "QgsCoordinateReferenceSystem", + plugin, +) -> Union[ + VectorLayer, RasterLayer +]: # now the input is QgsVectorLayer instead of qgis._core.QgsLayerTreeLayer + """Converts a given QGIS Layer to Speckle""" + try: + # print("___layerToSpeckle") + dataStorage = plugin.dataStorage + project: QgsProject = plugin.project + layerName = selectedLayer.name() + + crs = selectedLayer.crs() + + offset_x = plugin.dataStorage.crs_offset_x + offset_y = plugin.dataStorage.crs_offset_y + rotation = plugin.dataStorage.crs_rotation + + units_proj = plugin.dataStorage.currentUnits + units_layer_native = str(QgsUnitTypes.encodeUnit(crs.mapUnits())) + + units_layer = units_layer_native + if crs.isGeographic(): + units_layer = "m" ## specklepy.logging.exceptions.SpeckleException: SpeckleException: Could not understand what unit degrees is referring to. Please enter a valid unit (eg ['mm', 'cm', 'm', 'in', 'ft', 'yd', 'mi']). + + if "unknown" in units_layer: + units_layer = "m" # if no-geometry layer + layerObjs = [] + + # Convert CRS to speckle, use the projectCRS + speckleReprojectedCrs = CRS( + authority_id=projectCRS.authid(), + name=str(projectCRS.description()), + wkt=projectCRS.toWkt(), + units=units_proj, + offset_x=offset_x, + offset_y=offset_y, + rotation=rotation, + ) + layerCRS = CRS( + authority_id=crs.authid(), + name=str(crs.description()), + wkt=crs.toWkt(), + units=units_layer, + units_native=units_layer_native, + offset_x=offset_x, + offset_y=offset_y, + rotation=rotation, + ) + + renderer = selectedLayer.renderer() + layerRenderer = rendererToSpeckle(renderer) + + if isinstance(selectedLayer, QgsVectorLayer): + fieldnames = [] # [str(field.name()) for field in selectedLayer.fields()] + attributes = Base() + for field in selectedLayer.fields(): + fieldnames.append(str(field.name())) + corrected = validateAttributeName(str(field.name()), []) + attribute_type = field.type() + r""" + all_types = [ + (1, "bool"), + (2, "int"), + (6, "decimal"), + (8, "map"), + (9, "int_list"), + (10, "string"), + (11, "string_list"), + (12, "binary"), + (14, "date"), + (15, "time"), + (16, "date_time") + ] + for att_type in all_types: + if attribute_type == att_type[0]: + attribute_type = att_type[1] + """ + attributes[corrected] = attribute_type + + extrusionApplied = isAppliedLayerTransformByKeywords( + selectedLayer, ["extrude", "polygon"], [], plugin.dataStorage + ) + + if extrusionApplied is True: + if not layerName.endswith("_as_Mesh"): + layerName += "_as_Mesh" + + geomType = getLayerGeomType(selectedLayer) + features = selectedLayer.getFeatures() + + elevationLayer = getElevationLayer(plugin.dataStorage) + projectingApplied = isAppliedLayerTransformByKeywords( + selectedLayer, + ["extrude", "polygon", "project", "elevation"], + [], + plugin.dataStorage, + ) + if projectingApplied is True and elevationLayer is None: + logToUser( + f"Elevation layer is not found. Layer '{selectedLayer.name()}' will not be projected on a 3d elevation.", + level=1, + plugin=plugin.dockwidget, + ) + + # write features + all_errors_count = 0 + for i, f in enumerate(features): + dataStorage.latestActionFeaturesReport.append( + {"feature_id": str(i + 1), "obj_type": "", "errors": ""} + ) + b = featureToSpeckle( + fieldnames, + f, + geomType, + selectedLayer, + plugin.dataStorage, + ) + # if b is None: continue + + if ( + extrusionApplied is True + and isinstance(b, GisPolygonElement) + and isinstance(b.geometry, list) + ): + # b.attributes["Speckle_ID"] = str(i+1) # not needed + for g in b.geometry: + if g is not None and g != "None": + # remove native polygon props, if extruded: + g.boundary = None + g.voids = None + + if isinstance(b, Base): + b.applicationId = generate_qgis_app_id(b, selectedLayer, f) + + layerObjs.append(b) + if ( + dataStorage.latestActionFeaturesReport[ + len(dataStorage.latestActionFeaturesReport) - 1 + ]["errors"] + != "" + ): + all_errors_count += 1 + + # Convert layer to speckle + layerBase = VectorLayer( + units=units_proj, + applicationId=hashlib.md5( + selectedLayer.id().encode("utf-8") + ).hexdigest(), + name=layerName, + crs=speckleReprojectedCrs, + elements=layerObjs, + attributes=attributes, + geomType=geomType, + ) + if all_errors_count == 0: + dataStorage.latestActionReport.append( + { + "feature_id": layerName, + "obj_type": layerBase.speckle_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "feature_id": layerName, + "obj_type": layerBase.speckle_type, + "errors": f"{all_errors_count} features failed", + } + ) + for item in dataStorage.latestActionFeaturesReport: + dataStorage.latestActionReport.append(item) + + layerBase.renderer = layerRenderer + # layerBase.applicationId = selectedLayer.id() + + return layerBase + + elif isinstance(selectedLayer, QgsRasterLayer): + # write feature attributes + b = rasterFeatureToSpeckle(selectedLayer, projectCRS, project, plugin) + b.applicationId = generate_qgis_raster_app_id(selectedLayer) + if b is None: + dataStorage.latestActionReport.append( + { + "feature_id": layerName, + "obj_type": "Raster Layer", + "errors": "Layer failed to send", + } + ) + return None + layerObjs.append(b) + # Convert layer to speckle + layerBase = RasterLayer( + units=units_proj, + applicationId=hashlib.md5( + selectedLayer.id().encode("utf-8") + ).hexdigest(), + name=layerName, + crs=speckleReprojectedCrs, + rasterCrs=layerCRS, + elements=layerObjs, + ) + dataStorage.latestActionReport.append( + { + "feature_id": layerName, + "obj_type": layerBase.speckle_type, + "errors": "", + } + ) + + layerBase.renderer = layerRenderer + # layerBase.applicationId = selectedLayer.id() + return layerBase + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + dataStorage.latestActionReport.append( + { + "feature_id": layerName, + "obj_type": "", + "errors": f"Layer conversion failed: {e}", + } + ) + return None + + +def layerToNative( + layer: Union[Layer, VectorLayer, RasterLayer], + streamBranch: str, + nameBase: str, + plugin, +) -> Union["QgsVectorLayer", "QgsRasterLayer", None]: + try: + project: QgsProject = plugin.project + # plugin.dataStorage.currentCRS = project.crs() + + if isinstance(layer.collectionType, str) and layer.collectionType.endswith( + "VectorLayer" + ): + vectorLayerToNative(layer, streamBranch, nameBase, plugin) + return + elif isinstance(layer.collectionType, str) and layer.collectionType.endswith( + "RasterLayer" + ): + rasterLayerToNative(layer, streamBranch, nameBase, plugin) + return + # if collectionType exists but not defined + elif isinstance(layer.type, str) and layer.type.endswith( + "VectorLayer" + ): # older commits + vectorLayerToNative(layer, streamBranch, nameBase, plugin) + return + elif isinstance(layer.type, str) and layer.type.endswith( + "RasterLayer" + ): # older commits + rasterLayerToNative(layer, streamBranch, nameBase, plugin) + return + except: + try: + if isinstance(layer.type, str) and layer.type.endswith( + "VectorLayer" + ): # older commits + vectorLayerToNative(layer, streamBranch, nameBase, plugin) + return + elif isinstance(layer.type, str) and layer.type.endswith( + "RasterLayer" + ): # older commits + rasterLayerToNative(layer, streamBranch, nameBase, plugin) + return + + return + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def nonGeometryLayerToNative( + geomList: List[Base], nameBase: str, val_id: str, streamBranch: str, plugin +): + # print("01_____NON-GEOMETRY layer to native") + + try: + layerName = removeSpecialCharacters(nameBase) + newFields = getLayerAttributes(geomList) + + if plugin.dataStorage.latestHostApp.endswith("excel"): + plugin.dockwidget.signal_6.emit( + { + "plugin": plugin, + "layerName": layerName, + "val_id": val_id, + "streamBranch": streamBranch, + "newFields": newFields, + "geomList": geomList, + } + ) + else: + plugin.dockwidget.signal_5.emit( + { + "plugin": plugin, + "layerName": layerName, + "layer_id": val_id, + "streamBranch": streamBranch, + "newFields": newFields, + "geomList": geomList, + } + ) + + return + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def addExcelMainThread(obj: Tuple): + # print("___addExcelMainThread") + try: + finalName = "" + plugin = obj["plugin"] + layerName = obj["layerName"] + streamBranch = obj["streamBranch"] + val_id = obj["val_id"] + newFields = obj["newFields"] + geomList = obj["geomList"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + dataStorage = plugin.dataStorage + project: QgsProject = plugin.dataStorage.project + + geomType = "None" + geom_print = "Table" + + shortName = layerName.split(SYMBOL)[len(layerName.split(SYMBOL)) - 1][:50] + try: + layerName = layerName.split(shortName)[0] + shortName + ("_" + geom_print) + except: + layerName = layerName + ("_" + geom_print) + finalName = shortName + ("_" + geom_print) + + try: + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + except: + groupName = streamBranch + SYMBOL + layerName + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + + dataStorage.latestActionLayers.append(finalName) + + ########################################### + + # get features and attributes + fets = [] + report_features = [] + all_feature_errors_count = 0 + # print("before newFields") + # print(newFields) + for f in geomList: + # print(f) + # pre-fill report: + report_features.append( + {"speckle_id": f.id, "obj_type": f.speckle_type, "errors": ""} + ) + + new_feat = nonGeomFeatureToNative(f, newFields, plugin.dataStorage) + if new_feat is not None and new_feat != "": + fets.append(new_feat) + else: + logToUser( + f"Table feature skipped due to invalid data", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": "Table feature skipped due to invalid data"} + ) + all_feature_errors_count += 1 + + if newFields is None: + newFields = QgsFields() + + # print("04") + vl = None + vl = QgsVectorLayer( + geomType + "?crs=" + "WGS84", finalName, "memory" + ) # do something to distinguish: stream_id_latest_name + project.addMapLayer(vl, False) + pr = vl.dataProvider() + vl.startEditing() + + # add Layer attribute fields + pr.addAttributes(newFields.toList()) + vl.updateFields() + + pr.addFeatures(fets) + vl.updateExtents() + vl.commitChanges() + + # print("07") + layerGroup.addLayer(vl) + + # report + all_feature_errors_count = 0 + for item in report_features: + if item["errors"] != "": + all_feature_errors_count += 1 + + # print("11") + obj_type = "Vector Layer" + if all_feature_errors_count == 0: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{val_id} {finalName}", + "obj_type": obj_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{val_id} {finalName}", + "obj_type": obj_type, + "errors": f"{all_feature_errors_count} features failed", + } + ) + + # print("12") + for item in report_features: + dataStorage.latestActionReport.append(item) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report + obj_type = "Vector Layer" + dataStorage.latestActionReport.append( + { + "speckle_id": f"{val_id} {finalName}", + "obj_type": obj_type, + "errors": f"{e}", + } + ) + dataStorage.latestConversionTime = datetime.now() + + +def addNonGeometryMainThread(obj: Tuple): + # print("___addCadMainThread") + try: + finalName = "" + plugin = obj["plugin"] + layerName = obj["layerName"] + layer_id = obj["layer_id"] + streamBranch = obj["streamBranch"] + newFields = obj["newFields"] + geomList = obj["geomList"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + project: QgsProject = plugin.dataStorage.project + dataStorage = plugin.dataStorage + + geomType = "None" + geom_print = "Table" + + shortName = layerName.split(SYMBOL)[len(layerName.split(SYMBOL)) - 1][:50] + try: + layerName = layerName.split(shortName)[0] + shortName + ("_" + geom_print) + except: + layerName = layerName + ("_" + geom_print) + finalName = shortName + ("_" + geom_print) + + try: + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + except: + groupName = streamBranch + SYMBOL + layerName + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + + dataStorage.latestActionLayers.append(finalName) + + crs = project.crs() # QgsCoordinateReferenceSystem.fromWkt(layer.crs.wkt) + plugin.dataStorage.currentUnits = str(QgsUnitTypes.encodeUnit(crs.mapUnits())) + if ( + plugin.dataStorage.currentUnits is None + or plugin.dataStorage.currentUnits == "degrees" + ): + plugin.dataStorage.currentUnits = "m" + # authid = trySaveCRS(crs, streamBranch) + + if crs.isGeographic is True: + logToUser( + f"Project CRS is set to Geographic type, and objects in linear units might not be received correctly", + level=1, + func=inspect.stack()[0][3], + ) + + vl = QgsVectorLayer( + geomType + "?crs=" + crs.authid(), finalName, "memory" + ) # do something to distinguish: stream_id_latest_name + vl.setCrs(crs) + project.addMapLayer(vl, False) + + pr = vl.dataProvider() + vl.startEditing() + + # create list of Features (fets) and list of Layer fields (fields) + attrs = QgsFields() + fets = [] + fetIds = [] + fetColors = [] + + report_features = [] + all_feature_errors_count = 0 + for f in geomList[:]: + # pre-fill report: + report_features.append( + {"speckle_id": f.id, "obj_type": f.speckle_type, "errors": ""} + ) + + new_feat = nonGeomFeatureToNative(f, newFields, plugin.dataStorage) + # update attrs for the next feature (if more fields were added from previous feature) + + # print("________cad feature to add") + if new_feat is not None and new_feat != "": + fets.append(new_feat) + for a in newFields.toList(): + attrs.append(a) + + pr.addAttributes( + newFields + ) # add new attributes from the current object + fetIds.append(f.id) + fetColors = findFeatColors(fetColors, f) + else: + logToUser( + f"Table feature skipped due to invalid data", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": "Table feature skipped due to invalid data"} + ) + all_feature_errors_count += 1 + + # add Layer attribute fields + pr.addAttributes(newFields) + vl.updateFields() + + # pr = vl.dataProvider() + pr.addFeatures(fets) + vl.updateExtents() + vl.commitChanges() + + layerGroup.addLayer(vl) + + # report + obj_type = ( + geom_print + " Vector Layer" + if "Mesh" not in geom_print + else "Multipolygon Vector Layer" + ) + if all_feature_errors_count == 0: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{all_feature_errors_count} features failed", + } + ) + for item in report_features: + dataStorage.latestActionReport.append(item) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report + obj_type = geom_print + "Vector Layer" + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{e}", + } + ) + dataStorage.latestConversionTime = datetime.now() + + +def geometryLayerToNative( + layerContentList: List[Base], + layerName: str, + val_id: str, + streamBranch: str, + plugin, + matrix=None, +): + # print("01_____GEOMETRY layer to native") + try: + # print(layerContentList) + geom_meshes = [] + + geom_points = [] + geom_polylines = [] + + layer_points = None + layer_polylines = None + # geom_meshes = [] + val = None + + # filter speckle objects by type within each layer, create sub-layer for each type (points, lines, polygons, mesh?) + for geom in layerContentList: + # print(geom) + if isinstance(geom, Point): + geom_points.append(geom) + continue + elif ( + isinstance(geom, Line) + or isinstance(geom, Polyline) + or isinstance(geom, Curve) + or isinstance(geom, Arc) + or isinstance(geom, Circle) + or isinstance(geom, Ellipse) + or isinstance(geom, Polycurve) + ): + geom_polylines.append(geom) + continue + try: + if ( + geom.speckle_type.endswith(".ModelCurve") + and geom["baseCurve"].speckle_type in GEOM_LINE_TYPES + ): + geom_polylines.append(geom["baseCurve"]) + continue + elif geom["baseLine"].speckle_type in GEOM_LINE_TYPES: + geom_polylines.append(geom["baseLine"]) + # don't skip the rest if baseLine is found + except: + pass # check for the Meshes + + # ________________get list of display values for Meshes___________________________ + val = getDisplayValueList(geom) + # print(val) # List of Meshes + + if isinstance(val, List) and len(val) > 0 and isinstance(val[0], Mesh): + # print("__________GET ACTUAL ELEMENT BEFORE DISPLAY VALUE") + # print(val[0]) # Mesh + + if isinstance(geom, List): + geom_meshes.extend(geom) + else: + geom_meshes.append(geom) + # print("__GEOM MESHES") + # print(geom_meshes) + + if len(geom_meshes) > 0: + bimVectorLayerToNative( + geom_meshes, layerName, val_id, "Mesh", streamBranch, plugin, matrix + ) + if len(geom_points) > 0: + cadVectorLayerToNative( + geom_points, layerName, val_id, "Points", streamBranch, plugin, matrix + ) + if len(geom_polylines) > 0: + cadVectorLayerToNative( + geom_polylines, + layerName, + val_id, + "Polylines", + streamBranch, + plugin, + matrix, + ) + + return True + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def bimVectorLayerToNative( + geomList: List[Base], + layerName_old: str, + val_id: str, + geomType: str, + streamBranch: str, + plugin, + matrix: list = None, +): + # print("02_________BIM vector layer to native_____") + try: + # project: QgsProject = plugin.project + # print(layerName_old) + + layerName = layerName_old # [:50] + layerName = removeSpecialCharacters(layerName) + # print(layerName) + + # get Project CRS, use it by default for the new received layer + vl = None + # layerName = layerName + "_" + geomType + # print(layerName) + + if "mesh" in geomType.lower(): + geomType = "MultiPolygonZ" + + newFields = getLayerAttributes(geomList) + # print("___________Layer fields_____________") + # print(newFields.toList()) + + plugin.dockwidget.signal_2.emit( + { + "plugin": plugin, + "geomType": geomType, + "layerName": layerName, + "layer_id": val_id, + "streamBranch": streamBranch, + "newFields": newFields, + "geomList": geomList, + "matrix": matrix, + } + ) + + return + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def addBimMainThread(obj: Tuple): + try: + finalName = "" + plugin = obj["plugin"] + geomType = obj["geomType"] + layerName = obj["layerName"] + layer_id = obj["layer_id"] + streamBranch = obj["streamBranch"] + newFields = obj["newFields"] + geomList = obj["geomList"] + matrix = obj["matrix"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + dataStorage = plugin.dataStorage + dataStorage.matrix = matrix + report_features = [] + + project: QgsProject = dataStorage.project + + geom_print = geomType + if "MultiPolygonZ" in geom_print: + geom_print = "Mesh" + elif "LineStringZ" in geom_print: + geom_print = "Polyline" + elif "PointZ" in geom_print: + geom_print = "Point" + + shortName = layerName.split(SYMBOL)[len(layerName.split(SYMBOL)) - 1][:50] + # print(f"Final short name: {shortName}") + try: + layerName = ( + layerName.split(shortName)[0] + shortName + ("_as_" + geom_print) + ) + except: + layerName = layerName + ("_as_" + geom_print) + finalName = shortName + ("_as_" + geom_print) + dataStorage.latestActionLayers.append(finalName) + + try: + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + except: + groupName = streamBranch + SYMBOL + layerName + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + + # newName = f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}' + newName_shp = f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}/{finalName[:30]}' + + crs = project.crs() # QgsCoordinateReferenceSystem.fromWkt(layer.crs.wkt) + dataStorage.currentUnits = str(QgsUnitTypes.encodeUnit(crs.mapUnits())) + if dataStorage.currentUnits is None or dataStorage.currentUnits == "degrees": + dataStorage.currentUnits = "m" + + if crs.isGeographic is True: + logToUser( + f"Project CRS is set to Geographic type, and objects in linear units might not be received correctly", + level=1, + func=inspect.stack()[0][3], + ) + + p = ( + os.path.expandvars(r"%LOCALAPPDATA%") + + "\\Temp\\Speckle_QGIS_temp\\" + + datetime.now().strftime("%Y-%m-%d_%H-%M") + ) + findOrCreatePath(p) + path = p + # logToUser(f"BIM layers can only be received in an existing saved project. Layer {layerName} will be ignored", level = 1, func = inspect.stack()[0][3]) + + path_bim = ( + path + + "/Layers_Speckle/BIM_layers/" + + streamBranch + + "/" + + layerName[:30] + + "/" + ) # arcpy.env.workspace + "\\" # + + findOrCreatePath(path_bim) + # print(path_bim) + + shp = writeMeshToShp(geomList, path_bim + newName_shp, dataStorage) + dataStorage.matrix = None + if shp is None: + return + # print("____ meshes saved___") + # print(shp) + + vl_shp = QgsVectorLayer( + shp + ".shp", finalName, "ogr" + ) # do something to distinguish: stream_id_latest_name + vl = QgsVectorLayer( + geomType + "?crs=" + crs.authid(), finalName, "memory" + ) # do something to distinguish: stream_id_latest_name + vl.setCrs(crs) + project.addMapLayer(vl, False) + + pr = vl.dataProvider() + vl.startEditing() + + # add Layer attribute fields + pr.addAttributes(newFields) + vl.updateFields() + + # create list of Features (fets) and list of Layer fields (fields) + # attrs = QgsFields() + fets = [] + fetIds = [] + fetColors = [] + + report_features = [] + all_feature_errors_count = 0 + for i, f in enumerate(geomList[:]): + # pre-fill report: + report_features.append( + {"speckle_id": f.id, "obj_type": f.speckle_type, "errors": ""} + ) + + try: + exist_feat: None = None + for n, shape in enumerate(vl_shp.getFeatures()): + if shape["speckle_id"] == f.id: + exist_feat = vl_shp.getFeature(n) + break + if exist_feat is None: + logToUser( + f"Feature skipped due to invalid geometry", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": "Feature skipped due to invalid geometry"} + ) + continue + + new_feat = bimFeatureToNative( + exist_feat, f, vl.fields(), crs, path_bim, dataStorage + ) + if new_feat is not None and new_feat != "": + fetColors = findFeatColors(fetColors, f) + fets.append(new_feat) + vl.addFeature(new_feat) + fetIds.append(f.id) + else: + logToUser( + f"Feature skipped due to invalid geometry", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": "Feature skipped due to invalid geometry"} + ) + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + report_features[len(report_features) - 1].update({"errors": f"{e}"}) + + vl.updateExtents() + vl.commitChanges() + + layerGroup.addLayer(vl) + + try: + ################################### RENDERER 3D ########################################### + # rend3d = QgsVectorLayer3DRenderer() # https://qgis.org/pyqgis/3.16/3d/QgsVectorLayer3DRenderer.html?highlight=layer3drenderer#module-QgsVectorLayer3DRenderer + + plugin_dir = os.path.dirname(__file__) + renderer3d = os.path.join(plugin_dir, "renderer3d.qml") + # print(renderer3d) + + vl.loadNamedStyle(renderer3d) + vl.triggerRepaint() + except: + pass + + try: + ################################### RENDERER ########################################### + # only set up renderer if the layer is just created + attribute = "Speckle_ID" + categories = [] + for i in range(len(fets)): + material = fetColors[i] + color = colorFromSpeckle(material) + + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) + symbol.setColor(color) + categories.append( + QgsRendererCategory(fetIds[i], symbol, fetIds[i], True) + ) + # create empty category for all other values + symbol2 = symbol.clone() + symbol2.setColor(QColor.fromRgb(245, 245, 245)) + cat = QgsRendererCategory() + cat.setSymbol(symbol2) + cat.setLabel("Other") + categories.append(cat) + rendererNew = QgsCategorizedSymbolRenderer(attribute, categories) + except Exception as e: + print(e) + + try: + vl.setRenderer(rendererNew) + except: + pass + + try: + project.removeMapLayer(dummy) + except: + pass + + # report + obj_type = ( + geom_print + " Vector Layer" + if "Mesh" not in geom_print + else "Multipolygon Vector Layer" + ) + if all_feature_errors_count == 0: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{all_feature_errors_count} features failed", + } + ) + for item in report_features: + dataStorage.latestActionReport.append(item) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report + obj_type = geom_print + "Vector Layer" + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{e}", + } + ) + dataStorage.latestConversionTime = datetime.now() + + +def cadVectorLayerToNative( + geomList: List[Base], + layerName: str, + val_id: str, + geomType: str, + streamBranch: str, + plugin, + matrix=None, +) -> "QgsVectorLayer": + # print("___________cadVectorLayerToNative") + try: + project: QgsProject = plugin.project + + # get Project CRS, use it by default for the new received layer + vl = None + + layerName = removeSpecialCharacters(layerName) + + # layerName = layerName + "_" + geomType + # print(layerName) + + newName = ( + f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}/{layerName}' + ) + + if geomType == "Points": + geomType = "PointZ" + elif geomType == "Polylines": + geomType = "LineStringZ" + + newFields = getLayerAttributes(geomList) + # print(newFields.toList()) + # print(geomList) + + plugin.dockwidget.signal_3.emit( + { + "plugin": plugin, + "geomType": geomType, + "layerName": layerName, + "layer_id": val_id, + "streamBranch": streamBranch, + "newFields": newFields, + "geomList": geomList, + "matrix": matrix, + } + ) + + return + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def addCadMainThread(obj: Tuple): + # print("___addCadMainThread") + try: + finalName = "" + plugin = obj["plugin"] + geomType = obj["geomType"] + layerName = obj["layerName"] + layer_id = obj["layer_id"] + streamBranch = obj["streamBranch"] + newFields = obj["newFields"] + geomList = obj["geomList"] + matrix = obj["matrix"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + project: QgsProject = plugin.dataStorage.project + dataStorage = plugin.dataStorage + dataStorage.matrix = matrix + + geom_print = geomType + if "MultiPolygonZ" in geom_print: + geom_print = "Mesh" + elif "LineStringZ" in geom_print: + geom_print = "Polyline" + elif "PointZ" in geom_print: + geom_print = "Point" + + shortName = layerName.split(SYMBOL)[len(layerName.split(SYMBOL)) - 1][:50] + + try: + layerName = ( + layerName.split(shortName)[0] + shortName + ("_as_" + geom_print) + ) + except: + layerName = layerName + ("_as_" + geom_print) + finalName = shortName + ("_as_" + geom_print) + + try: + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + except: + groupName = streamBranch + SYMBOL + layerName + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + + dataStorage.latestActionLayers.append(finalName) + + crs = project.crs() # QgsCoordinateReferenceSystem.fromWkt(layer.crs.wkt) + plugin.dataStorage.currentUnits = str(QgsUnitTypes.encodeUnit(crs.mapUnits())) + if ( + plugin.dataStorage.currentUnits is None + or plugin.dataStorage.currentUnits == "degrees" + ): + plugin.dataStorage.currentUnits = "m" + # authid = trySaveCRS(crs, streamBranch) + + if crs.isGeographic is True: + logToUser( + f"Project CRS is set to Geographic type, and objects in linear units might not be received correctly", + level=1, + func=inspect.stack()[0][3], + ) + + vl = QgsVectorLayer( + geomType + "?crs=" + crs.authid(), finalName, "memory" + ) # do something to distinguish: stream_id_latest_name + vl.setCrs(crs) + project.addMapLayer(vl, False) + + pr = vl.dataProvider() + vl.startEditing() + + # create list of Features (fets) and list of Layer fields (fields) + attrs = QgsFields() + fets = [] + fetIds = [] + fetColors = [] + + report_features = [] + all_feature_errors_count = 0 + for f in geomList[:]: + # pre-fill report: + report_features.append( + {"speckle_id": f.id, "obj_type": f.speckle_type, "errors": ""} + ) + + new_feat = cadFeatureToNative(f, newFields, plugin.dataStorage) + # update attrs for the next feature (if more fields were added from previous feature) + + # print("________cad feature to add") + if new_feat is not None and new_feat != "": + fets.append(new_feat) + for a in newFields.toList(): + attrs.append(a) + + pr.addAttributes( + newFields + ) # add new attributes from the current object + fetIds.append(f.id) + fetColors = findFeatColors(fetColors, f) + else: + logToUser( + f"Feature skipped due to invalid geometry", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": "Feature skipped due to invalid geometry"} + ) + all_feature_errors_count += 1 + + dataStorage.matrix = None + + # add Layer attribute fields + pr.addAttributes(newFields) + vl.updateFields() + + # pr = vl.dataProvider() + pr.addFeatures(fets) + vl.updateExtents() + vl.commitChanges() + + layerGroup.addLayer(vl) + + ################################### RENDERER ########################################### + # only set up renderer if the layer is just created + attribute = "Speckle_ID" + categories = [] + for i in range(len(fets)): + rgb = fetColors[i] + if rgb: + r = (rgb & 0xFF0000) >> 16 + g = (rgb & 0xFF00) >> 8 + b = rgb & 0xFF + color = QColor.fromRgb(r, g, b) + else: + color = QColor.fromRgb(0, 0, 0) + + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) + symbol.setColor(color) + categories.append(QgsRendererCategory(fetIds[i], symbol, fetIds[i], True)) + # create empty category for all other values + symbol2 = symbol.clone() + symbol2.setColor(QColor.fromRgb(0, 0, 0)) + cat = QgsRendererCategory() + cat.setSymbol(symbol2) + cat.setLabel("Other") + categories.append(cat) + rendererNew = QgsCategorizedSymbolRenderer(attribute, categories) + + try: + vl.setRenderer(rendererNew) + except: + pass + + try: + ################################### RENDERER 3D ########################################### + # rend3d = QgsVectorLayer3DRenderer() # https://qgis.org/pyqgis/3.16/3d/QgsVectorLayer3DRenderer.html?highlight=layer3drenderer#module-QgsVectorLayer3DRenderer + + plugin_dir = os.path.dirname(__file__) + renderer3d = os.path.join(plugin_dir, "renderer3d.qml") + # print(renderer3d) + + vl.loadNamedStyle(renderer3d) + vl.triggerRepaint() + except: + pass + + try: + project.removeMapLayer(dummy) + except: + pass + + # report + obj_type = ( + geom_print + " Vector Layer" + if "Mesh" not in geom_print + else "Multipolygon Vector Layer" + ) + if all_feature_errors_count == 0: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{all_feature_errors_count} features failed", + } + ) + for item in report_features: + dataStorage.latestActionReport.append(item) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report + obj_type = geom_print + "Vector Layer" + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer_id} {finalName}", + "obj_type": obj_type, + "errors": f"{e}", + } + ) + dataStorage.latestConversionTime = datetime.now() + + +def vectorLayerToNative( + layer: Layer or VectorLayer, streamBranch: str, nameBase: str, plugin +): + try: + # print("vectorLayerToNative") + project: QgsProject = plugin.project + layerName = removeSpecialCharacters(nameBase + SYMBOL + layer.name) + # print(layerName) + + newName = layerName # f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}' + # print(newName) + + # particularly if the layer comes from ArcGIS + geomType = ( + layer.geomType + ) # for ArcGIS: Polygon, Point, Polyline, Multipoint, MultiPatch + if geomType == "Point": + geomType = "Point" + elif geomType == "Polygon": + geomType = "MultiPolygon" + elif geomType == "Polyline": + geomType = "MultiLineString" + elif geomType.lower() == "multipoint": + geomType = "MultiPoint" + elif geomType == "MultiPatch": + geomType = "Polygon" + + fets = [] + # print("before newFields") + + newFields = QgsFields() + objectEmit = { + "plugin": plugin, + "geomType": geomType, + "newName": newName, + "streamBranch": streamBranch, + "wkt": layer.crs.wkt, + "layer": layer, + "newFields": newFields, + "fets": fets, + } + plugin.dockwidget.signal_1.emit(objectEmit) + + return + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def addVectorMainThread(obj: Tuple): + # print("___addVectorMainThread") + try: + finalName = "" + plugin = obj["plugin"] + geomType = obj["geomType"] + newName = obj["newName"] + streamBranch = obj["streamBranch"] + wkt = obj["wkt"] + layer = obj["layer"] + newFields = obj["newFields"] + fets = obj["fets"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + dataStorage = plugin.dataStorage + + plugin.dataStorage.currentUnits = layer.crs.units + if ( + plugin.dataStorage.currentUnits is None + or plugin.dataStorage.currentUnits == "degrees" + ): + plugin.dataStorage.currentUnits = "m" + try: + 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 + except AttributeError as e: + print(e) + + project: QgsProject = plugin.dataStorage.project + + # print(layer.name) + + shortName = newName.split(SYMBOL)[len(newName.split(SYMBOL)) - 1][:50] + try: + layerName = newName.split(shortName)[0] + shortName # + ("_" + geom_print) + except: + layerName = newName + finalName = shortName # + ("_" + geom_print) + # print(f"Final layer name: {finalName}") + try: + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + except: + groupName = streamBranch + SYMBOL + layerName + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + + dataStorage.latestActionLayers.append(finalName) + ########################################### + + # get features and attributes + fets = [] + report_features = [] + all_feature_errors_count = 0 + # print("before newFields") + newFields = getLayerAttributes(layer.elements) + for f in layer.elements: + # pre-fill report: + report_features.append( + {"speckle_id": f.id, "obj_type": f.speckle_type, "errors": ""} + ) + + new_feat = featureToNative(f, newFields, plugin.dataStorage) + if new_feat is not None and new_feat != "": + fets.append(new_feat) + else: + logToUser( + f"'{geomType}' feature skipped due to invalid data", + level=2, + func=inspect.stack()[0][3], + ) + report_features[len(report_features) - 1].update( + {"errors": f"'{geomType}' feature skipped due to invalid data"} + ) + all_feature_errors_count += 1 + + if newFields is None: + newFields = QgsFields() + + crs = QgsCoordinateReferenceSystem.fromWkt(wkt) + srsid = trySaveCRS(crs, streamBranch) + crs_new = QgsCoordinateReferenceSystem.fromSrsId(srsid) + authid = crs_new.authid() + # print(authid) + + ################################################# + # print("03") + r""" + if "polygon" in geomType.lower(): # not newName.endswith("_Mesh") and and "Speckle_ID" in newFields.names(): + # reproject all QGIS polygon geometry to EPSG 4326 until the CRS issue is found + for i, f in enumerate(fets): + #reproject + xform = QgsCoordinateTransform(crs, QgsCoordinateReferenceSystem(4326), project) + geometry = fets[i].geometry() + geometry.transform(xform) + fets[i].setGeometry(geometry) + crs = QgsCoordinateReferenceSystem(4326) + authid = "EPSG:4326" + ################################################# + """ + + # print("05") + # layerGroup = tryCreateGroup(project, streamBranch) + + vl = QgsVectorLayer( + geomType + "?crs=" + authid, finalName, "memory" + ) # do something to distinguish: stream_id_latest_name + vl.setCrs(crs) + project.addMapLayer(vl, False) + + pr = vl.dataProvider() + vl.startEditing() + + # add Layer attribute fields + pr.addAttributes(newFields.toList()) + vl.updateFields() + + pr.addFeatures(fets) + vl.updateExtents() + vl.commitChanges() + + ################################################# + + if ( + "polygon" in geomType.lower() + ): # and "Speckle_ID" in newFields.names(): #not newName.endswith("_Mesh") and + p = ( + os.path.expandvars(r"%LOCALAPPDATA%") + + "\\Temp\\Speckle_QGIS_temp\\" + + datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ) + findOrCreatePath(p) + file_name = os.path.join(p, newName) + + options = QgsVectorFileWriter.SaveVectorOptions() + options.fileEncoding = "utf-8" + + options.driverName = "GeoJSON" + options.overrideGeometryType = QgsWkbTypes.parseType("MultiPolygonZ") + options.forceMulti = True + options.includeZ = True + writer = QgsVectorFileWriter.writeAsVectorFormatV3( + layer=vl, + fileName=file_name, + transformContext=project.transformContext(), + options=options, + ) + del writer + + # geojson writer fix + try: + with open(file_name + ".geojson", "r") as file: + lines = file.readlines() + polygonType = False + for i, line in enumerate(lines): + if '"type": "Polygon"' in line: + polygonType = True + break + + if polygonType is True: + new_lines = [] + for i, line in enumerate(lines): + # print(line) + if '"type": "Polygon"' in line: + line = line.replace( + '"type": "Polygon"', '"type": "MultiPolygonZ"' + ) + if ( + " ] ] ] " in line + and '"coordinates": [ [ [ [ ' not in line + ): + line = line.replace(" ] ] ] ", " ] ] ] ] ") + if ( + '"coordinates": [ [ [ ' in line + and '"coordinates": [ [ [ [ ' not in line + ): + line = line.replace( + '"coordinates": [ [ [ ', '"coordinates": [ [ [ [ ' + ) + new_lines.append(line) + with open(file_name + ".geojson", "w") as file: + file.writelines(new_lines) + file.close() + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + if not newName.endswith("_Mesh"): + finalName += "_Mesh" + + project.removeMapLayer(vl) + vl = QgsVectorLayer(file_name + ".geojson", finalName, "ogr") + vl.setCrs(crs) + project.addMapLayer(vl, False) + + ################################################# + + layerGroup.addLayer(vl) + + rendererNew = vectorRendererToNative(layer, newFields) + if rendererNew is None: + symbol = QgsSymbol.defaultSymbol( + QgsWkbTypes.geometryType(QgsWkbTypes.parseType(geomType)) + ) + rendererNew = QgsSingleSymbolRenderer(symbol) + + # time.sleep(3) + try: + vl.setRenderer(rendererNew) + except: + pass + + # print("08") + try: + ################################### RENDERER 3D ########################################### + # rend3d = QgsVectorLayer3DRenderer() # https://qgis.org/pyqgis/3.16/3d/QgsVectorLayer3DRenderer.html?highlight=layer3drenderer#module-QgsVectorLayer3DRenderer + + plugin_dir = os.path.dirname(__file__) + renderer3d = os.path.join(plugin_dir, "renderer3d.qml") + + vl.loadNamedStyle(renderer3d) + vl.triggerRepaint() + except: + pass + + try: + project.removeMapLayer(dummy) + except: + pass + + # report + all_feature_errors_count = 0 + for item in report_features: + if item["errors"] != "": + all_feature_errors_count += 1 + + obj_type = "Vector Layer" + if all_feature_errors_count == 0: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer.id} {finalName}", + "obj_type": obj_type, + "errors": "", + } + ) + else: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer.id} {finalName}", + "obj_type": obj_type, + "errors": f"{all_feature_errors_count} features failed", + } + ) + + for item in report_features: + dataStorage.latestActionReport.append(item) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report + obj_type = "Vector Layer" + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer.id} {finalName}", + "obj_type": obj_type, + "errors": f"{e}", + } + ) + dataStorage.latestConversionTime = datetime.now() + + +def rasterLayerToNative(layer: RasterLayer, streamBranch: str, nameBase: str, plugin): + try: + # project = plugin.project + # layerName = removeSpecialCharacters(layer.name) + "_Speckle" + layerName = removeSpecialCharacters(nameBase + SYMBOL + layer.name) + "_Speckle" + + newName = layerName # f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}' + + plugin.dockwidget.signal_4.emit( + { + "plugin": plugin, + "layerName": layerName, + "newName": newName, + "streamBranch": streamBranch, + "layer": layer, + } + ) + + return + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def addRasterMainThread(obj: Tuple): + try: + finalName = "" + plugin = obj["plugin"] + layerName = obj["layerName"] + newName = obj["newName"] + streamBranch = obj["streamBranch"] + layer = obj["layer"] + plugin.dockwidget.msgLog.removeBtnUrl("cancel") + + project: QgsProject = plugin.dataStorage.project + dataStorage = plugin.dataStorage + + plugin.dataStorage.currentUnits = layer.crs.units + if ( + plugin.dataStorage.currentUnits is None + or plugin.dataStorage.currentUnits == "degrees" + ): + plugin.dataStorage.currentUnits = "m" + + try: + plugin.dataStorage.current_layer_crs_offset_x = layer.crs.offset_x + plugin.dataStorage.current_layer_crs_offset_y = layer.crs.offset_y + plugin.dataStorage.current_layer_crs_rotation = layer.crs.rotation + except AttributeError as e: + print(e) + + shortName = newName.split(SYMBOL)[len(newName.split(SYMBOL)) - 1][:50] + # print(f"Final short name: {shortName}") + try: + layerName = newName.split(shortName)[0] + shortName # + ("_" + geom_print) + except: + layerName = newName + finalName = shortName # + ("_" + geom_print) + + # report on receive: + dataStorage.latestActionLayers.append(finalName) + + ######################## testing, only for receiving layers ################# + source_folder = project.absolutePath() + + feat = layer.elements[0] + + vl = None + crs = QgsCoordinateReferenceSystem.fromWkt( + layer.crs.wkt + ) # moved up, because CRS of existing layer needs to be rewritten + # try, in case of older version "rasterCrs" will not exist + try: + if layer.rasterCrs.wkt is None or layer.rasterCrs.wkt == "": + raise Exception + crsRasterWkt = str(layer.rasterCrs.wkt) + crsRaster = QgsCoordinateReferenceSystem.fromWkt( + layer.rasterCrs.wkt + ) # moved up, because CRS of existing layer needs to be rewritten + except: + crsRasterWkt = str(layer.crs.wkt) + crsRaster = crs + logToUser( + f"Raster layer '{layer.name}' might have been sent from the older version of plugin. Try sending it again for more accurate results.", + level=1, + plugin=plugin.dockwidget, + ) + + srsid = trySaveCRS(crsRaster, streamBranch) + crs_new = QgsCoordinateReferenceSystem.fromSrsId(srsid) + authid = crs_new.authid() + + try: + bandNames = feat.band_names + except: + bandNames = feat["Band names"] + bandValues = [feat["@(10000)" + name + "_values"] for name in bandNames] + + if source_folder == "": + p = ( + os.path.expandvars(r"%LOCALAPPDATA%") + + "\\Temp\\Speckle_QGIS_temp\\" + + datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ) + findOrCreatePath(p) + source_folder = p + logToUser( + f'Project directory not found. Raster layers will be saved to "{p}".', + level=1, + plugin=plugin.dockwidget, + ) + + path_fn = source_folder + "/Layers_Speckle/raster_layers/" + streamBranch + "/" + if not os.path.exists(path_fn): + os.makedirs(path_fn) + + fn = path_fn + layerName + ".tif" # arcpy.env.workspace + "\\" # + # fn = source_folder + '/' + newName.replace("/","_") + '.tif' #'_received_raster.tif' + driver = gdal.GetDriverByName("GTiff") + # create raster dataset + try: + ds = driver.Create( + fn, + xsize=feat.x_size, + ysize=feat.y_size, + bands=feat.band_count, + eType=gdal.GDT_Float32, + ) + except: + ds = driver.Create( + fn, + xsize=feat["X pixels"], + ysize=feat["Y pixels"], + bands=feat["Band count"], + eType=gdal.GDT_Float32, + ) + + # Write data to raster band + # No data issue: https://gis.stackexchange.com/questions/389587/qgis-set-raster-no-data-value + + try: + b_count = int(feat.band_count) # from 2.14 + except: + b_count = feat["Band count"] + + for i in range(b_count): + rasterband = np.array(bandValues[i]) + try: + rasterband = np.reshape(rasterband, (feat.y_size, feat.x_size)) + except: + rasterband = np.reshape( + rasterband, (feat["Y pixels"], feat["X pixels"]) + ) + + band = ds.GetRasterBand( + i + 1 + ) # https://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html + + # get noDataVal or use default + try: + try: + noDataVal = float(feat.noDataValue) + except: + noDataVal = float(feat["NoDataVal"][i]) # if value available + try: + band.SetNoDataValue(noDataVal) + except: + band.SetNoDataValue(float(noDataVal)) + except: + pass + + band.WriteArray(rasterband) # or "rasterband.T" + + # create GDAL transformation in format [top-left x coord, cell width, 0, top-left y coord, 0, cell height] + pt = None + ptSpeckle = None + try: + try: + pt = QgsPoint(feat.x_origin, feat.y_origin, 0) + ptSpeckle = Point( + x=feat.x_origin, y=feat.y_origin, z=0, units=feat.units + ) + except: + pt = QgsPoint(feat["X_min"], feat["Y_min"], 0) + ptSpeckle = Point( + x=feat["X_min"], y=feat["Y_min"], z=0, units=feat.units + ) + except: + try: + displayVal = feat.displayValue + except: + displayVal = feat["displayValue"] + if displayVal is not None: + if isinstance(displayVal[0], Point): + pt = pointToNativeWithoutTransforms( + displayVal[0], plugin.dataStorage + ) + ptSpeckle = displayVal[0] + if isinstance(displayVal[0], Mesh): + pt = QgsPoint(displayVal[0].vertices[0], displayVal[0].vertices[1]) + ptSpeckle = Point( + x=displayVal[0].vertices[0], + y=displayVal[0].vertices[1], + z=displayVal[0].vertices[2], + units=displayVal[0].units, + ) + if pt is None or ptSpeckle is None: + logToUser( + "Raster layer doesn't have the origin point", + level=2, + plugin=plugin.dockwidget, + ) + return + + try: # if the CRS has offset props + 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 + + pt = pointToNative( + ptSpeckle, plugin.dataStorage + ) # already transforms the offsets + dataStorage.current_layer_crs_offset_x = ( + dataStorage.current_layer_crs_offset_y + ) = dataStorage.current_layer_crs_rotation = None + + except AttributeError as e: + print(e) + xform = QgsCoordinateTransform(crs, crsRaster, project) + pt.transform(xform) + try: + ds.SetGeoTransform( + [pt.x(), feat.x_resolution, 0, pt.y(), 0, feat.y_resolution] + ) + except: + ds.SetGeoTransform( + [pt.x(), feat["X resolution"], 0, pt.y(), 0, feat["Y resolution"]] + ) + + # create a spatial reference object + ds.SetProjection(crsRasterWkt) + # close the rater datasource by setting it equal to None + ds = None + + raster_layer = QgsRasterLayer(fn, finalName, "gdal") + project.addMapLayer(raster_layer, False) + + # layerGroup = tryCreateGroup(project, streamBranch) + groupName = streamBranch + SYMBOL + layerName.split(finalName)[0] + layerGroup = tryCreateGroupTree(project.layerTreeRoot(), groupName, plugin) + layerGroup.addLayer(raster_layer) + + dataProvider = raster_layer.dataProvider() + rendererNew = rasterRendererToNative(layer, dataProvider) + + try: + raster_layer.setRenderer(rendererNew) + except: + pass + + try: + project.removeMapLayer(dummy) + except: + pass + + # report on receive: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer.id} {finalName}", + "obj_type": "Raster Layer", + "errors": "", + } + ) + dataStorage.latestConversionTime = datetime.now() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + # report on receive: + dataStorage.latestActionReport.append( + { + "speckle_id": f"{layer.id} {finalName}", + "obj_type": "Raster Layer", + "errors": f"Receiving layer {layer.name} failed", + } + ) + dataStorage.latestConversionTime = datetime.now() diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/symbology.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/symbology.py index b2eec3d..77282d5 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/symbology.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/symbology.py @@ -16,14 +16,9 @@ from arcpy.management import (CreateFeatureclass, MakeFeatureLayer, from specklepy.objects import Base from specklepy.objects.other import RenderMaterial -try: - from speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer - from speckle.speckle.ui.logger import logToUser - from speckle.speckle.plugin_utils.helpers import findOrCreatePath -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.Layer import Layer, VectorLayer, RasterLayer - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import findOrCreatePath +from specklepy.objects.GIS.layers import Layer, VectorLayer, RasterLayer +from speckle.speckle.utils.panel_logging import logToUser +from speckle.speckle.plugin_utils.helpers import findOrCreatePath def jsonFromLayerStyle(layerArcgis, path_style): # write updated renderer to file and get layerStyle variable diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/utils.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/utils.py index 2797da1..694ee93 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/utils.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/converter/layers/utils.py @@ -1,64 +1,165 @@ from datetime import datetime from typing import Dict, Any, List, Union -import json +import json +import hashlib from specklepy.objects import Base -import arcpy +from specklepy.objects.geometry import Mesh +from specklepy.objects.other import Collection + +import arcpy from arcpy._mp import ArcGISProject, Map, Layer as arcLayer import os import inspect +from PyQt5.QtGui import QColor -try: - from speckle.speckle.converter.layers.emptyLayerTemplates import createGroupLayer - from speckle.speckle.plugin_utils.helpers import findOrCreatePath - from speckle.speckle.ui.logger import logToUser - from speckle.speckle.plugin_utils.helpers import validateNewFclassName +from speckle.speckle.converter.layers.emptyLayerTemplates import createGroupLayer +from speckle.speckle.plugin_utils.helpers import findOrCreatePath, SYMBOL +from speckle.speckle.utils.panel_logging import logToUser +from speckle.speckle.plugin_utils.helpers import validateNewFclassName -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.emptyLayerTemplates import createGroupLayer - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import findOrCreatePath - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import validateNewFclassName -#ATTRS_REMOVE = ['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh', 'displayValue'] -ATTRS_REMOVE = ['speckleTyp','speckle_id','geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh', 'displayValue'] +# ATTRS_REMOVE = ['geometry','applicationId','bbox','displayStyle', 'id', 'renderMaterial', 'displayMesh', 'displayValue'] +ATTRS_REMOVE = [ + "speckleTyp", + "speckle_id", + "geometry", + "applicationId", + "bbox", + "displayStyle", + "id", + "renderMaterial", + "displayMesh", + "displayValue", +] - -def findAndClearLayerGroup(gis_project: ArcGISProject, newGroupName: str = ""): + +def generate_qgis_app_id( + base: Base, + layer, + f, +): + """Generate unique ID for Vector feature.""" + return "" + try: + fieldnames = [str(field.name()) for field in layer.fields()] + props = [str(f[prop]) for prop in fieldnames] + try: + geoms = f.geometry() + except Exception as e: + geoms = "" + + id_data: str = ( + layer.id() + + str(layer.wkbType()) + + str(fieldnames) + + str(props) + + str(geoms) + ) + return hashlib.md5(id_data.encode("utf-8")).hexdigest() + + except Exception as e: + logToUser( + f"Application ID not generated for feature in layer {layer.name()}: {e}", + level=1, + ) + return "" + + +def generate_qgis_raster_app_id(rasterLayer): + """Generate unique ID for Raster layer.""" + return "" + try: + id_data = str(get_raster_stats(rasterLayer)) + file_ds = gdal.Open(rasterLayer.source(), gdal.GA_ReadOnly) + for i in range(rasterLayer.bandCount()): + band = file_ds.GetRasterBand(i + 1) + id_data += str(band.ReadAsArray()) + return hashlib.md5(id_data.encode("utf-8")).hexdigest() + + except Exception as e: + logToUser( + f"Application ID not generated for layer {rasterLayer.name()}: {e}", + level=1, + ) + return "" + + +def collectionsFromJson( + jsonObj: dict, levels: list, layerConverted, baseCollection: Collection +): + if jsonObj == {} or len(levels) == 0: + # print("RETURN") + baseCollection.elements.append(layerConverted) + return baseCollection + + lastLevel = baseCollection + for i, l in enumerate(levels): + sub_collection_found = 0 + for item in lastLevel.elements: + # print("___ITEM") + # print(l) + if item.name == l: + # print("___ITEM FOUND") + # print(l) + lastLevel = item + sub_collection_found = 1 + break + if sub_collection_found == 0: + # print("___ SUB COLLECTION NOT FOUND") + subCollection = Collection( + units="m", collectionType="QGIS Layer Group", name=l, elements=[] + ) + lastLevel.elements.append(subCollection) + lastLevel = lastLevel.elements[ + len(lastLevel.elements) - 1 + ] # reassign last element + + if i == len(levels) - 1: # if last level + lastLevel.elements.append(layerConverted) + + return baseCollection + + +def findAndClearLayerGroup(project: ArcGISProject, newGroupName: str = ""): print("find And Clear LayerGroup") try: groupExists = 0 print(newGroupName) - for l in gis_project.activeMap.listLayers(): - #print(l.longName) + for l in project.activeMap.listLayers(): + # print(l.longName) if l.longName.startswith(newGroupName + "\\"): - #print(l.longName) + # print(l.longName) if l.isFeatureLayer: # condition for feature layers: - fields = [f.name for f in arcpy.ListFields(l.dataSource)] + fields = [f.name for f in arcpy.ListFields(l.dataSource)] print(fields) if "Speckle_ID" in fields or "speckle_id" in fields: - gis_project.activeMap.removeLayer(l) - groupExists+=1 + project.activeMap.removeLayer(l) + groupExists += 1 elif l.isRasterLayer: - # condition for raster layers: + # condition for raster layers: if "_Speckle" in l.name: - gis_project.activeMap.removeLayer(l) - groupExists+=1 + project.activeMap.removeLayer(l) + groupExists += 1 - elif l.longName == newGroupName: - groupExists+=1 + elif l.longName == newGroupName: + groupExists += 1 print(newGroupName) if groupExists == 0: # create empty group layer file "\\Layers_Speckle\\ - - path: str = os.path.expandvars(r'%LOCALAPPDATA%') + "\\Temp\\Speckle_ArcGIS_temp\\" + datetime.now().strftime("%Y-%m-%d %H-%M") + + path: str = ( + os.path.expandvars(r"%LOCALAPPDATA%") + + "\\Temp\\Speckle_ArcGIS_temp\\" + + datetime.now().strftime("%Y-%m-%d %H-%M") + ) path += "\\Layers_Speckle\\" findOrCreatePath(path) - #path = "\\".join(gis_project.filePath.split("\\")[:-1]) + "\\Layers_Speckle\\" - #findOrCreatePath(path) + # path = "\\".join(project.filePath.split("\\")[:-1]) + "\\Layers_Speckle\\" + # findOrCreatePath(path) lyr_path = path + newGroupName + ".lyrx" print(lyr_path) try: @@ -67,207 +168,375 @@ def findAndClearLayerGroup(gis_project: ArcGISProject, newGroupName: str = ""): f.write(content) f.close() newGroupLayer = arcpy.mp.LayerFile(lyr_path) - layerGroup = gis_project.activeMap.addLayer(newGroupLayer)[0] + layerGroup = project.activeMap.addLayer(newGroupLayer)[0] print(layerGroup) - except: # for 3.0.0 - if gis_project.active_map is not None: + except: # for 3.0.0 + if project.active_map is not None: print("try creating the group") - layerGroup = gis_project.activeMap.createGroupLayer(newGroupName) + layerGroup = project.activeMap.createGroupLayer(newGroupName) print(layerGroup) else: - logToUser("The map didn't fully load, try selecting the project Map or/and refreshing the plugin.", level=1, func = inspect.stack()[0][3]) + logToUser( + "The map didn't fully load, try selecting the project Map or/and refreshing the plugin.", + level=1, + func=inspect.stack()[0][3], + ) return except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) def getVariantFromValue(value: Any) -> Union[str, None]: - #print("_________get variant from value_______") + # print("_________get variant from value_______") # TODO add Base object res = None try: - pairs = [ - (str, "TEXT"), # 10 - (float, "FLOAT"), - (int, "LONG"), - (bool, "SHORT") - ] + pairs = [(str, "TEXT"), (float, "FLOAT"), (int, "LONG"), (bool, "SHORT")] # 10 for p in pairs: - if isinstance(value, p[0]): + if isinstance(value, p[0]): res = p[1] try: - if res == "LONG" and (value>= 2147483647 or value<= -2147483647): - #https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/arcgis-field-data-types.htm + if res == "LONG" and (value >= 2147483647 or value <= -2147483647): + # https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/arcgis-field-data-types.htm res = "FLOAT" - except Exception as e: print(e) + except Exception as e: + print(e) break except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return res -def getLayerAttributes(featuresList: List[Base], attrsToRemove: List[str] = ATTRS_REMOVE ) -> Dict[str, str]: + +def colorFromSpeckle(rgb): + try: + color = QColor.fromRgb(245, 245, 245) + if isinstance(rgb, int): + r = (rgb & 0xFF0000) >> 16 + g = (rgb & 0xFF00) >> 8 + b = rgb & 0xFF + color = QColor.fromRgb(r, g, b) + return color + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return QColor.fromRgb(245, 245, 245) + + +def getDisplayValueList(geom: Any) -> List: + try: + # print("___getDisplayValueList") + val = [] + # get list of display values for Meshes + if isinstance(geom, Mesh): + val = [geom] + elif isinstance(geom, List) and len(geom) > 0: + if isinstance(geom[0], Mesh): + val = geom + else: + print("not an individual geometry") + else: + try: + val = geom.displayValue # list + except Exception as e: + print(e) + try: + val = geom["@displayValue"] # list + except Exception as e: + print(e) + try: + val = geom.displayMesh + except: + pass + return val + except Exception as e: + print(e) + return [] + + +def getLayerGeomType(layer) -> str: + return + + +def tryCreateGroupTree(root, fullGroupName, plugin=None): + return + # CREATE A GROUP "received blabla" with sublayers + # print("_________CREATE GROUP TREE: " + fullGroupName) + + # receive_layer_tree: dict = plugin.receive_layer_tree + receive_layer_list = fullGroupName.split(SYMBOL) + path_list = [] + for x in receive_layer_list: + if len(x) > 0: + path_list.append(x) + group_to_create_name = path_list[0] + + layerGroup = QgsLayerTreeGroup(group_to_create_name) + if root.findGroup(group_to_create_name) is not None: + layerGroup = root.findGroup(group_to_create_name) # -> QgsLayerTreeNode + else: + layerGroup = root.insertGroup( + 0, group_to_create_name + ) # root.addChildNode(layerGroup) + layerGroup.setExpanded(True) + layerGroup.setItemVisibilityChecked(True) + + path_list.pop(0) + + if len(path_list) > 0: + layerGroup = tryCreateGroupTree(layerGroup, SYMBOL.join(path_list), plugin) + + return layerGroup + + +def validateAttributeName(name: str, fieldnames: List[str]) -> str: + try: + new_list = [x for x in fieldnames if x != name] + + corrected = name.replace("/", "_").replace(".", "_") + if corrected == "id": + corrected = "applicationId" + + for i, x in enumerate(corrected): + if corrected[0] != "_" and corrected not in new_list: + break + else: + corrected = corrected[1:] + + if len(corrected) <= 1 and len(name) > 1: + corrected = "0" + name # if the loop removed the property name completely + + return corrected + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def trySaveCRS(crs, streamBranch: str = ""): + return + try: + authid = crs.authid() + wkt = crs.toWkt() + if authid == "": + crs_id = crs.saveAsUserCrs("SpeckleCRS_" + streamBranch) + return crs_id + else: + return crs.srsid() + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def getLayerAttributes( + featuresList: List[Base], attrsToRemove: List[str] = ATTRS_REMOVE +) -> Dict[str, str]: print("03________ get layer attributes") fields = {} - try: - if not isinstance(featuresList, list): features = [featuresList] - else: features = featuresList[:] - + try: + if not isinstance(featuresList, list): + features = [featuresList] + else: + features = featuresList[:] + all_props = [] - for feature in features: - #get object properties to add as attributes + for feature in features: + # get object properties to add as attributes dynamicProps = feature.get_dynamic_member_names() for att in ATTRS_REMOVE: - try: dynamicProps.remove(att) - except: pass - + try: + dynamicProps.remove(att) + except: + pass + dynamicProps.sort() - # add field names and variands + # add field names and variands for name in dynamicProps: - #if name not in all_props: all_props.append(name) + # if name not in all_props: all_props.append(name) value = feature[name] variant = getVariantFromValue(value) - #if name == 'area': print(value); print(variant) - if not variant: variant = None #LongLong #4 + # if name == 'area': print(value); print(variant) + if not variant: + variant = None # LongLong #4 # go thought the dictionary object if value and isinstance(value, list): - #all_props.remove(name) # remove generic dict name + # all_props.remove(name) # remove generic dict name for i, val_item in enumerate(value): - newF, newVals = traverseDict( {}, {}, name+"_"+str(i), val_item) + newF, newVals = traverseDict( + {}, {}, name + "_" + str(i), val_item + ) - for i, (k,v) in enumerate(newF.items()): - if k not in all_props: all_props.append(k) - if k not in fields.keys(): fields.update({k: v}) - else: #check if the field was empty previously: + for i, (k, v) in enumerate(newF.items()): + if k not in all_props: + all_props.append(k) + if k not in fields.keys(): + fields.update({k: v}) + else: # check if the field was empty previously: oldVariant = fields[k] # replace if new one is NOT Float (too large integers) - #if oldVariant != "FLOAT" and v == "FLOAT": - # fields.update({k: v}) + # if oldVariant != "FLOAT" and v == "FLOAT": + # fields.update({k: v}) # replace if new one is NOT LongLong or IS String - if oldVariant != "TEXT" and v == "TEXT": - fields.update({k: v}) - - # add a field if not existing yet - else: # if str, Base, etc - newF, newVals = traverseDict( {}, {}, name, value) - - for i, (k,v) in enumerate(newF.items()): - if k not in all_props: all_props.append(k) - if k not in fields.keys(): fields.update({k: v}) #if variant is known - else: #check if the field was empty previously: + if oldVariant != "TEXT" and v == "TEXT": + fields.update({k: v}) + + # add a field if not existing yet + else: # if str, Base, etc + newF, newVals = traverseDict({}, {}, name, value) + + for i, (k, v) in enumerate(newF.items()): + if k not in all_props: + all_props.append(k) + if k not in fields.keys(): + fields.update({k: v}) # if variant is known + else: # check if the field was empty previously: oldVariant = fields[k] # replace if new one is NOT Float (too large integers) - #print(oldVariant, v) - #if oldVariant == "LONG" and v == "FLOAT": - # fields.update({k: v}) + # print(oldVariant, v) + # if oldVariant == "LONG" and v == "FLOAT": + # fields.update({k: v}) # replace if new one is NOT LongLong or IS String - if oldVariant != "TEXT" and v == "TEXT": - fields.update({k: v}) - #print(fields) + if oldVariant != "TEXT" and v == "TEXT": + fields.update({k: v}) + # print(fields) # replace all empty ones wit String - all_props.append("Speckle_ID") + all_props.append("Speckle_ID") for name in all_props: - if name not in fields.keys(): - fields.update({name: 'TEXT'}) + if name not in fields.keys(): + fields.update({name: "TEXT"}) print(fields) - #fields_sorted = {k: v for k, v in sorted(fields.items(), key=lambda item: item[0])} + # fields_sorted = {k: v for k, v in sorted(fields.items(), key=lambda item: item[0])} except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return fields + def traverseDict(newF: dict, newVals: dict, nam: str, val: Any): try: if isinstance(val, dict): - for i, (k,v) in enumerate(val.items()): - newF, newVals = traverseDict( newF, newVals, nam+"_"+k, v) + for i, (k, v) in enumerate(val.items()): + newF, newVals = traverseDict(newF, newVals, nam + "_" + k, v) elif isinstance(val, Base): dynamicProps = val.get_dynamic_member_names() for att in ATTRS_REMOVE: - try: dynamicProps.remove(att) - except: pass + try: + dynamicProps.remove(att) + except: + pass dynamicProps.sort() - item_dict = {} + item_dict = {} for prop in dynamicProps: item_dict.update({prop: val[prop]}) - for i, (k,v) in enumerate(item_dict.items()): - newF, newVals = traverseDict( newF, newVals, nam+"_"+k, v) - else: + for i, (k, v) in enumerate(item_dict.items()): + newF, newVals = traverseDict(newF, newVals, nam + "_" + k, v) + else: var = getVariantFromValue(val) - if var is None: - var = 'TEXT' + if var is None: + var = "TEXT" val = str(val) - #print(var) + # print(var) newF.update({nam: var}) - newVals.update({nam: val}) + newVals.update({nam: val}) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return newF, newVals + def get_scale_factor(units: str) -> float: 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, + "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 units.lower() in unit_scale.keys(): return unit_scale[units] - logToUser(f"Units {units} are not supported. Meters will be applied by default.", level=0, func = inspect.stack()[0][3]) + logToUser( + f"Units {units} are not supported. Meters will be applied by default.", + level=0, + func=inspect.stack()[0][3], + ) return 1.0 -def findTransformation(f_shape, geomType, layer_sr: arcpy.SpatialReference, projectCRS: arcpy.SpatialReference, selectedLayer: arcLayer): - #apply transformation if needed + +def findTransformation( + f_shape, + geomType, + layer_sr: arcpy.SpatialReference, + projectCRS: arcpy.SpatialReference, + selectedLayer: arcLayer, +): + # apply transformation if needed try: if layer_sr.name != projectCRS.name: tr0 = tr1 = tr2 = tr_custom = None - midSr = arcpy.SpatialReference("WGS 1984") # GCS_WGS_1984 - #print(layer_sr) + midSr = arcpy.SpatialReference("WGS 1984") # GCS_WGS_1984 + # print(layer_sr) try: transformations = arcpy.ListTransformations(layer_sr, projectCRS) - #print(transformations) - customTransformName = "layer_sr.name"+"_To_"+ projectCRS.name + # print(transformations) + customTransformName = "layer_sr.name" + "_To_" + projectCRS.name if len(transformations) == 0: try: tr1 = arcpy.ListTransformations(layer_sr, midSr)[0] tr2 = arcpy.ListTransformations(midSr, projectCRS)[0] - except: - #customGeoTransfm = "GEOGTRAN[METHOD['Geocentric_Translation'],PARAMETER['X_Axis_Translation',''],PARAMETER['Y_Axis_Translation',''],PARAMETER['Z_Axis_Translation','']]" - #CreateCustomGeoTransformation(customTransformName, layer_sr, projectCRS) + except: + # customGeoTransfm = "GEOGTRAN[METHOD['Geocentric_Translation'],PARAMETER['X_Axis_Translation',''],PARAMETER['Y_Axis_Translation',''],PARAMETER['Z_Axis_Translation','']]" + # CreateCustomGeoTransformation(customTransformName, layer_sr, projectCRS) tr_custom = customTransformName - else: - #print("else") - # choose equation based instead of file-based/grid-based method, + else: + # print("else") + # choose equation based instead of file-based/grid-based method, # to be consistent with QGIS: https://desktop.arcgis.com/en/arcmap/latest/map/projections/choosing-an-appropriate-transformation.htm selecterTr = {} for tr in transformations: - if "NTv2" not in tr and "NADCON" not in tr: - set1 = set( layer_sr.name.split("_") + projectCRS.name.split("_") ) - set2 = set( tr.split("_") ) - diff = len( set(set1).symmetric_difference(set2) ) + if "NTv2" not in tr and "NADCON" not in tr: + set1 = set( + layer_sr.name.split("_") + projectCRS.name.split("_") + ) + set2 = set(tr.split("_")) + diff = len(set(set1).symmetric_difference(set2)) selecterTr.update({tr: diff}) - selecterTr = dict(sorted(selecterTr.items(), key=lambda item: item[1])) + selecterTr = dict( + sorted(selecterTr.items(), key=lambda item: item[1]) + ) tr0 = list(selecterTr.keys())[0] - if geomType != "Point" and geomType != "Polyline" and geomType != "Polygon" and geomType != "Multipoint" and geomType != "MultiPatch": - try: logToUser("Unsupported or invalid geometry in layer " + selectedLayer.name, level=2, func = inspect.stack()[0][3]) - except: logToUser("Unsupported or invalid geometry", level=2, func = inspect.stack()[0][3]) + if ( + geomType != "Point" + and geomType != "Polyline" + and geomType != "Polygon" + and geomType != "Multipoint" + and geomType != "MultiPatch" + ): + try: + logToUser( + "Unsupported or invalid geometry in layer " + + selectedLayer.name, + level=2, + func=inspect.stack()[0][3], + ) + except: + logToUser( + "Unsupported or invalid geometry", + level=2, + func=inspect.stack()[0][3], + ) # reproject geometry using chosen transformstion(s) if tr0 is not None: @@ -280,58 +549,87 @@ def findTransformation(f_shape, geomType, layer_sr: arcpy.SpatialReference, proj else: ptgeo1 = f_shape.projectAs(projectCRS) f_shape = ptgeo1 - + except: - logToUser(f"Spatial Transformation not found for layer {selectedLayer.name}", level=2, func = inspect.stack()[0][3]) + logToUser( + f"Spatial Transformation not found for layer {selectedLayer.name}", + level=2, + func=inspect.stack()[0][3], + ) return None except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return f_shape + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return f_shape -def traverseDictByKey(d: Dict, key:str ="", result = None) -> Dict: + +def traverseDictByKey(d: Dict, key: str = "", result=None) -> Dict: print("__traverse") try: result = None - #print(d) + # print(d) for k, v in d.items(): - - try: v = json.loads(v) - except: pass + + try: + v = json.loads(v) + except: + pass if isinstance(v, dict): - #print("__dict__") - if k == key: print("__break loop"); result = v; return result - else: + # print("__dict__") + if k == key: + print("__break loop") + result = v + return result + else: result = traverseDictByKey(v, key, result) - if result is not None: return result + if result is not None: + return result if isinstance(v, list): - for item in v: - #print(item) - if isinstance(item, dict): + for item in v: + # print(item) + if isinstance(item, dict): result = traverseDictByKey(item, key, result) - if result is not None: return result + if result is not None: + return result except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None - #print("__result is: ____________") - #return result + # print("__result is: ____________") + # return result + def hsv_to_rgb(listHSV): try: h, s, v = listHSV[0], listHSV[1], listHSV[2] - if s == 0.0: v*=255; return (v, v, v) - i = int(h*6.) # XXX assume int() truncates! - f = (h*6.)-i; p,q,t = int(255*(v*(1.-s))), int(255*(v*(1.-s*f))), int(255*(v*(1.-s*(1.-f)))); v*=255; i%=6 - if i == 0: return (v, t, p) - if i == 1: return (q, v, p) - if i == 2: return (p, v, t) - if i == 3: return (p, q, v) - if i == 4: return (t, p, v) - if i == 5: return (v, p, q) - + if s == 0.0: + v *= 255 + return (v, v, v) + i = int(h * 6.0) # XXX assume int() truncates! + f = (h * 6.0) - i + p, q, t = ( + int(255 * (v * (1.0 - s))), + int(255 * (v * (1.0 - s * f))), + int(255 * (v * (1.0 - s * (1.0 - f)))), + ) + v *= 255 + i %= 6 + if i == 0: + return (v, t, p) + if i == 1: + return (q, v, p) + if i == 2: + return (p, v, t) + if i == 3: + return (p, q, v) + if i == 4: + return (t, p, v) + if i == 5: + return (v, p, q) + except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return (0,0,0) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return (0, 0, 0) + def cmyk_to_rgb(c, m, y, k, cmyk_scale, rgb_scale=255): try: @@ -340,36 +638,43 @@ def cmyk_to_rgb(c, m, y, k, cmyk_scale, rgb_scale=255): b = rgb_scale * (1.0 - y / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale)) return r, g, b except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return 0,0,0 + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return 0, 0, 0 -def newLayerGroupAndName(layerName: str, streamBranch: str, project: ArcGISProject) -> str: + +def newLayerGroupAndName( + layerName: str, streamBranch: str, project: ArcGISProject +) -> str: print("___new Layer Group and Name") layerGroup = None - newGroupName = f'{streamBranch}' + newGroupName = f"{streamBranch}" try: - #CREATE A GROUP "received blabla" with sublayers + # CREATE A GROUP "received blabla" with sublayers print(newGroupName) for l in project.activeMap.listLayers(): - if l.longName == newGroupName: layerGroup = l; break - - #find a layer with a matching name in the "latest" group - newName = f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}' + if l.longName == newGroupName: + layerGroup = l + break + + # find a layer with a matching name in the "latest" group + newName = ( + f'{streamBranch.split("_")[len(streamBranch.split("_"))-1]}_{layerName}' + ) all_layer_names = [] layerExists = 0 - for l in project.activeMap.listLayers(): + for l in project.activeMap.listLayers(): if l.longName.startswith(newGroupName + "\\"): all_layer_names.append(l.longName) - #print(all_layer_names) + # print(all_layer_names) print(newName) newName = validateNewFclassName(newName, all_layer_names, streamBranch + "\\") - print(newName) - return newName, layerGroup + print(newName) + return newName, layerGroup except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None, None @@ -379,36 +684,46 @@ def curvedFeatureClassToSegments(layer) -> str: data = arcpy.Describe(layer.dataSource) dataPath = data.catalogPath print(dataPath) - newPath = dataPath+"_backup" + newPath = dataPath + "_backup" - arcpy.management.CopyFeatures(dataPath, newPath) # features copied like this do not preserve curved segments + arcpy.management.CopyFeatures( + dataPath, newPath + ) # features copied like this do not preserve curved segments - arcpy.edit.Densify(in_features = newPath, densification_method = "ANGLE", max_angle = 0.01, max_vertex_per_segment = 100) # https://pro.arcgis.com/en/pro-app/latest/tool-reference/editing/densify.htm + arcpy.edit.Densify( + in_features=newPath, + densification_method="ANGLE", + max_angle=0.01, + max_vertex_per_segment=100, + ) # https://pro.arcgis.com/en/pro-app/latest/tool-reference/editing/densify.htm print(newPath) return newPath - + except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return None + def validate_path(path: str): """If our path contains a DB name, make sure we have a valid DB name and not a standard file name.""" try: # https://github.com/EsriOceans/btm/commit/a9c0529485c9b0baa78c1f094372c0f9d83c0aaf dirname, file_name = os.path.split(path) - #print(dirname) - #print(file_name) + # print(dirname) + # print(file_name) file_base = os.path.splitext(file_name)[0] - if dirname == '': + if dirname == "": # a relative path only, relying on the workspace dirname = arcpy.env.workspace path_ext = os.path.splitext(dirname)[1].lower() - if path_ext in ['.mdb', '.gdb', '.sde']: + if path_ext in [".mdb", ".gdb", ".sde"]: # we're working in a database - file_name = arcpy.ValidateTableName(file_base) # e.g. add a letter in front of the name + file_name = arcpy.ValidateTableName( + file_base + ) # e.g. add a letter in front of the name validated_path = os.path.join(dirname, file_name) - #msg("validated path: %s; (from %s)" % (validated_path, path)) + # msg("validated path: %s; (from %s)" % (validated_path, path)) return validated_path except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/helpers.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/helpers.py index d4ac5a1..f4e21bc 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/helpers.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/helpers.py @@ -1,99 +1,216 @@ import os from typing import List -import inspect +import inspect + +SYMBOL = "_x_x_" +UNSUPPORTED_PROVIDERS = ["WFS", "wms", "wcs", "vectortile"] + + +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] + try: + from speckle.speckle.utils.panel_logging import logToUser + + logToUser( + f"Units {units} are not supported. Meters will be applied by default.", + level=1, + func=inspect.stack()[0][3], + ) + return 1.0 + except: + print( + f"Units {units} are not supported. Meters will be applied by default." + ) + return 1.0 + except Exception as e: + try: + from speckle.speckle.utils.panel_logging import logToUser + + logToUser( + f"{e}. Meters will be applied by default.", + level=2, + func=inspect.stack()[0][3], + ) + return 1.0 + except: + print(f"{e}. Meters will be applied by default.") + return 1.0 + def getAppName(name: str) -> str: new_name = "" for i, x in enumerate(str(name)): - if x.lower() in [a for k,a in enumerate("abcdefghijklmnopqrstuvwxyz")]: + if x.lower() in [a for k, a in enumerate("abcdefghijklmnopqrstuvwxyz")]: new_name += x - else: break + else: + break return new_name + def findOrCreatePath(path: str): - if not os.path.exists(path): + if not os.path.exists(path): os.makedirs(path) + def removeSpecialCharacters(text: str) -> str: - new_text = text.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_") - #new_text = text.encode('iso-8859-1', errors='ignore').decode('utf-8') + new_text = ( + text.replace("[", "_") + .replace("]", "_") + .replace(" ", "_") + .replace("-", "_") + .replace("(", "_") + .replace(")", "_") + .replace(":", "_") + .replace("\\", "_") + .replace("/", "_") + .replace('"', "_") + .replace("&", "_") + .replace("@", "_") + .replace("$", "_") + .replace("%", "_") + .replace("^", "_") + ) + # new_text = text.encode('iso-8859-1', errors='ignore').decode('utf-8') return new_text -def splitTextIntoLines(text: str = "", number: int= 40) -> str: + +def splitTextIntoLines(text: str = "", number: int = 40) -> str: print("__splitTextIntoLines") print(text) msg = "" try: - if len(text)>number: + if len(text) > number: try: for i, x in enumerate(text): msg += x - if i!=0 and i%number == 0: msg += "\n" - except Exception as e: print(e) - else: + if i != 0 and i % number == 0: + msg += "\n" + except Exception as e: + print(e) + else: msg = text except Exception as e: print(e) print(text) - + return msg -def validateNewFclassName(newName: str, all_layer_names: List[str], prefix: str = "") -> str: - + +def jsonFromList(jsonObj: dict, levels: list): + # print("jsonFromList") + if len(levels) == 0: + return jsonObj + lastLevel = jsonObj + for l in levels: + # print(lastLevel) + try: + lastLevel = lastLevel[l] + except: + lastLevel.update({l: {}}) + # print(jsonObj) + return jsonObj + + +def validateNewFclassName( + newName: str, all_layer_names: List[str], prefix: str = "" +) -> str: + fixed_name = newName - if (prefix + fixed_name) in all_layer_names: - + if (prefix + fixed_name) in all_layer_names: + layerNameCreated = 0 - for index, letter in enumerate('234567890abcdefghijklmnopqrstuvwxyz'): - if ((prefix + fixed_name) + "_" + letter) not in all_layer_names: - fixed_name += "_"+letter - layerNameCreated +=1 - break + for index, letter in enumerate("234567890abcdefghijklmnopqrstuvwxyz"): + if ((prefix + fixed_name) + "_" + letter) not in all_layer_names: + fixed_name += "_" + letter + layerNameCreated += 1 + break if layerNameCreated == 0: - for index, letter in enumerate('234567890abcdefghijklmnopqrstuvwxyz'): - test_fixed_name = validateNewFclassName((fixed_name + "_" + letter), all_layer_names, prefix) - if (prefix + test_fixed_name) not in all_layer_names: - fixed_name = test_fixed_name - layerNameCreated +=1 - break - #else: layerNameCreated +=1 + for index, letter in enumerate("234567890abcdefghijklmnopqrstuvwxyz"): + test_fixed_name = validateNewFclassName( + (fixed_name + "_" + letter), all_layer_names, prefix + ) + if (prefix + test_fixed_name) not in all_layer_names: + fixed_name = test_fixed_name + layerNameCreated += 1 + break + # else: layerNameCreated +=1 if layerNameCreated == 0: - pass #logToUser('Feature class name already exists', level=2, func = inspect.stack()[0][3]) - #return fixed_name + pass # logToUser('Feature class name already exists', level=2, func = inspect.stack()[0][3]) + # return fixed_name return fixed_name - + + def findFeatColors(fetColors, f): - + colorFound = 0 - try: # get render material from any part of the mesh (list of items in displayValue) + try: # get render material from any part of the mesh (list of items in displayValue) for k, item in enumerate(f.displayValue): try: - fetColors.append(item.renderMaterial.diffuse) + fetColors.append(item.renderMaterial.diffuse) colorFound += 1 break - except: pass - if colorFound == 0: fetColors.append(f.renderMaterial.diffuse) - except: + except: + pass + if colorFound == 0: + fetColors.append(f.renderMaterial.diffuse) + except: try: for k, item in enumerate(f["@displayValue"]): - try: - fetColors.append(item.renderMaterial.diffuse) + try: + fetColors.append(item.renderMaterial.diffuse) colorFound += 1 break - except: pass - if colorFound == 0: fetColors.append(f.renderMaterial.diffuse) - except: - # the Mesh itself has a renderer - try: # get render material from any part of the mesh (list of items in displayValue) - fetColors.append(f.renderMaterial.diffuse) + except: + pass + if colorFound == 0: + fetColors.append(f.renderMaterial.diffuse) + except: + # the Mesh itself has a renderer + try: # get render material from any part of the mesh (list of items in displayValue) + fetColors.append(f.renderMaterial.diffuse) colorFound += 1 - except: + except: try: - fetColors.append(f.displayStyle.color) + fetColors.append(f.displayStyle.color) colorFound += 1 - except: pass - if colorFound == 0: fetColors.append(None) + except: + pass + if colorFound == 0: + fetColors.append(None) return fetColors diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/object_utils.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/object_utils.py index 37fb203..5c21403 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/object_utils.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/object_utils.py @@ -1,22 +1,25 @@ +from typing import Any, Callable, List, Optional -from typing import Any, Callable, List, Optional - -import inspect +import inspect from specklepy.objects import Base +from specklepy.objects.GIS.layers import VectorLayer, RasterLayer, Layer -try: - from speckle.speckle.ui.logger import logToUser - from speckle.speckle.converter.layers.Layer import VectorLayer, RasterLayer, Layer - from speckle.speckle.converter.layers import bimLayerToNative, cadLayerToNative, layerToNative -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.Layer import VectorLayer, RasterLayer, Layer - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import bimLayerToNative, cadLayerToNative, layerToNative +from speckle.speckle.utils.panel_logging import logToUser +from speckle.speckle.converter.layers import ( + bimLayerToNative, + cadLayerToNative, + layerToNative, +) import arcpy -SPECKLE_TYPES_TO_READ = ["Objects.Geometry.", "Objects.BuiltElements.", "IFC"] # will properly traverse and check for displayValue +SPECKLE_TYPES_TO_READ = [ + "Objects.Geometry.", + "Objects.BuiltElements.", + "IFC", +] # will properly traverse and check for displayValue + def traverseObject( base: Base, @@ -26,119 +29,175 @@ def traverseObject( plugin=None, ): try: - #print("traverse Object") - #print(base) + # print("traverse Object") + # print(base) if check and check(base): res = callback(base, streamBranch, plugin) if callback else False - #print(res) + # print(res) if res: return memberNames = base.get_member_names() - #print(base) - #print(memberNames) + # print(base) + # print(memberNames) for name in memberNames: try: if ["id", "applicationId", "units", "speckle_type"].index(name): continue except: pass - #print(name) + # print(name) traverseValue(base[name], callback, check, streamBranch, plugin) logToUser("Data received", level=0) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + def traverseValue( value: Any, callback: Optional[Callable[[Base, str, Any], bool]], check: Optional[Callable[[Base], bool]], streamBranch: str, - plugin = None, + plugin=None, ): try: - #print("traverse Value") - #print(value) + # print("traverse Value") + # print(value) if isinstance(value, Base): traverseObject(value, callback, check, streamBranch, plugin) if isinstance(value, List): for item in value: traverseValue(item, callback, check, streamBranch, plugin) except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + def callback(base: Base, streamBranch: str, plugin=None) -> bool: try: - #print("callback") - if base.speckle_type.endswith("VectorLayer") or base.speckle_type.endswith("RasterLayer"): + # print("callback") + if base.speckle_type.endswith("VectorLayer") or base.speckle_type.endswith( + "RasterLayer" + ): if isinstance(base, Layer): - logToUser(f"Speckle class \"Layer\" will be deprecated in future updates in favour of \"VectorLayer\" or \"RasterLayer\"", level=0, func = inspect.stack()[0][3]) + logToUser( + f'Speckle class "Layer" will be deprecated in future updates in favour of "VectorLayer" or "RasterLayer"', + level=0, + func=inspect.stack()[0][3], + ) layerToNative(base, streamBranch, plugin) - #print(layer) - #if layer is not None: + # print(layer) + # if layer is not None: # logToUser("Layer created: " + layer.name(), level=0) else: loopObj(base, "", streamBranch, plugin) return True except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return False + def loopObj(base: Base, baseName: str, streamBranch: str, plugin=None): try: memberNames = base.get_member_names() for name in memberNames: - if name in ["id", "applicationId", "units", "speckle_type"]: continue + if name in ["id", "applicationId", "units", "speckle_type"]: + continue # skip if traversal goes to displayValue of an object, that will be readable anyway: - if not isinstance(base, Base): logToUser("NOT BASE: "+type(base), level=1, func = inspect.stack()[0][3]); continue - if (name == "displayValue" or name == "@displayValue") and base.speckle_type.startswith(tuple(SPECKLE_TYPES_TO_READ)): continue + if not isinstance(base, Base): + logToUser( + "NOT BASE: " + type(base), level=1, func=inspect.stack()[0][3] + ) + continue + if ( + name == "displayValue" or name == "@displayValue" + ) and base.speckle_type.startswith(tuple(SPECKLE_TYPES_TO_READ)): + continue + + try: + loopVal(base[name], baseName + "/" + name, streamBranch, plugin) + except: + pass - try: loopVal(base[name], baseName + "/" + name, streamBranch, plugin) - except: pass - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + logToUser(str(e), level=2, func=inspect.stack()[0][3]) -def loopVal(value: Any, name: str, streamBranch: str, plugin=None): # "name" is the parent object/property/layer name + +def loopVal( + value: Any, name: str, streamBranch: str, plugin=None +): # "name" is the parent object/property/layer name try: - if isinstance(value, Base): - try: # loop through objects with Speckletype prop, but don't go through parts of Speckle Geometry object - if not value.speckle_type.startswith("Objects.Geometry."): + if isinstance(value, Base): + try: # loop through objects with Speckletype prop, but don't go through parts of Speckle Geometry object + if not value.speckle_type.startswith("Objects.Geometry."): loopObj(value, name, streamBranch, plugin) - except: + except: loopObj(value, name, streamBranch, plugin) elif isinstance(value, List): - streamBranch = streamBranch.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_") + streamBranch = ( + streamBranch.replace("[", "_") + .replace("]", "_") + .replace(" ", "_") + .replace("-", "_") + .replace("(", "_") + .replace(")", "_") + .replace(":", "_") + .replace("\\", "_") + .replace("/", "_") + .replace('"', "_") + .replace("&", "_") + .replace("@", "_") + .replace("$", "_") + .replace("%", "_") + .replace("^", "_") + ) objectListConverted = 0 - #print("loop val - List") + # print("loop val - List") for i, item in enumerate(value): loopVal(item, name, streamBranch, plugin) - if not isinstance(item, Base): continue - if item.speckle_type and item.speckle_type.startswith("IFC"): + if not isinstance(item, Base): + continue + if item.speckle_type and item.speckle_type.startswith("IFC"): # keep traversing infinitely, just don't run repeated conversion for the same list of objects - try: - if item["displayValue"] is not None and objectListConverted == 0: + try: + if ( + item["displayValue"] is not None + and objectListConverted == 0 + ): bimLayerToNative(value, name, streamBranch, None, plugin) objectListConverted += 1 - except: - try: - if item["@displayValue"] is not None and objectListConverted == 0: - bimLayerToNative(value, name, streamBranch, None, plugin) + except: + try: + if ( + item["@displayValue"] is not None + and objectListConverted == 0 + ): + bimLayerToNative( + value, name, streamBranch, None, plugin + ) objectListConverted += 1 - except: pass - elif item.speckle_type and item.speckle_type.endswith(".ModelCurve"): - if item["baseCurve"] is not None: + except: + pass + elif item.speckle_type and item.speckle_type.endswith(".ModelCurve"): + if item["baseCurve"] is not None: cadLayerToNative(value, name, streamBranch, plugin) break - elif item.speckle_type and (item.speckle_type == "Objects.Geometry.Mesh" or item.speckle_type == "Objects.Geometry.Brep" or item.speckle_type.startswith("Objects.BuiltElements.")): + elif item.speckle_type and ( + item.speckle_type == "Objects.Geometry.Mesh" + or item.speckle_type == "Objects.Geometry.Brep" + or item.speckle_type.startswith("Objects.BuiltElements.") + ): bimLayerToNative(value, name, streamBranch, None, plugin) break - elif item.speckle_type and item.speckle_type != "Objects.Geometry.Mesh" and item.speckle_type != "Objects.Geometry.Brep" and item.speckle_type.startswith("Objects.Geometry."): # or item.speckle_type == 'Objects.BuiltElements.Alignment'): + elif ( + item.speckle_type + and item.speckle_type != "Objects.Geometry.Mesh" + and item.speckle_type != "Objects.Geometry.Brep" + and item.speckle_type.startswith("Objects.Geometry.") + ): # or item.speckle_type == 'Objects.BuiltElements.Alignment'): cadLayerToNative(value, name, streamBranch, plugin) - #if pt is not None: arcpy.AddMessage("Layer group created: " + str(pt.name)) - #if pl is not None: arcpy.AddMessage("Layer group created: " + str(pl.name)) + # if pt is not None: arcpy.AddMessage("Layer group created: " + str(pt.name)) + # if pl is not None: arcpy.AddMessage("Layer group created: " + str(pl.name)) break except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - + logToUser(str(e), level=2, func=inspect.stack()[0][3]) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/threads.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/threads.py new file mode 100644 index 0000000..fb4e1f5 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/plugin_utils/threads.py @@ -0,0 +1,63 @@ + +import sys +import trace +import threading + +class KThread(threading.Thread): + """A subclass of threading.Thread, with a kill() + method.""" + # https://web.archive.org/web/20130503082442/http://mail.python.org/pipermail/python-list/2004-May/281943.html + def __init__(self, *args, **keywords): + threading.Thread.__init__(self, *args, **keywords) + self.killed = False + + def start(self): + """Start the thread.""" + self.__run_backup = self.run + self.run = self.__run # Force the Thread to install our trace. + threading.Thread.start(self) + + def __run(self): + """Hacked run function, which installs the trace.""" + sys.settrace(self.globaltrace) + self.__run_backup() + self.run = self.__run_backup + + def globaltrace(self, frame, why, arg): + if why == 'call': + return self.localtrace + else: + return None + + def localtrace(self, frame, why, arg): + if self.killed: + if why == 'line': + raise SystemExit() + return self.localtrace + + def kill(self): + self.killed = True + +class KillableThread(threading.Thread): + # is NOT running in the background + # https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread + def __init__(self, sleep_interval=1): + super().__init__() + self._kill = threading.Event() + self._interval = sleep_interval + + def run(self): + while True: + print("Do Something") + + # If no kill signal is set, sleep for the interval, + # If kill signal comes in while sleeping, immediately + # wake up and handle + is_killed = self._kill.wait(self._interval) + if is_killed: + break + + print("Killing Thread") + + def kill(self): + self._kill.set() \ No newline at end of file diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py index 2169e80..2cb953c 100644 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/speckle_arcgis.py @@ -1,4 +1,5 @@ - +from copy import copy +from datetime import datetime import os import os.path import sys @@ -7,117 +8,129 @@ from typing import Any, Callable, List, Optional, Tuple, Union import threading import inspect -from PyQt5.QtCore import QCoreApplication, QSettings, Qt, QTranslator, QRect -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QApplication, QAction, QDockWidget, QVBoxLayout, QWidget -from PyQt5 import QtWidgets +from PyQt5.QtWidgets import QApplication from specklepy.api import operations -from specklepy.logging.exceptions import SpeckleException, GraphQLException -#from specklepy.api.credentials import StreamWrapper +from specklepy.logging.exceptions import ( + SpeckleException, + GraphQLException, + SpeckleInvalidUnitException, +) + + +# from specklepy.api.credentials import StreamWrapper from specklepy.api.models import Stream from specklepy.api.wrapper import StreamWrapper from specklepy.objects import Base -from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper +from specklepy.api.credentials import Account, get_local_accounts # , StreamWrapper from specklepy.api.client import SpeckleClient from specklepy.logging import metrics +from specklepy.objects.other import Collection import webbrowser import arcpy from arcpy._mp import ArcGISProject, Map from arcpy._mp import Layer as arcLayer +from specklepy.objects.units import get_units_from_string -try: - from speckle.speckle.plugin_utils.object_utils import callback, traverseObject - from speckle.speckle.converter.layers.Layer import (Layer, VectorLayer, RasterLayer) - from speckle.speckle.converter.layers import convertSelectedLayers, getLayers - from speckle.speckle.converter.layers.utils import findAndClearLayerGroup - from speckle.speckle.ui.validation import tryGetStream, tryGetClient, validateBranch, validateCommit, validateStream, validateTransport - from speckle.speckle.ui.add_stream_modal import AddStreamModalDialog - from speckle.speckle.ui.create_stream import CreateStreamModalDialog - from speckle.speckle.ui.create_branch import CreateBranchModalDialog - from speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle.speckle.ui.logger import logToUser, logToUserWithAction - from speckle.speckle.plugin_utils.helpers import removeSpecialCharacters, getAppName - from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage +from speckle.speckle.plugin_utils.threads import KThread +from speckle.speckle.plugin_utils.object_utils import callback, traverseObject +from speckle.speckle.converter.layers import convertSelectedLayers, getLayers +from speckle.speckle.converter.layers.utils import findAndClearLayerGroup +from speckle.speckle.utils.validation import ( + tryGetStream, + tryGetClient, + validateBranch, + validateCommit, + validateStream, + validateTransport, +) -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.object_utils import callback, traverseObject - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.Layer import (Layer, VectorLayer, RasterLayer) - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import convertSelectedLayers, getLayers - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.emptyLayerTemplates import createGroupLayer - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers.utils import findAndClearLayerGroup - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.validation import tryGetStream, tryGetClient, validateBranch, validateCommit, validateStream, validateTransport - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.add_stream_modal import AddStreamModalDialog - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.create_stream import CreateStreamModalDialog - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.create_branch import CreateBranchModalDialog - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser, logToUserWithAction - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import removeSpecialCharacters, getAppName - from speckle_toolbox.esri.toolboxes.speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage +from speckle.speckle.converter.layers.layer_conversions import ( + addBimMainThread, + addCadMainThread, + addExcelMainThread, + addNonGeometryMainThread, + addRasterMainThread, + addVectorMainThread, + convertSelectedLayersToSpeckle, +) + +from speckle.specklepy_qt_ui.qt_ui.widget_add_stream import AddStreamModalDialog +from speckle.specklepy_qt_ui.qt_ui.widget_create_stream import CreateStreamModalDialog +from speckle.specklepy_qt_ui.qt_ui.widget_create_branch import CreateBranchModalDialog +from speckle.ui_widgets.main_window import SpeckleGISDialog +from speckle.speckle.utils.panel_logging import logToUser +from speckle.specklepy_qt_ui.qt_ui.utils.utils import constructCommitURL +from speckle.speckle.plugin_utils.helpers import removeSpecialCharacters, getAppName +from speckle.specklepy_qt_ui.qt_ui.DataStorage import DataStorage +from speckle.specklepy_qt_ui.qt_ui.widget_custom_crs import CustomCRSDialog # Import the code for the dialog -SPECKLE_COLOR = (59,130,246) -SPECKLE_COLOR_LIGHT = (69,140,255) +SPECKLE_COLOR = (59, 130, 246) +SPECKLE_COLOR_LIGHT = (69, 140, 255) -def startThread(sp_class): + +def startThread(sp_class): print("START THREAD") t = threading.Thread(target=qtApp, args=(sp_class,)) t.start() threads = threading.enumerate() print("__Total threads: " + str(len(threads))) + def qtApp(text: str): print("MAIN function") - - #threads = threading.enumerate() - #print("__Total threads: " + str(len(threads))) + app = QApplication(sys.argv) ex = SpeckleGIS() - #ex.show() + # ex.show() sys.exit(app.exec_()) + class Toolbox: def __init__(self): """Define the toolbox (the name of the toolbox is the name of the .pyt file).""" print("___start_Toolbox") self.label = "Speckle Tools" - self.alias = "speckle_toolbox_" + self.alias = "speckle_toolbox_" # List of tool classes associated with this toolbox - self.tools = [Speckle] - try: - version: str = arcpy.GetInstallInfo()['Version'] + self.tools = [Speckle] + try: + version: str = arcpy.GetInstallInfo()["Version"] python_version: str = f"python {'.'.join(map(str, sys.version_info[:2]))}" - #metrics.set_host_app("ArcGIS", "ArcGIS " + ', '.join([f"{version}", python_version])) metrics.set_host_app("ArcGIS", "ArcGIS " + version.split(".")[0]) - except: + except: metrics.set_host_app("ArcGIS") + class Speckle: - #instances = [] - def __init__(self): - + # instances = [] + def __init__(self): + print("___start speckle tool_________") - self.label = "Speckle" - self.description = "Allows you to send and receive your layers " + \ - "to/from other software using Speckle server." + self.label = "Speckle" + self.description = ( + "Allows you to send and receive your layers " + + "to/from other software using Speckle server." + ) def getParameterInfo(self): cat1 = "category 1" param0 = arcpy.Parameter( displayName="""▷ Run to launch Speckle Connector -""", #▶ +""", # ▶ name="param0", datatype="GPString", parameterType="Optional", direction="Input", enabled="True", - ) + ) param0.value = """This is an experimental version of plugin. Save your work before using! @@ -125,15 +138,15 @@ Save your work before using! Report issues at https://speckle.community/""" return [param0] - def isLicensed(self): #optional + def isLicensed(self): # optional return True - def updateParameters(self, parameters: List, toRefresh = False): #optional - return + def updateParameters(self, parameters: List, toRefresh=False): # optional + return - def execute(self, parameters: List, messages): + def execute(self, parameters: List, messages): qtApp("") - #startThread("") + class SpeckleGIS: """Speckle Connector Plugin for ArcGIS""" @@ -144,12 +157,12 @@ class SpeckleGIS: dockwidget: Optional[SpeckleGISDialog] add_stream_modal: AddStreamModalDialog create_stream_modal: CreateStreamModalDialog - current_streams: List[Tuple[StreamWrapper, Stream]] #{id:(sw,st),id2:()} + current_streams: List[Tuple[StreamWrapper, Stream]] # {id:(sw,st),id2:()} current_layers: List[Tuple[str, arcLayer]] = [] - active_stream: Optional[Tuple[StreamWrapper, Stream]] + active_stream: Optional[Tuple[StreamWrapper, Stream]] - gis_project: ArcGISProject #QgsProject + project: ArcGISProject # QgsProject lat: float lon: float @@ -159,264 +172,360 @@ class SpeckleGIS: active_account: Account def __init__(self): - """Constructor. - """ + """Constructor.""" print("Start SpeckleGIS") self.version = "0.0.99" try: - version = arcpy.GetInstallInfo()['Version'] + version = arcpy.GetInstallInfo()["Version"] python_version = f"python {'.'.join(map(str, sys.version_info[:2]))}" - full_version = ' '.join([f"{version}", python_version]) - except: full_version = arcpy.GetInstallInfo()['Version'] + full_version = " ".join([f"{version}", python_version]) + except: + full_version = arcpy.GetInstallInfo()["Version"] self.gis_version = full_version # Save reference to the QGIS interface self.dataStorage = None self.dockwidget = None - #self.iface = None - self.gis_project = ArcGISProject('CURRENT') #QgsProject.instance() + # self.iface = None + self.project = ArcGISProject("CURRENT") # QgsProject.instance() self.current_streams = [] self.active_stream = None - self.default_account = None - self.active_account = None - self.accounts = [] - + self.active_branch = None + self.active_commit = None + self.receive_layer_tree = None + # self.default_account = None + # self.accounts = [] + # self.active_account = None + self.theads_total = 0 self.btnAction = 0 self.lat = 0.0 self.lon = 0.0 # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) - # initialize locale - #locale = QSettings().value("locale/userLocale")[0:2] - #locale_path = os.path.join( - # self.plugin_dir, "i18n", "SpeckleQGIS_{}.qm".format(locale) - #) - - #if os.path.exists(locale_path): - # self.translator = QTranslator() - # self.translator.load(locale_path) - # QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] - self.menu = self.tr("&SpeckleArcGIS") # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads - self.pluginIsActive = False + self.pluginIsActive = False self.run() - # noinspection PyMethodMayBeStatic - - def tr(self, message: str): - """Get the translation for a string using Qt translation API. - - We implement this ourselves since we do not inherit QObject. - - :param message: String for translation. - :type message: str, QString - - :returns: Translated version of message. - :rtype: QString - """ - # noinspection PyTypeChecker,PyArgumentList,PyCallByClass - return QCoreApplication.translate("SpeckleGIS", message) - - def add_action( - self, - icon_path: str, - text, - callback, - enabled_flag=True, - add_to_menu=True, - add_to_toolbar=True, - status_tip=None, - whats_this=None, - parent=None, - ): - """Add a toolbar icon to the toolbar. - - :param icon_path: Path to the icon for this action. Can be a resource - path (e.g. ':/plugins/foo/bar.png') or a normal file system path. - :type icon_path: str - - :param text: Text that should be shown in menu items for this action. - :type text: str - - :param callback: Function to be called when the action is triggered. - :type callback: function - - :param enabled_flag: A flag indicating if the action should be enabled - by default. Defaults to True. - :type enabled_flag: bool - - :param add_to_menu: Flag indicating whether the action should also - be added to the menu. Defaults to True. - :type add_to_menu: bool - - :param add_to_toolbar: Flag indicating whether the action should also - be added to the toolbar. Defaults to True. - :type add_to_toolbar: bool - - :param status_tip: Optional text to show in a popup when mouse pointer - hovers over the action. - :type status_tip: str - - :param parent: Parent widget for the new action. Defaults None. - :type parent: QWidget - - :param whats_this: Optional text to show in the status bar when the - mouse pointer hovers over the action. - - :returns: The action that was created. Note that the action is also - added to self.actions list. - :rtype: QAction - """ - - icon = QIcon(icon_path) - action = QAction(icon, text, parent) - action.triggered.connect(callback) - action.setEnabled(enabled_flag) - - if status_tip is not None: - action.setStatusTip(status_tip) - - if whats_this is not None: - action.setWhatsThis(whats_this) - - #if add_to_toolbar: - # # Adds plugin icon to Plugins toolbar - # self.iface.addToolBarIcon(action) - - #if add_to_menu and self.menu: - # self.iface.addPluginToWebMenu(self.menu, action) - - self.actions.append(action) - - return action - - def initGui(self): - """Create the menu entries and toolbar icons inside the QGIS GUI.""" - - icon_path = "" #":/plugins/speckle_qgis/icon.png" - self.add_action( - icon_path, - text=self.tr("SpeckleGIS"), - callback=self.run, - add_to_menu=False, - add_to_toolbar=False, - parent=None, #self.iface.mainWindow(), - ) - def onClosePlugin(self): """Cleanup necessary items here when plugin dockwidget is closed""" try: # disconnects if self.dockwidget: - try: + try: self.dockwidget.closingPlugin.disconnect(self.onClosePlugin) self.dockwidget.close() - except: pass + except: + pass self.pluginIsActive = False # remove this statement if dockwidget is to remain - # for reuse if plugin is reopened - except Exception as e: - logToUser(str(e), func = inspect.stack()[0][3]) - + # for reuse if plugin is reopened + except Exception as e: + logToUser(str(e), func=inspect.stack()[0][3]) def unload(self): """Removes the plugin menu item and icon from GIS GUI.""" return - #for action in self.actions: - # self.iface.removePluginWebMenu(self.tr("&SpeckleQGIS"), action) - # self.iface.removeToolBarIcon(action) def onRunButtonClicked(self): - self.dockwidget.msgLog.setGeometry(0, 0, self.dockwidget.frameSize().width(), self.dockwidget.frameSize().height()) + try: - streamWrapper = self.active_stream[0] - client = streamWrapper.get_client() - self.active_account = client.account - except: - pass + all_threads = threading.enumerate() - if self.btnAction == 0: self.onSend() - elif self.btnAction == 1: self.onReceive() + for t in all_threads: + if t.name.startswith("speckle"): + name = "" + if "receive" in t.name: + name = "Receive" + if "send" in t.name: + name = "Send" + logToUser( + f"Previous {name} operation is still running \nClick here to cancel", + level=2, + url="cancel", + plugin=self.dockwidget, + ) + return - def onSend(self): + # set the project instance + self.project = ArcGISProject("CURRENT") + self.dataStorage.project = self.project + self.dockwidget.msgLog.setGeometry( + 0, + 0, + self.dockwidget.frameSize().width(), + self.dockwidget.frameSize().height(), + ) + self.dockwidget.reportBtn.setEnabled(True) + + # send + if self.btnAction == 0: + # Reset Survey point + # self.dockwidget.populateSurveyPoint(self) + # Get and clear message + message = str(self.dockwidget.messageInput.text()) + self.dockwidget.messageInput.setText("") + + try: + streamWrapper = self.active_stream[0] + client = streamWrapper.get_client() + self.dataStorage.active_account = client.account + logToUser( + f"Sending data... \nClick here to cancel", + level=0, + url="cancel", + plugin=self.dockwidget, + ) + t = KThread( + target=self.onSend, name="speckle_send", args=(message,) + ) + t.start() + except: + self.onSend(message) + + # receive + elif self.btnAction == 1: + ################### repeated + try: + if not self.dockwidget: + return + # Check if stream id/url is empty + if self.active_stream is None: + logToUser( + "Please select a stream from the list", + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + return + + # Get the stream wrapper + streamWrapper = self.active_stream[0] + streamId = streamWrapper.stream_id + + # client = streamWrapper.get_client() + client, stream = tryGetClient( + streamWrapper, self.dataStorage, False, self.dockwidget + ) + stream = validateStream(stream, self.dockwidget) + if stream == None: + return + except Exception as e: + logToUser( + e, level=2, func=inspect.stack()[0][3], plugin=self.dockwidget + ) + return + + # Ensure the stream actually exists + try: + branchName = str(self.dockwidget.streamBranchDropdown.currentText()) + branch = validateBranch(stream, branchName, True, self.dockwidget) + if branch == None: + return + + commitId = str(self.dockwidget.commitDropdown.currentText()) + commit = validateCommit(branch, commitId, self.dockwidget) + if commit == None: + return + + # If group exists, remove layers inside + newGroupName = streamId + "_" + branch.name + "_" + commit.id + newGroupName = removeSpecialCharacters(newGroupName) + findAndClearLayerGroup( + self.project.layerTreeRoot(), newGroupName, self + ) + + except Exception as e: + logToUser( + str(e), + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + return + ########################################### end of repeated + + try: + streamWrapper = self.active_stream[0] + client = streamWrapper.get_client() + self.dataStorage.active_account = client.account + logToUser( + "Receiving data... \nClick here to cancel", + level=0, + url="cancel", + plugin=self.dockwidget, + ) + + t = KThread(target=self.onReceive, name="speckle_receive", args=()) + t.start() + except: + self.onReceive() + except Exception as e: + logToUser(e, level=2, plugin=self.dockwidget) + + def onSend(self, message: str): """Handles action when Send button is pressed.""" try: - if not self.dockwidget: return + if not self.dockwidget: + return print("On Send") + bySelection = True + if self.dockwidget.layerSendModeDropdown.currentIndex() == 1: + bySelection = False + layers = getLayers(self, bySelection) # List[QgsLayerTreeNode] + # Check if stream id/url is empty if self.active_stream is None: - logToUser("Please select a stream from the list.", level=1, func = inspect.stack()[0][3], plugin = self.dockwidget) + logToUser( + "Please select a stream from the list.", + level=1, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + return + current_active_stream = copy(self.active_stream) + + # Check if no layers are selected + if len(layers) == 0: # len(selectedLayerNames) == 0: + logToUser( + "No layers selected", + level=1, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + return + print(layers) + self.dataStorage.latestActionLayers = [l.name() for l in layers] + + # TODO: get layer tree + # root = self.dataStorage.project.layerTreeRoot() + # self.dataStorage.all_layers = getAllLayers(root) + self.dataStorage.all_layers = layers + + self.project = ArcGISProject("CURRENT") + if self.project.activeMap is None: + logToUser( + "No active Map", + level=1, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) return - self.gis_project = ArcGISProject("CURRENT") - if self.gis_project.activeMap is None: - logToUser("No active Map", level=1, func = inspect.stack()[0][3], plugin = self.dockwidget) - return + units = str(self.project.activeMap.spatialReference.linearUnitName) + self.dataStorage.latestActionUnits = units + try: + units = get_units_from_string(units) + except SpeckleInvalidUnitException: + units = "none" + self.dataStorage.currentUnits = units + + if ( + self.dataStorage.crs_offset_x is not None + and self.dataStorage.crs_offset_x + ) != 0 or ( + self.dataStorage.crs_offset_y is not None + and self.dataStorage.crs_offset_y + ): + logToUser( + f"Applying CRS offsets: x={self.dataStorage.crs_offset_x}, y={self.dataStorage.crs_offset_y}", + level=0, + plugin=self.dockwidget, + ) + if ( + self.dataStorage.crs_rotation is not None + and self.dataStorage.crs_rotation + ) != 0: + logToUser( + f"Applying CRS rotation: {self.dataStorage.crs_rotation}°", + level=0, + plugin=self.dockwidget, + ) print("On Send 2") # creating our parent base object - project = self.gis_project - #projectCRS = project.Sp - #layerTreeRoot = project.layerTreeRoot() + project = self.project + # projectCRS = project.Sp + # layerTreeRoot = project.layerTreeRoot() + + self.dataStorage.latestActionReport = [] + self.dataStorage.latestActionFeaturesReport = [] + base_obj = Collection( + units=units, + collectionType="ArcGIS commit", + name="ArcGIS commit", + elements=[], + ) - bySelection = True - if self.dockwidget.layerSendModeDropdown.currentIndex() == 1: bySelection = False - layers = getLayers(self, bySelection) # List[QgsLayerTreeNode] - - # Check if no layers are selected - if len(layers) == 0: #len(selectedLayerNames) == 0: - logToUser("No layers selected", level=1, func = inspect.stack()[0][3], plugin = self.dockwidget) - return - print(layers) print("On Send 3") - base_obj = Base(units = "m") + # conversions + time_start_conversion = datetime.now() base_obj.layers = convertSelectedLayers(layers, project) - if base_obj.layers is None: - return + time_end_conversion = datetime.now() - # Reset Survey point - self.dockwidget.populateSurveyPoint(self) + if ( + base_obj is None + or base_obj.elements is None + or (isinstance(base_obj.elements, List) and len(base_obj.elements) == 0) + ): + logToUser(f"No data to send", level=2, plugin=self.dockwidget) + return - # Get the stream wrapper - streamWrapper = self.active_stream[0] - streamName = self.active_stream[1].name + logToUser(f"Sending data to the server...", level=0, plugin=self.dockwidget) + + streamWrapper = current_active_stream[0] + streamName = current_active_stream[1].name streamId = streamWrapper.stream_id - - # client = streamWrapper.get_client() + client, stream = tryGetClient( - streamWrapper, self.dataStorage, False, self.dockwidget - ) + streamWrapper, self.dataStorage, True, self.dockwidget + ) + if not isinstance(client, SpeckleClient) or not isinstance(stream, Stream): + return stream = validateStream(stream, self.dockwidget) - if stream == None: return - + if not isinstance(stream, Stream): + return + branchName = str(self.dockwidget.streamBranchDropdown.currentText()) - branch = validateBranch(stream, branchName, False) - if branch == None: return + branch = validateBranch(stream, branchName, False, self.dockwidget) + branchId = branch.id + if branch == None: + return transport = validateTransport(client, streamId) - if transport == None: return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) + if transport == None: + return + + except Exception as e: + logToUser( + str(e), level=2, func=inspect.stack()[0][3], plugin=self.dockwidget + ) return - + try: + self.dockwidget.signal_remove_btn_url.emit("cancel") + time_start_transfer = datetime.now() # this serialises the block and sends it to the transport objId = operations.send(base=base_obj, transports=[transport]) except SpeckleException as e: - logToUser("Error sending data: " + str(e.message), level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) + logToUser( + "Error sending data: " + str(e), + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + time_end_transfer = datetime.now() return try: - message = str(self.dockwidget.messageInput.text()) # you can now create a commit on your stream with this object commit_id = client.commit.create( stream_id=streamId, @@ -425,15 +534,15 @@ class SpeckleGIS: message="Sent objects from ArcGIS" if len(message) == 0 else message, source_application="ArcGIS " + self.gis_version.split(".")[0], ) - r''' + r""" try: metr_filter = "Visible" if bySelection is True else "Saved" metr_main = True if branchName=="main" else False metr_saved_streams = len(self.current_streams) metr_branches = len(self.active_stream[1].branches.items) metr_collab = len(self.active_stream[1].collaborators) - metr_projected = True if self.gis_project.activeMap.spatialReference.type != "Geographic" else False - if self.gis_project.activeMap.spatialReference is None: metr_projected = None + metr_projected = True if self.project.activeMap.spatialReference.type != "Geographic" else False + if self.project.activeMap.spatialReference is None: metr_projected = None python_version: str = f"python {'.'.join(map(str, sys.version_info[:2]))}" try: @@ -446,134 +555,253 @@ class SpeckleGIS: metrics.track(metrics.SEND, self.active_account, {"hostAppFullVersion":self.gis_version, "pythonVersion": python_version,"branches":metr_branches, "collaborators":metr_collab,"connector_version": str(self.version), "filter": metr_filter, "isMain": metr_main, "savedStreams": metr_saved_streams, "projectedCRS": metr_projected, "customCRS": metr_crs}) except: metrics.track(metrics.SEND, self.active_account) - ''' - + """ + + # add time stats to the report + self.dataStorage.latestActionTime = str( + datetime.now().strftime("%d/%m/%Y, %H:%M:%S") + ) + self.dataStorage.latestTransferTime = str( + time_end_transfer - time_start_transfer + ) + self.dataStorage.latestConversionTime = str( + time_end_conversion - time_start_conversion + ) + if isinstance(commit_id, SpeckleException): - logToUser("Error creating commit: "+str(commit_id.message), level = 2, func = inspect.stack()[0][3], plugin=self.dockwidget) + logToUser( + "Error creating commit: " + str(commit_id.message), + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) return + + url: str = constructCommitURL(streamWrapper, branchId, commit_id) + + if str(self.dockwidget.commitDropdown.currentText()).startswith("Latest"): + stream = client.stream.get( + id=streamId, branch_limit=100, commit_limit=100 + ) + branch = validateBranch(stream, branchName, False, self.dockwidget) + self.active_commit = branch.commits.items[0] + + if self.project.activeMap.spatialReference.type == "Geographic": + logToUser( + "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software. You can also create a custom CRS by setting geographic coordinates and using 'Set as a project center' function.", + level=1, + plugin=self.dockwidget, + ) + arcpy.AddMessage("Successfully sent data to stream: " + streamId) - url = streamWrapper.stream_url.split("?")[0] + "/commits/" + commit_id + self.dockwidget.msgLog.dataStorage = self.dataStorage - self.dockwidget.messageInput.setText("") - logToUser(f"👌 Data sent to \"{streamName}\" \n View it online", level = 0, plugin=self.dockwidget, url = url) + logToUser( + "Data sent to '" + + str(streamName) + + "'" + + "\nClick to view commit online", + level=0, + plugin=self.dockwidget, + url=url, + report=True, + ) except SpeckleException as e: - logToUser("Error creating commit:" + e.message, level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) - + logToUser( + "Error creating commit:" + e, + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) + def onReceive(self): """Handles action when the Receive button is pressed""" try: print("ON RECEIVE") - if not self.dockwidget: return + if not self.dockwidget: + return # Check if stream id/url is empty if self.active_stream is None: - logToUser("Please select a stream from the list.", level=1, func = inspect.stack()[0][3], plugin = self.dockwidget) + logToUser( + "Please select a stream from the list.", + level=1, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) return - self.gis_project = ArcGISProject("CURRENT") - if self.gis_project.activeMap is None: - logToUser("No active Map", level=1, func = inspect.stack()[0][3]) - return + self.project = ArcGISProject("CURRENT") + if self.project.activeMap is None: + logToUser("No active Map", level=1, func=inspect.stack()[0][3]) + return # Get the stream wrapper streamWrapper = self.active_stream[0] streamId = streamWrapper.stream_id - #client = streamWrapper.get_client() - + # client = streamWrapper.get_client() + client, stream = tryGetClient( - streamWrapper, self.dataStorage, False, self.dockwidget - ) + streamWrapper, self.dataStorage, False, self.dockwidget + ) # Ensure the stream actually exists print("ON RECEIVE 2") - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) + except Exception as e: + logToUser( + str(e), level=2, func=inspect.stack()[0][3], plugin=self.dockwidget + ) return try: stream = validateStream(stream, self.dockwidget) - if stream == None: return - + if stream == None: + return + branchName = str(self.dockwidget.streamBranchDropdown.currentText()) branch = validateBranch(stream, branchName, True) - if branch == None: return + if branch == None: + return commitId = str(self.dockwidget.commitDropdown.currentText()) commit = validateCommit(branch, commitId) - if commit == None: return + if commit == None: + return except SpeckleException as e: - logToUser(str(e.message), level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) + logToUser( + str(e.message), + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) return transport = validateTransport(client, streamId) - if transport == None: return + if transport == None: + return print("ON RECEIVE 3") try: objId = commit.referencedObject - - if branch.name is None or commit.id is None or objId is None: - return - - #commitDetailed = client.commit.get(streamId, commit.id) + + if branch.name is None or commit.id is None or objId is None: + return + + # commitDetailed = client.commit.get(streamId, commit.id) app_full = commit.sourceApplication app = getAppName(commit.sourceApplication) client_id = client.account.userInfo.id commitObj = operations._untracked_receive(objId, transport, None) - + try: - crs_lat = self.gis_project.activeMap.spatialReference.latitudeOfOrigin - crs_lon = self.gis_project.activeMap.spatialReference.centralMeridian - metr_crs = True if self.lat!=0 and self.lon!=0 and crs_lat == self.lat and crs_lon == self.lon else False - metr_projected = True if self.gis_project.activeMap.spatialReference.type != "Geographic" else False - if self.gis_project.activeMap.spatialReference is None: metr_projected = None + crs_lat = self.project.activeMap.spatialReference.latitudeOfOrigin + crs_lon = self.project.activeMap.spatialReference.centralMeridian + metr_crs = ( + True + if self.lat != 0 + and self.lon != 0 + and crs_lat == self.lat + and crs_lon == self.lon + else False + ) + metr_projected = ( + True + if self.project.activeMap.spatialReference.type != "Geographic" + else False + ) + if self.project.activeMap.spatialReference is None: + metr_projected = None except: metr_crs = False try: - python_version: str = f"python {'.'.join(map(str, sys.version_info[:2]))}" - metrics.track(metrics.RECEIVE, self.active_account, {"hostAppFullVersion":self.gis_version,"pythonVersion": python_version,"sourceHostAppVersion": app_full, "sourceHostApp": app, "isMultiplayer": commit.authorId != client_id,"connector_version": str(self.version), "projectedCRS": metr_projected, "customCRS": metr_crs}) + python_version: str = ( + f"python {'.'.join(map(str, sys.version_info[:2]))}" + ) + metrics.track( + metrics.RECEIVE, + self.active_account, + { + "hostAppFullVersion": self.gis_version, + "pythonVersion": python_version, + "sourceHostAppVersion": app_full, + "sourceHostApp": app, + "isMultiplayer": commit.authorId != client_id, + "connector_version": str(self.version), + "projectedCRS": metr_projected, + "customCRS": metr_crs, + }, + ) except: metrics.track(metrics.RECEIVE, self.active_account) client.commit.received( - streamId, - commit.id, - source_application="ArcGIS " + self.gis_version.split(".")[0], - message="Received commit in ArcGIS", + streamId, + commit.id, + source_application="ArcGIS " + self.gis_version.split(".")[0], + message="Received commit in ArcGIS", ) if app.lower() != "qgis" and app.lower() != "arcgis": - if self.gis_project.activeMap.spatialReference.type == "Geographic" or self.gis_project.activeMap.spatialReference is None: #TODO test with invalid CRS - logToUser("Conversion from metric units to DEGREES not supported. It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates", level=0, func = inspect.stack()[0][3], plugin = self.dockwidget) + if ( + self.project.activeMap.spatialReference.type == "Geographic" + or self.project.activeMap.spatialReference is None + ): # TODO test with invalid CRS + logToUser( + "Conversion from metric units to DEGREES not supported. It is advisable to set the project Spatial reference to Projected type before receiving CAD geometry (e.g. EPSG:32631), or create a custom one from geographic coordinates", + level=0, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) arcpy.AddMessage(f"Succesfully received {objId}") - # If group exists, remove layers inside + # If group exists, remove layers inside newGroupName = streamId + "_" + branch.name + "_" + commit.id newGroupName = removeSpecialCharacters(newGroupName) - findAndClearLayerGroup(self.gis_project, newGroupName) - + findAndClearLayerGroup(self.project, newGroupName) + print("after create group") - if app.lower() == "qgis" or app.lower() == "arcgis": check: Callable[[Base], bool] = lambda base: base.speckle_type and (base.speckle_type.endswith("VectorLayer") or base.speckle_type.endswith("Layer") or base.speckle_type.endswith("RasterLayer") ) - else: check: Callable[[Base], bool] = lambda base: (base.speckle_type and base.speckle_type.endswith("Base") ) + if app.lower() == "qgis" or app.lower() == "arcgis": + check: Callable[[Base], bool] = lambda base: base.speckle_type and ( + base.speckle_type.endswith("VectorLayer") + or base.speckle_type.endswith("Layer") + or base.speckle_type.endswith("RasterLayer") + ) + else: + check: Callable[[Base], bool] = lambda base: ( + base.speckle_type and base.speckle_type.endswith("Base") + ) traverseObject(commitObj, callback, check, str(newGroupName), self) - logToUser("👌 Data received", level = 0, plugin = self.dockwidget, blue = True) - return - + logToUser("👌 Data received", level=0, plugin=self.dockwidget, blue=True) + return + except SpeckleException as e: - logToUser("Receive failed: "+ e.message, level=2, func = inspect.stack()[0][3], plugin = self.dockwidget) + logToUser( + "Receive failed: " + e.message, + level=2, + func=inspect.stack()[0][3], + plugin=self.dockwidget, + ) return def reloadUI(self): - + try: - from speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection - except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection - + from speckle.speckle.utils.project_vars import ( + get_project_streams, + get_survey_point, + get_project_layer_selection, + ) + except: + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.project_vars import ( + get_project_streams, + get_survey_point, + get_project_layer_selection, + ) + self.dataStorage = DataStorage() self.dataStorage.plugin_version = self.version @@ -589,172 +817,299 @@ class SpeckleGIS: def check_for_accounts(self): def go_to_manager(): webbrowser.open("https://speckle-releases.netlify.app/") + try: accounts = get_local_accounts() self.accounts = accounts if len(accounts) == 0: - logToUser("No accounts were found. Please remember to install the Speckle Manager and setup at least one account", level=1, url="https://speckle-releases.netlify.app/", func = inspect.stack()[0][3]) + logToUser( + "No accounts were found. Please remember to install the Speckle Manager and setup at least one account", + level=1, + url="https://speckle-releases.netlify.app/", + func=inspect.stack()[0][3], + ) return False for acc in accounts: - if acc.isDefault: - self.default_account = acc + if acc.isDefault: + self.default_account = acc self.active_account = acc break return True - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) return False def run(self): """Run method that performs all the real work""" print("run plugin") try: - from speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection - except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.speckle_qgis_dialog import SpeckleGISDialog - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import get_project_streams, get_survey_point, get_project_layer_selection - try: + from speckle.ui_widgets.main_window import SpeckleGISDialog + from speckle.speckle.utils.project_vars import ( + get_project_streams, + get_survey_point, + get_rotation, + get_crs_offsets, + get_project_saved_layers, + ) + # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started - self.is_setup = self.check_for_accounts() - + + self.dataStorage = DataStorage() + self.dataStorage.plugin_version = self.version + self.dataStorage.project = self.project + + self.is_setup = self.dataStorage.check_for_accounts() + if self.pluginIsActive: self.reloadUI() else: - - self.dataStorage = DataStorage() - self.dataStorage.plugin_version = self.version - self.is_setup = self.dataStorage.check_for_accounts() - + print("Plugin inactive, launch") self.pluginIsActive = True + print("run plugin 100") if self.dockwidget is None: self.dockwidget = SpeckleGISDialog() - self.dockwidget.addLabel(self) - self.dockwidget.addProps(self) - self.dockwidget.show() - #self.gis_project.fileNameChanged.connect(self.reloadUI) - #self.gis_project.homePathChanged.connect(self.reloadUI) - print("run plugin 2") - get_project_streams(self) - print("run plugin 3") - get_survey_point(self) - print("run plugin 4") - get_project_layer_selection(self) - print("run plugin 5") - self.dockwidget.run(self) + self.dockwidget.addDataStorage(self) + self.dockwidget.runSetup(self) - # show the dockwidget - #self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockwidget) - self.dockwidget.enableElements(self) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + self.dockwidget.runButton.clicked.connect(self.onRunButtonClicked) + self.dockwidget.crsSettings.clicked.connect( + self.customCRSDialogCreate + ) + + self.dockwidget.signal_1.connect(addVectorMainThread) + self.dockwidget.signal_2.connect(addBimMainThread) + self.dockwidget.signal_3.connect(addCadMainThread) + self.dockwidget.signal_4.connect(addRasterMainThread) + self.dockwidget.signal_5.connect(addNonGeometryMainThread) + self.dockwidget.signal_6.connect(addExcelMainThread) + self.dockwidget.signal_remove_btn_url.connect( + self.dockwidget.msgLog.removeBtnUrl + ) + self.dockwidget.signal_cancel_operation.connect( + self.dockwidget.cancelOperations + ) + else: + self.dockwidget.addDataStorage(self) + + get_project_streams(self) + get_rotation(self.dataStorage) + get_survey_point(self.dataStorage) + get_crs_offsets(self.dataStorage) + get_project_saved_layers(self) + self.dockwidget.populateSavedLayerDropdown(self) + + self.dockwidget.run(self) + self.dockwidget.saveLayerSelection.clicked.connect( + lambda: self.populateSelectedLayerDropdown() + ) + self.dockwidget.enableElements(self) + + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) def onStreamAddButtonClicked(self): - self.add_stream_modal = AddStreamModalDialog(None) - self.add_stream_modal.handleStreamAdd.connect(self.handleStreamAdd) - self.add_stream_modal.show() - - def set_survey_point(self): try: - from speckle.ui.project_vars import set_survey_point - except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_survey_point + self.add_stream_modal = AddStreamModalDialog(None) + self.add_stream_modal.dataStorage = self.dataStorage + self.add_stream_modal.connect() + self.add_stream_modal.handleStreamAdd.connect(self.handleStreamAdd) + self.add_stream_modal.show() + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + def set_survey_point(self): + try: + from speckle.speckle.utils.project_vars import set_survey_point + except: + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.project_vars import ( + set_survey_point, + ) set_survey_point(self) def onStreamCreateClicked(self): self.create_stream_modal = CreateStreamModalDialog(None) self.create_stream_modal.handleStreamCreate.connect(self.handleStreamCreate) - #self.create_stream_modal.handleCancelStreamCreate.connect(lambda: self.dockwidget.populateProjectStreams(self)) + # self.create_stream_modal.handleCancelStreamCreate.connect(lambda: self.dockwidget.populateProjectStreams(self)) self.create_stream_modal.show() - - def handleStreamCreate(self, account, str_name, description, is_public): - try: - #if len(str_name)<3 and len(str_name)!=0: + + def handleStreamCreate(self, account, str_name, description, is_public): + try: + # if len(str_name)<3 and len(str_name)!=0: # logger.logToUser("Stream Name should be at least 3 characters", Qgis.Warning) new_client = SpeckleClient( - account.serverInfo.url, - account.serverInfo.url.startswith("https") + account.serverInfo.url, account.serverInfo.url.startswith("https") ) new_client.authenticate_with_token(token=account.token) - str_id = new_client.stream.create(name=str_name, description = description, is_public = is_public) - if isinstance(str_id, GraphQLException) or isinstance(str_id, SpeckleException): - logToUser(str_id.message, level=2, func = inspect.stack()[0][3]) + str_id = new_client.stream.create( + name=str_name, description=description, is_public=is_public + ) + if isinstance(str_id, GraphQLException) or isinstance( + str_id, SpeckleException + ): + logToUser(str_id.message, level=2, func=inspect.stack()[0][3]) return else: sw = StreamWrapper(account.serverInfo.url + "/streams/" + str_id) - self.handleStreamAdd(sw) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return + self.handleStreamAdd(sw) + return + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return def onBranchCreateClicked(self): self.create_stream_modal = CreateBranchModalDialog(None) self.create_stream_modal.handleBranchCreate.connect(self.handleBranchCreate) self.create_stream_modal.show() - + def handleBranchCreate(self, br_name, description): - #if len(br_name)<3: + # if len(br_name)<3: # logger.logToUser("Branch Name should be at least 3 characters", Qgis.Warning) - # return + # return try: br_name = br_name.lower() sw: StreamWrapper = self.active_stream[0] account = sw.get_account() new_client = SpeckleClient( - account.serverInfo.url, - account.serverInfo.url.startswith("https") + account.serverInfo.url, account.serverInfo.url.startswith("https") ) new_client.authenticate_with_token(token=account.token) - #description = "No description provided" - br_id = new_client.branch.create(stream_id = sw.stream_id, name = br_name, description = description) + # description = "No description provided" + br_id = new_client.branch.create( + stream_id=sw.stream_id, name=br_name, description=description + ) if isinstance(br_id, GraphQLException): - logToUser(br_id.message, level=2, func = inspect.stack()[0][3]) + logToUser(br_id.message, level=2, func=inspect.stack()[0][3]) self.active_stream = (sw, tryGetStream(sw, self.dataStorage)) self.current_streams[0] = self.active_stream self.dockwidget.populateActiveStreamBranchDropdown(self) self.dockwidget.populateActiveCommitDropdown(self) - self.dockwidget.streamBranchDropdown.setCurrentText(br_name) # will be ignored if branch name is not in the list + self.dockwidget.streamBranchDropdown.setCurrentText( + br_name + ) # will be ignored if branch name is not in the list - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + return + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) - def handleStreamAdd(self, sw: StreamWrapper): + def handleStreamAdd(self, objectPacked: Tuple): try: - from speckle.speckle.ui.project_vars import set_project_streams + from speckle.speckle.utils.project_vars import set_project_streams except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_streams - - streamExists = 0 - index = 0 - try: - stream = tryGetStream(sw, self.dataStorage) - - for st in self.current_streams: - if isinstance(stream, Stream) and st[0].stream_id == stream.id: - streamExists = 1; - break + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.project_vars import ( + set_project_streams, + ) + try: + sw, branch, commit = objectPacked + # print(sw) + # print(branch) + # print(commit) + streamExists = 0 + index = 0 + + self.dataStorage.check_for_accounts() + stream = sw.get_client().stream.get( + id=sw.stream_id, branch_limit=100, commit_limit=100 + ) + # stream = tryGetStream(sw, self.dataStorage, False, self.dockwidget) + # print(stream) + + if stream is not None and branch in stream.branches.items: + self.active_branch = branch + self.active_commit = commit + else: + self.active_branch = None + self.active_commit = None + + # try: print(f"ACTIVE BRANCH NAME: {self.active_branch.name}") + # except: print("ACTIVE BRANCH IS NONE") + for st in self.current_streams: + # if isinstance(st[1], SpeckleException) or isinstance(stream, SpeckleException): pass + if isinstance(stream, Stream) and st[0].stream_id == stream.id: + streamExists = 1 + break index += 1 except SpeckleException as e: - logToUser(e.message, level=2, func = inspect.stack()[0][3]) + logToUser(e, level=1, plugin=self.dockwidget) stream = None + try: - if streamExists == 0: - self.current_streams.insert(0,(sw, stream)) - else: + if streamExists == 0: + self.current_streams.insert(0, (sw, stream)) + else: del self.current_streams[index] - self.current_streams.insert(0,(sw, stream)) - try: self.add_stream_modal.handleStreamAdd.disconnect(self.handleStreamAdd) - except: pass - set_project_streams(self) + self.current_streams.insert(0, (sw, stream)) + try: + self.add_stream_modal.handleStreamAdd.disconnect(self.handleStreamAdd) + except: + pass + # set_project_streams(self) self.dockwidget.populateProjectStreams(self) - - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self.dockwidget) + return + + def customCRSDialogCreate(self): + try: + self.dataStorage.currentCRS = self.dataStorage.project.crs() + units = str(self.project.activeMap.spatialReference.linearUnitName) + self.dataStorage.currentOriginalUnits = units + + if units is None or units == "degrees": + units = "m" + self.dataStorage.currentUnits = units + + self.dockwidget.custom_crs_modal = CustomCRSDialog(None) + self.dockwidget.custom_crs_modal.dataStorage = self.dataStorage + self.dockwidget.custom_crs_modal.populateModeDropdown() + self.dockwidget.custom_crs_modal.populateSurveyPoint() + self.dockwidget.custom_crs_modal.populateOffsets() + self.dockwidget.custom_crs_modal.populateRotation() + + self.dockwidget.custom_crs_modal.dialog_button_box.button( + QtWidgets.QDialogButtonBox.Apply + ).clicked.connect(self.customCRSApply) + crs_info_url = "https://speckle.guide/user/qgis.html#custom-project-center" + self.dockwidget.custom_crs_modal.dialog_button_box.button( + QtWidgets.QDialogButtonBox.Cancel + ).clicked.connect(lambda: self.openUrl(crs_info_url)) + + self.dockwidget.custom_crs_modal.show() + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self.dockwidget) + return + + def openUrl(self, url: str = ""): + import webbrowser + + # url = "https://speckle.guide/user/qgis.html#custom-project-center" + try: + if "/commits/" in url or "/models/" in url: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Open In Web", + "connector_version": str(self.dataStorage.plugin_version), + "data": "Commit", + }, + ) + else: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Open In Web", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + if url is not None and url != "": + webbrowser.open(url, new=0, autoraise=True) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/LogWidget.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/LogWidget.py deleted file mode 100644 index 8defb69..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/LogWidget.py +++ /dev/null @@ -1,153 +0,0 @@ - -import time -from typing import Any, List, Tuple -from PyQt5 import QtCore -from PyQt5.QtCore import QCoreApplication, QSettings, Qt, QTranslator, QRect, QObject -from PyQt5.QtWidgets import QAction, QDockWidget, QVBoxLayout, QWidget, QPushButton -from PyQt5 import QtWidgets -import webbrowser -from specklepy.logging import metrics -from specklepy.api.credentials import Account - -import inspect - -try: - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -SPECKLE_COLOR = (59,130,246) -SPECKLE_COLOR_LIGHT = (69,140,255) - -BACKGR_COLOR = f"background-color: rgb{str(SPECKLE_COLOR)};" -BACKGR_COLOR_LIGHT = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};" - -BACKGR_COLOR_GREY = f"background-color: Gainsboro;" - -class LogWidget(QWidget): - - msgs: List[str] = [] - used_btns: List[int] = [] - btns: List[QPushButton] - max_msg: int - - active_account: Account - speckle_version: str - - # constructor - def __init__(self, parent=None): - super(LogWidget, self).__init__(parent) - print("start LogWidget") - self.parentWidget = parent - print(self.parentWidget) - self.max_msg = 10 - - # create a temporary floating button - width = 0 #parent.frameSize().width() - height = 0# parent.frameSize().height() - - self.setAttribute(QtCore.Qt.WA_StyledBackground, True) - self.setStyleSheet("background-color: rgba(250,250,250,80);") - - self.layout = QVBoxLayout(self) - self.layout.setContentsMargins(10, 60, 10, 40) - self.layout.setAlignment(Qt.AlignBottom) - self.setGeometry(0, 0, width, height) - - # generate 100 buttons to use later - self.btns = [] - for i in range(self.max_msg): - button = QPushButton(f"👌 Error") # to '{streamName}' Sent , v - button.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") - button.clicked.connect(lambda: self.openLink()) - button.clicked.connect(lambda: self.hide()) - self.btns.append(button) - - self.hide() - - # overriding the mouseReleaseEvent method - def mouseReleaseEvent(self, event): - print("Mouse Release Event") - self.hide() - #self.parentWidget.hideError() - - def hide(self): - - self.setGeometry(0, 0, 0, 0) - - # remove all buttons - for i in reversed(range(self.layout.count())): - self.layout.itemAt(i).widget().setParent(None) - - # remove list of used btns - self.used_btns.clear() - self.msgs.clear() - - - def addButton(self, text: str = "something went wrong", level: int = 2, url = "", blue = False): - print("Add button") - - self.setGeometry(0, 0, self.parentWidget.frameSize().width(), self.parentWidget.frameSize().height()) - - # find index of the first unused button - btn, index = self.getNextBtn() - btn.setAccessibleName(url) - - if url != "": - btn.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_COLOR_LIGHT}" + " }") - - else: - if blue is False: - btn.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") - else: - btn.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "}") - - btn.setText(text) - self.resizeToText(btn) - - #btn.resize(btn.sizeHint()) - self.layout.addWidget(btn) #, alignment=Qt.AlignCenter) - - self.msgs.append(text) - self.used_btns.append(1) - - def openLink(self, url = ""): - try: - btn = self.sender() - url = btn.accessibleName() - if url == "": return - - webbrowser.open(url, new=0, autoraise=True) - - try: - metrics.track("Connector Action", self.active_account, {"name": "Open In Web", "connector_version": str(self.speckle_version)}) - except: - pass - - self.hide() - except Exception as e: - pass #logger.logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def getNextBtn(self) -> Tuple[QPushButton, int]: - index = len(self.used_btns) # get the next "free" button - - if index >= len(self.btns): - # remove first button - self.layout.itemAt(0).widget().setParent(None) - - self.used_btns.clear() - index = 0 - - btn = self.btns[index] - return btn, index - - def resizeToText(self, btn): - try: - text = btn.text() - if len(text.split("\n"))>2: - height = len(text.split("\n"))*25 - btn.setMinimumHeight(height) - return btn - except Exception as e: - print(e) - return btn diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.py deleted file mode 100644 index 137b187..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -from typing import List, Union -#import ui.speckle_qgis_dialog - -from PyQt5 import QtWidgets, uic, QtCore -from PyQt5.QtCore import pyqtSignal -from specklepy.api.models import Stream -from specklepy.api.client import SpeckleClient -from specklepy.logging.exceptions import SpeckleException - -from specklepy.api.credentials import get_local_accounts #, StreamWrapper -from specklepy.api.wrapper import StreamWrapper -from gql import gql - -import inspect - -try: - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -import arcpy - -# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer -ui_class = os.path.dirname(os.path.abspath(__file__)) + "/add_stream_modal.ui" - -class AddStreamModalDialog(QtWidgets.QWidget): - - search_button: QtWidgets.QPushButton = None - search_text_field: QtWidgets.QLineEdit = None - search_results_list: QtWidgets.QListWidget = None - dialog_button_box: QtWidgets.QDialogButtonBox = None - accounts_dropdown: QtWidgets.QComboBox - - stream_results: List[Stream] = [] - speckle_client: Union[SpeckleClient, None] = None - - #Events - handleStreamAdd = pyqtSignal(StreamWrapper) - - def __init__(self, parent=None, speckle_client: SpeckleClient = None): - super(AddStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint) - uic.loadUi(ui_class, self) # Load the .ui file - self.show() - try: - - self.speckle_client = speckle_client - - self.setWindowTitle("Add Speckle stream") - - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - - self.search_button.clicked.connect(self.onSearchClicked) - self.search_results_list.currentItemChanged.connect( self.searchResultChanged ) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked) - self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected) - self.populate_accounts_dropdown() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def searchResultChanged(self): - try: - index = self.search_results_list.currentIndex().row() - if index == -1: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - else: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def onSearchClicked(self): - try: - query = self.search_text_field.text() - sw = None - results = [] - if "http" in query and len(query.split("/")) >= 3: # URL - sw = StreamWrapper(query) - stream = sw.get_client().stream.get(sw.stream_id) - if isinstance(stream, Stream): results = [stream] - else: results = [] - - elif self.speckle_client is not None: - results = self.speckle_client.stream.search(query) - elif self.speckle_client is None: - logToUser(f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", level=2, func = inspect.stack()[0][3]) - - self.stream_results = results - self.populateResultsList(sw) - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def populateResultsList(self, sw): - try: - self.search_results_list.clear() - if isinstance(self.stream_results, SpeckleException): - logToUser("Some streams cannot be accessed", level=1, func = inspect.stack()[0][3]) - return - for stream in self.stream_results: - host = "" - if sw is not None: - host = sw.get_account().serverInfo.url - else: - host = self.speckle_client.account.serverInfo.url - - if isinstance(stream, SpeckleException): - logToUser("Some streams cannot be accessed", level=1, func = inspect.stack()[0][3]) - else: - self.search_results_list.addItems([ - f"{stream.name}, {stream.id} | {host}" #for stream in self.stream_results - ]) - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def onOkClicked(self): - try: - if isinstance(self.stream_results, SpeckleException): - logToUser("Selected stream cannot be accessed", level=1, func = inspect.stack()[0][3]) - return - #elif index == -1 or len(self.stream_results) == 0: - # logger.logToUser("Select stream from \"Search Results\". No stream selected", Qgis.Warning) - # return - else: - try: - index = self.search_results_list.currentIndex().row() - stream = self.stream_results[index] - item = self.search_results_list.item(index) - url = item.text().split(" | ")[1] + "/streams/" + item.text().split(", ")[1].split(" | ")[0] - sw = StreamWrapper(url) - #acc = sw.get_account() #get_local_accounts()[self.accounts_dropdown.currentIndex()] - self.handleStreamAdd.emit(sw) #StreamWrapper(f"{acc.serverInfo.url}/streams/{stream.id}?u={acc.userInfo.id}")) - self.close() - except Exception as e: - logToUser("Some streams cannot be accessed: " + str(e), level=1, func = inspect.stack()[0][3]) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def onCancelClicked(self): - self.close() - - def onAccountSelected(self, index): - try: - account = self.speckle_accounts[index] - self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https")) - self.speckle_client.authenticate_with_token(token=account.token) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def populate_accounts_dropdown(self): - # Populate the accounts comboBox - try: - self.speckle_accounts = get_local_accounts() - self.accounts_dropdown.clear() - self.accounts_dropdown.addItems( - [ - f"{acc.userInfo.name}, {acc.userInfo.email} | {acc.serverInfo.url}" - for acc in self.speckle_accounts - ] - ) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.ui b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.ui deleted file mode 100644 index b50dc45..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/add_stream_modal.ui +++ /dev/null @@ -1,87 +0,0 @@ - - - AddStreamDialog - - - Qt::NonModal - - - - 0 - 0 - - - - Form - - - - - - QLayout::SetNoConstraint - - - - - - - Search Stream by name or URL - - - - - - - - - - Search - - - - - - - - - - Account - - - - - - - - - Search Results - - - - - - - - 0 - 100 - - - - QAbstractScrollArea::AdjustToContents - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.py deleted file mode 100644 index 129d0e5..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -from typing import List, Tuple, Union -#import ui.speckle_qgis_dialog - -from PyQt5 import QtWidgets, uic, QtCore -from PyQt5.QtCore import pyqtSignal -from specklepy.api.models import Stream -from specklepy.api.client import SpeckleClient -from specklepy.logging.exceptions import SpeckleException - -from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper -from specklepy.api.wrapper import StreamWrapper -from gql import gql - -import inspect - -import arcpy -try: - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer - -ui_class = os.path.dirname(os.path.abspath(__file__)) + "/create_branch.ui" - -class CreateBranchModalDialog(QtWidgets.QWidget): - - name_field: QtWidgets.QLineEdit = None - description_field: QtWidgets.QLineEdit = None - dialog_button_box: QtWidgets.QDialogButtonBox = None - speckle_client: Union[SpeckleClient, None] = None - - #Events - handleBranchCreate = pyqtSignal(str,str) - - def __init__(self, parent=None, speckle_client: SpeckleClient = None): - super(CreateBranchModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint) - uic.loadUi(ui_class, self) # Load the .ui file - self.show() - try: - - self.speckle_client = speckle_client - - self.setWindowTitle("Create New Branch") - - self.name_field.textChanged.connect(self.nameCheck) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def nameCheck(self): - try: - if len(self.name_field.text()) >= 3: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - else: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def onOkClicked(self): - try: - name = self.name_field.text() - description = self.description_field.text() - self.handleBranchCreate.emit(name, description) - self.close() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return - - def onCancelClicked(self): - self.close() - - def onAccountSelected(self, index): - try: - account = self.speckle_accounts[index] - self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https")) - self.speckle_client.authenticate_with_token(token=account.token) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.ui b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.ui deleted file mode 100644 index 992234f..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_branch.ui +++ /dev/null @@ -1,64 +0,0 @@ - - - CreateStreamDialog - - - Qt::NonModal - - - - 0 - 0 - - - - Form - - - - - - QLayout::SetNoConstraint - - - - - - - - Branch Name - - - - - - - Description - - - - - - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.py deleted file mode 100644 index 495200d..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -from typing import List, Tuple, Union -#import ui.speckle_qgis_dialog - - -from PyQt5 import QtWidgets, uic, QtCore -from PyQt5.QtCore import pyqtSignal - -import arcpy - -from specklepy.api.models import Stream -from specklepy.api.client import SpeckleClient -from specklepy.logging.exceptions import SpeckleException - -from specklepy.api.credentials import Account, get_local_accounts #, StreamWrapper -from specklepy.api.wrapper import StreamWrapper -from gql import gql -import inspect - -try: - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer - -ui_class = os.path.dirname(os.path.abspath(__file__)) + "/create_stream.ui" - -class CreateStreamModalDialog(QtWidgets.QWidget): - - name_field: QtWidgets.QLineEdit = None - description_field: QtWidgets.QLineEdit = None - dialog_button_box: QtWidgets.QDialogButtonBox = None - accounts_dropdown: QtWidgets.QComboBox - public_toggle: QtWidgets.QCheckBox - - speckle_client: Union[SpeckleClient, None] = None - - #Events - handleStreamCreate = pyqtSignal(Account, str, str, bool) - - def __init__(self, parent=None, speckle_client: SpeckleClient = None): - super(CreateStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint) - uic.loadUi(ui_class, self) # Load the .ui file - self.show() - try: - - self.speckle_client = speckle_client - - self.setWindowTitle("Create New Stream") - - self.name_field.textChanged.connect(self.nameCheck) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked) - self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected) - self.populate_accounts_dropdown() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def nameCheck(self): - try: - if len(self.name_field.text()) == 0 or len(self.name_field.text()) >= 3: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - else: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def onOkClicked(self): - try: - acc = get_local_accounts()[self.accounts_dropdown.currentIndex()] - name = self.name_field.text() - description = self.description_field.text() - public = self.public_toggle.isChecked() - self.handleStreamCreate.emit(acc,name,description,public) - self.close() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return - - def onCancelClicked(self): - #self.handleCancelStreamCreate.emit() - self.close() - - def onAccountSelected(self, index): - try: - account = self.speckle_accounts[index] - self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https")) - self.speckle_client.authenticate_with_token(token=account.token) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - def populate_accounts_dropdown(self): - try: - # Populate the accounts comboBox - self.speckle_accounts = get_local_accounts() - self.accounts_dropdown.clear() - self.accounts_dropdown.addItems( - [ - f"{acc.userInfo.name}, {acc.userInfo.email} | {acc.serverInfo.url}" - for acc in self.speckle_accounts - ] - ) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.ui b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.ui deleted file mode 100644 index b0ae678..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/create_stream.ui +++ /dev/null @@ -1,85 +0,0 @@ - - - CreateStreamDialog - - - Qt::NonModal - - - - 0 - 0 - - - - Form - - - - - - QLayout::SetNoConstraint - - - - - - - - Account - - - - - - - - Stream Name - - - - - - - Description - - - - - - - Public - - - - - - - - - - - - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-black.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-black.png deleted file mode 100644 index a64d9e2..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-black.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-blue.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-blue.png deleted file mode 100644 index bf1a6ff..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive-blue.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive.png deleted file mode 100644 index df2ef15..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-receive.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-black.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-black.png deleted file mode 100644 index fa92ea0..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-black.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-blue.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-blue.png deleted file mode 100644 index aca8091..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send-blue.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send.png deleted file mode 100644 index 14f12da..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/cube-send.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete-blue.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete-blue.png deleted file mode 100644 index d73c2b4..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete-blue.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete.png deleted file mode 100644 index 0273745..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/delete.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_line.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_line.png deleted file mode 100644 index 57c2f88..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_line.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_point.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_point.png deleted file mode 100644 index e869f9e..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_point.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_polygon.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_polygon.png deleted file mode 100644 index b4034b0..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_polygon.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_raster.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_raster.png deleted file mode 100644 index 28b894e..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/legend_raster.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logger.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logger.py deleted file mode 100644 index fa8f240..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logger.py +++ /dev/null @@ -1,55 +0,0 @@ - -from PyQt5.QtWidgets import QMessageBox -from PyQt5 import QtCore -import arcpy -try: - from speckle.speckle.plugin_utils.helpers import splitTextIntoLines -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import splitTextIntoLines - -import inspect - -def logToUser(msg: str, func=None, level: int = 2, plugin = None, url = "", blue = False): - print("Log to user") - - msg = str(msg) - dockwidget = plugin - - try: - if url == "" and blue is False: # only for info messages - msg = addLevelSymbol(msg, level) - if func is not None: - msg += "::" + str(func) - writeToLog(msg, level) - - if dockwidget is None: return - - new_msg = splitTextIntoLines(msg, 70) - - dockwidget.msgLog.addButton(new_msg, level=level, url=url, blue=blue) - - except Exception as e: print(e); return - -def logToUserWithAction(msg: str, level: int = 0, plugin = None, url = ""): - print("Log to user with action") - return - msg = str(msg) - dockwidget = plugin - if dockwidget is None: return - try: - new_msg = splitTextIntoLines(msg, 70) - dockwidget.msgLog.addButton(new_msg, level=level, url=url) - writeToLog(new_msg, level) - except Exception as e: print(e); return - -def addLevelSymbol(msg: str, level: int): - if level == 0: msg = "🛈 " + msg - if level == 1: msg = "⚠️ " + msg - if level == 2: msg = "❗ " + msg - return msg - -def writeToLog(msg: str = "", level: int = 2): - print(msg) - if level == 0: arcpy.AddMessage(msg) - if level == 1: arcpy.AddWarning(msg) - if level == 2: arcpy.AddError(msg) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logo-slab-white@0.5x.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logo-slab-white@0.5x.png deleted file mode 100644 index b704b77..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/logo-slab-white@0.5x.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/magnify.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/magnify.png deleted file mode 100644 index ec33a63..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/magnify.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py deleted file mode 100644 index 75f3cf7..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/project_vars.py +++ /dev/null @@ -1,357 +0,0 @@ - -from typing import Any, List, Optional, Tuple, Union -import arcpy -from arcpy._mp import ArcGISProject, Map, Layer as arcLayer -from arcpy.management import CreateTable - -import os.path - -from specklepy.api.credentials import Account, get_local_accounts -from specklepy.api.client import SpeckleClient -from specklepy.logging.exceptions import ( - GraphQLException, - SpeckleException, -) -from specklepy.api.wrapper import StreamWrapper -from specklepy.api.models import Branch, Stream, Streams -from specklepy.logging import metrics - -from osgeo import osr - -import inspect - -try: - from speckle.speckle.ui.validation import tryGetStream - from speckle.speckle.speckle_arcgis import SpeckleGIS - from speckle.speckle.converter.layers import getAllProjLayers - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.validation import tryGetStream - from speckle_toolbox.esri.toolboxes.speckle.speckle.speckle_arcgis import SpeckleGIS - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import getAllProjLayers - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -FIELDS = ["project_streams","project_layer_selection", "lat_lon"] - -def get_project_streams(plugin: SpeckleGIS, content: str = None): - try: - print("GET proj streams") - project = plugin.gis_project - table = findOrCreateSpeckleTable(project) - logToUser(table, level=0, func = inspect.stack()[0][3]) - - rows = arcpy.da.SearchCursor(table, "project_streams") - saved_streams = [] - for x in rows: - logToUser(x, level=0, func = inspect.stack()[0][3]) - saved_streams.append(x[0]) - - temp = [] - ######### need to check whether saved streams are available (account reachable) - if len(saved_streams) > 0: - for url in saved_streams: - if url=="": continue - try: - sw = StreamWrapper(url) - try: - stream = tryGetStream(sw, plugin.dataStorage) - except SpeckleException as e: - logToUser(e.message, level=2, func = inspect.stack()[0][3]) - stream = None - #strId = stream.id # will cause exception if invalid - temp.append((sw, stream)) - except SpeckleException as e: - logToUser(e.message, level=2, func = inspect.stack()[0][3]) - #except GraphQLException as e: - # logger.logToUser(e.message, Qgis.Warning) - plugin.current_streams = temp - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -def set_project_streams(plugin: SpeckleGIS): - try: - print("SET proj streams") - project = plugin.gis_project - table = findOrCreateSpeckleTable(project) - print("SET proj streams 2") - - value = [stream[0].stream_url for stream in plugin.current_streams] #",".join() - print(value) - logToUser(value, level=0, func = inspect.stack()[0][3]) - - if table is not None: - proj_layers = [] - lan_lot = "" - with arcpy.da.UpdateCursor(table, FIELDS) as cursor: - for row in cursor: # just one row - if row[1] is not None and row[1] != "": proj_layers.append(row[1]) - if row[2] is not None and row[2] != "": lan_lot = row[2] - cursor.deleteRow() - del cursor - if len(proj_layers) == 0: proj_layers.append("") - if len(value) == 0: value.append("") - - cursor = arcpy.da.InsertCursor(table, FIELDS ) - length = max(len(proj_layers), len(value)) - - for i in range(length): - if i==0: - cursor.insertRow([value[i], proj_layers[i] , lan_lot]) - else: - try: - cursor.insertRow([value[i], proj_layers[i] , ""]) - except: - if len(value) <= i: cursor.insertRow(["", proj_layers[i] , ""]) - if len(proj_layers) <= i: cursor.insertRow([value[i], "" , ""]) - del cursor - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -def get_project_layer_selection(plugin: SpeckleGIS): - try: - print("GET project layer selection from the table") - project = plugin.gis_project - table = findOrCreateSpeckleTable(project) - if table is None: return - - rows = arcpy.da.SearchCursor(table, "project_layer_selection") - saved_layers = [] - for x in rows: - saved_layers.append(x[0]) - - - temp = [] - proj_layers = getAllProjLayers(project) - ######### need to check whether saved streams are available (account reachable) - if len(saved_layers) > 0: - for layerPath in saved_layers: - if layerPath == "": continue - found = 0 - for layer in proj_layers: - print(layer.dataSource) - if layer.dataSource == layerPath: - temp.append((layer.name, layer)) - found += 1 - break - if found == 0: - logToUser(f'Saved layer not found: "{layerPath}"', level=1, func = inspect.stack()[0][3]) - plugin.current_layers = temp - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -def set_project_layer_selection(plugin: SpeckleGIS): - try: - print("SET project layer selection function") - project = plugin.gis_project - value: List[str] = [layer[1].dataSource for layer in plugin.current_layers] #",".join([layer[1].dataSource for layer in plugin.current_layers]) - print(value) - - table = findOrCreateSpeckleTable(project) - #print(table) - if table is not None: - lan_lot = "" - proj_streams = [] - with arcpy.da.UpdateCursor(table, FIELDS) as cursor: - for row in cursor: # just one row - if row[0] is not None and row[0] != "": proj_streams.append(row[0]) - if row[2] is not None and row[2] != "": lan_lot = row[2] - cursor.deleteRow() - del cursor - if len(proj_streams) == 0: proj_streams.append("") - if len(value) == 0: value.append("") - #print(proj_streams) - - cursor = arcpy.da.InsertCursor(table, FIELDS ) - length = max(len(proj_streams), len(value)) - #print(length) - for i in range(length): - #print(i) - if i==0: - cursor.insertRow([proj_streams[i], value[i] , lan_lot]) - print(i) - else: - try: - cursor.insertRow([proj_streams[i], value[i] , ""]) - except: - if len(proj_streams) <= i: cursor.insertRow(["", value[i] , ""]) - if len(value) <= i: cursor.insertRow([proj_streams[i], "" , ""]) - #print(i) - del cursor - - try: - metrics.track("Connector Action", plugin.active_account, {"name": "Save Layer Selection", "connector_version": str(plugin.version)}) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - - #print(table) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - - print("SET project layer selection 2") - -def get_survey_point(plugin: SpeckleGIS, content = None): - try: - print("get survey point") - project = plugin.gis_project - table = findOrCreateSpeckleTable(project) - if table is None: return - - rows = arcpy.da.SearchCursor(table, "lat_lon") - points = "" - for x in rows: - points = x[0] - break - - if points != "": - vals: List[str] = points.replace(" ","").split(";")[:2] - plugin.lat, plugin.lon = [float(i) for i in vals] - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -def set_survey_point(plugin: SpeckleGIS): - - try: - # from widget (2 strings) to local vars + update SR of the map - print("SET survey point") - - project = plugin.gis_project - vals =[ str(plugin.dockwidget.surveyPointLat.text()), str(plugin.dockwidget.surveyPointLon.text()) ] - - plugin.lat, plugin.lon = [float(i.replace(" ","")) for i in vals] - - if plugin.lat>180 or plugin.lat<-180 or plugin.lon >180 or plugin.lon<-180: - logToUser("LAT LON values must be within (-180, 180). You can right-click on the canvas location to copy coordinates in WGS 84", level = 1, plugin=self.dockwidget) - return True - pt = str(plugin.lat) + ";" + str(plugin.lon) - - table = findOrCreateSpeckleTable(project) - if table is not None: - with arcpy.da.UpdateCursor(table, ["lat_lon"]) as cursor: - for row in cursor: # just one row - cursor.updateRow([pt]) - break - del cursor - - setProjectReferenceSystem(plugin) - - try: - metrics.track("Connector Action", plugin.active_account, {"name": "Set As Center Point", "connector_version": str(plugin.version)}) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - - return True - - except Exception as e: - plugin.dockwidget.surveyPointLat.setText(str(plugin.lat)) - plugin.dockwidget.surveyPointLon.setText(str(plugin.lon)) - logToUser("Lat, Lon values invalid: " + str(e), level=2, func = inspect.stack()[0][3]) - return False - -def setProjectReferenceSystem(plugin: SpeckleGIS): - try: - # save to project; create SR - newCrsString = "+proj=tmerc +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=" + str(plugin.lon) + " lat_0=" + str(plugin.lat) + " +x_0=0 +y_0=0 +k_0=1" - newCrs = osr.SpatialReference() - newCrs.ImportFromProj4(newCrsString) - newCrs.MorphToESRI() # converts the WKT to an ESRI-compatible format - - validate = True if len(newCrs.ExportToWkt())>10 else False - - if validate: - newProjSR = arcpy.SpatialReference() - newProjSR.loadFromString(newCrs.ExportToWkt()) - - #source = osr.SpatialReference() - #source.ImportFromWkt(plugin.project.activeMap.spatialReference.exportToString()) - #transform = osr.CoordinateTransformation(source, newCrs) - - plugin.gis_project.activeMap.spatialReference = newProjSR - logToUser("Custom project Spatial Reference successfully applied", level=0, func = inspect.stack()[0][3]) - else: - logToUser("Custom Spatial Reference could not be created", level=1, func = inspect.stack()[0][3]) - - return True - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return False - -def findOrCreateSpeckleTable(project: ArcGISProject) -> Union[str, None]: - try: - path = arcpy.env.workspace #project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" # - - if 'speckle_gis' not in arcpy.ListTables(): - try: - table = CreateTable(path, "speckle_gis") - arcpy.management.AddField(table, "project_streams", "TEXT") - arcpy.management.AddField(table, "project_layer_selection", "TEXT") - arcpy.management.AddField(table, "lat_lon", "TEXT") - - cursor = arcpy.da.InsertCursor(table, FIELDS ) - cursor.insertRow(["","",""]) - del cursor - - except Exception as e: - logToUser("Error creating a table: " + str(e), level=1, func = inspect.stack()[0][3]) - raise e - else: - #print("table already exists") - # make sure fileds exist - table = path + "\\speckle_gis" - findOrCreateTableField(table, FIELDS[0]) - findOrCreateTableField(table, FIELDS[1]) - findOrCreateTableField(table, FIELDS[2]) - - findOrCreateRow(table, FIELDS) - - return table - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None - -def findOrCreateTableField(table: str, field: str): - try: - with arcpy.da.UpdateCursor(table, [field]) as cursor: - value = None - for row in cursor: - value = row # tuple(val,) - if value[0] is None: cursor.updateRow("") - break # look at the 1st row only - del cursor - - #if value is None: # if there are no rows - # cursor = arcpy.da.InsertCursor(table, [field]) - # cursor.insertRow([""]) - # del cursor - - except: # if field doesn't exist - arcpy.management.AddField(table, field, "TEXT") - #cursor = arcpy.da.InsertCursor(table, [field] ) - #cursor.insertRow([""]) - del cursor - -def findOrCreateRow(table:str, fields: List[str]): - try: - # check if the row exists - cursor = arcpy.da.SearchCursor(table, fields) - k=-1 - for k, row in enumerate(cursor): - #print(row) - break - del cursor - - # if no rows - if k == -1: - cursor = arcpy.da.InsertCursor(table, fields) - cursor.insertRow(["", "", ""]) - del cursor - else: - with arcpy.da.UpdateCursor(table, fields) as cursor: - for row in cursor: - if None in row: cursor.updateRow(["","",""]) - break # look at the 1st row only - del cursor - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/size-xxl.png b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/size-xxl.png deleted file mode 100644 index 33a45e7..0000000 Binary files a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/size-xxl.png and /dev/null differ diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py deleted file mode 100644 index 5b1edf4..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog.py +++ /dev/null @@ -1,647 +0,0 @@ - -import os -import sys -from typing import List -#from speckle.converter.layers import getLayers - -#import ui.speckle_qgis_dialog - -from specklepy.logging.exceptions import (SpeckleException, GraphQLException) -from PyQt5 import QtWidgets, uic -from PyQt5 import QtGui -from PyQt5.QtGui import QIcon, QPixmap -from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, - QListWidgetItem, QAction, QDockWidget, QVBoxLayout, - QHBoxLayout, QWidget, QLabel) -from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent -from PyQt5 import QtGui, uic - -from specklepy.api.credentials import get_local_accounts - -import importlib - -from specklepy.api.wrapper import StreamWrapper -from specklepy.api.client import SpeckleClient -from specklepy.logging import metrics - -import arcpy - -import inspect - -try: - #from speckle.speckle_arcgis_new import Speckle - from speckle.speckle.converter.layers import getLayers, getAllProjLayers - from speckle.speckle.ui.logger import logToUser - from speckle.speckle.ui.LogWidget import LogWidget -except: - #from speckle_toolbox.esri.toolboxes.speckle.speckle_arcgis_new import Speckle - from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import getLayers, getAllProjLayers - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.LogWidget import LogWidget - -#from ui.validation import tryGetStream - -# Create module-like object -#pytPath = os.path.dirname(os.path.abspath(__file__)).replace("/speckle/ui","/Speckle.pyt") -#print(pytPath) -#pytModule = importlib.machinery.SourceFileLoader("specklePyt", pytPath ) -#specklePyt = pytModule.load_module("specklePyt") - - -# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer - -COLOR_HIGHLIGHT = (210,210,210) - -SPECKLE_COLOR = (59,130,246) -SPECKLE_COLOR_LIGHT = (69,140,255) -ICON_LOGO = os.path.dirname(os.path.abspath(__file__)) + "/logo-slab-white@0.5x.png" - -ICON_SEARCH = os.path.dirname(os.path.abspath(__file__)) + "/magnify.png" - -ICON_DELETE = os.path.dirname(os.path.abspath(__file__)) + "/delete.png" -ICON_DELETE_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/delete-blue.png" - -ICON_SEND = os.path.dirname(os.path.abspath(__file__)) + "/cube-send.png" -ICON_RECEIVE = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive.png" - -ICON_SEND_BLACK = os.path.dirname(os.path.abspath(__file__)) + "/cube-send-black.png" -ICON_RECEIVE_BLACK = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive-black.png" - -ICON_SEND_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/cube-send-blue.png" -ICON_RECEIVE_BLUE = os.path.dirname(os.path.abspath(__file__)) + "/cube-receive-blue.png" - -COLOR = f"color: rgb{str(SPECKLE_COLOR)};" -BACKGR_COLOR = f"background-color: rgb{str(SPECKLE_COLOR)};" -BACKGR_COLOR_LIGHT = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};" - -ui_class = os.path.dirname(os.path.abspath(__file__)) + "/speckle_qgis_dialog_base.ui" -print(os.path.dirname(__file__)) - -class SpeckleGISDialog(QMainWindow): - - closingPlugin = pyqtSignal() - streamList: QtWidgets.QComboBox - sendModeButton: QtWidgets.QPushButton - receiveModeButton: QtWidgets.QPushButton - streamBranchDropdown: QtWidgets.QComboBox - layerSendModeDropdown: QtWidgets.QComboBox - commitDropdown: QtWidgets.QComboBox - layersWidget: QtWidgets.QListWidget - saveLayerSelection: QtWidgets.QPushButton - runButton: QtWidgets.QPushButton - msgLog: LogWidget = None - - gridLayoutTitleBar = QtWidgets.QGridLayout - - def __init__(self): - """Constructor.""" - print("START MAIN WINDOW") - super(SpeckleGISDialog, self).__init__(None)#, QtCore.Qt.WindowStaysOnTopHint) - uic.loadUi(ui_class, self) # Load the .ui file - #self.installEventFilter(self) - self.show() - #self.instances.append(1) - try: - self.streamBranchDropdown.setMaxCount(100) - self.commitDropdown.setMaxCount(100) - - self.streams_add_button.setFlat(True) - self.streams_remove_button.setFlat(True) - self.saveSurveyPoint.setFlat(True) - self.saveLayerSelection.setFlat(True) - self.reloadButton.setFlat(True) - self.closeButton.setFlat(True) - - #backgr_color = f"background-color: rgb{str(SPECKLE_COLOR)};" - #backgr_color_light = f"background-color: rgb{str(SPECKLE_COLOR_LIGHT)};" - backgr_image_del = f"border-image: url({ICON_DELETE_BLUE});" - self.streams_add_button.setIcon(QIcon(ICON_SEARCH)) - self.streams_add_button.setMaximumWidth(25) - self.streams_add_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") - self.streams_remove_button.setIcon(QIcon(ICON_DELETE)) - self.streams_remove_button.setMaximumWidth(25) - self.streams_remove_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left; image-position:right} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") #+ f"{backgr_image_del}" - - self.saveLayerSelection.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - self.saveSurveyPoint.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - self.reloadButton.setStyleSheet("QPushButton {text-align: left;} QPushButton:hover { " + f"{COLOR}" + " }") - self.closeButton.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - - - self.sendModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px; " + f"color: rgb{str(SPECKLE_COLOR)};"+ "} QPushButton:hover { " + "}" ) - self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE)) - - self.receiveModeButton.setFlat(True) - self.receiveModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px;}"+ "QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + "}" ) - self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK)) - - self.runButton.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 10px;"+ f"{BACKGR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_COLOR_LIGHT}" + " }") - self.runButton.setMaximumWidth(200) - self.runButton.setIcon(QIcon(ICON_SEND)) - - # add widgets that will only show on event trigger - logWidget = LogWidget(parent=self) - self.layout().addWidget(logWidget) - self.msgLog = logWidget - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def addProps(self, plugin): - self.msgLog.active_account = plugin.active_account - self.msgLog.speckle_version = plugin.version - - def addLabel(self, plugin): - - try: - exitIcon = QPixmap(ICON_LOGO) - exitActIcon = QIcon(exitIcon) - - # create a label - text_label = QtWidgets.QPushButton(" for ArcGIS") - text_label.setStyleSheet("border: 0px;" - "color: white;" - f"{BACKGR_COLOR}" - "top-margin: 40 px;" - "padding: 10px;" - "padding-left: 20px;" - "font-size: 15px;" - "height: 30px;" - "text-align: left;" - ) - text_label.setIcon(exitActIcon) - text_label.setIconSize(QSize(300, 93)) - text_label.setMinimumSize(QSize(100, 40)) - text_label.setMaximumWidth(220) - - version = "" - try: - if isinstance(plugin.version, str): version = str(plugin.version) - except: pass - - version_label = QtWidgets.QPushButton(f"{version}") - version_label.setStyleSheet("border: 0px;" - "color: white;" - f"{BACKGR_COLOR}" - "padding-top: 15px;" - "padding-left: 0px;" - "margin-left: 0px;" - "font-size: 10px;" - "height: 30px;" - "text-align: left;" - ) - - widget = QWidget() - widget.setStyleSheet(f"{BACKGR_COLOR}") - connect_box = QHBoxLayout(widget) - connect_box.addWidget(text_label) #, alignment=Qt.AlignCenter) - connect_box.addWidget(version_label) - connect_box.setContentsMargins(0, 0, 0, 0) - self.gridLayoutTitleBar.addWidget(widget) # fro QMainWindow - #self.setTitleBarWidget(widget) # for QDockWidget - except Exception as e: - logToUser(e) - - def resizeEvent(self, event): - try: - #print("resize") - QtWidgets.QMainWindow.resizeEvent(self, event) - if self.msgLog.size().height() != 0: # visible - self.msgLog.setGeometry(0, 0, self.msgLog.parentWidget.frameSize().width(), self.msgLog.parentWidget.frameSize().height()) #.resize(self.frameSize().width(), self.frameSize().height()) - except Exception as e: - #logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def closeEvent(self, event): - try: - #import threading - print("Close event") - #threads = threading.enumerate() - #print(f"Threads total: {str(len(threads))}: {str(threads)}") - - #print(self.instances) - - self.closingPlugin.emit() - event.accept() - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def clearDropdown(self): - try: - #self.streamIdField.clear() - self.streamBranchDropdown.clear() - self.commitDropdown.clear() - #self.layerSendModeDropdown.clear() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def reloadDialogUI(self, plugin): - try: - self.clearDropdown() - self.populateUI(plugin) - self.enableElements(plugin) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - - - def run(self, plugin): - try: - print("dockwidget run") - # Setup events on first load only! - self.setupOnFirstLoad(plugin) - # Connect streams section events - self.completeStreamSection(plugin) - # Populate the UI dropdowns - self.populateUI(plugin) - print("dockwidget run end") - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - - def setupOnFirstLoad(self, plugin): - try: - self.runButton.clicked.connect(plugin.onRunButtonClicked) - - self.streams_add_button.clicked.connect( plugin.onStreamAddButtonClicked ) - self.reloadButton.clicked.connect(lambda: self.refreshClicked(plugin)) - self.closeButton.clicked.connect(lambda: self.closeClicked(plugin)) - self.saveSurveyPoint.clicked.connect(plugin.set_survey_point) - self.saveLayerSelection.clicked.connect(lambda: self.populateLayerDropdown(plugin)) - self.sendModeButton.clicked.connect(lambda: self.setSendMode(plugin)) - self.layerSendModeDropdown.currentIndexChanged.connect( lambda: self.layerSendModeChange(plugin) ) - self.receiveModeButton.clicked.connect(lambda: self.setReceiveMode(plugin)) - - self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) ) - self.commitDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) ) - - self.closingPlugin.connect(plugin.onClosePlugin) - return - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def refreshClicked(self, plugin): - try: - try: - metrics.track("Connector Action", plugin.active_account, {"name": "Refresh", "connector_version": str(plugin.version)}) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - - plugin.reloadUI() - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def closeClicked(self, plugin): - try: - try: - metrics.track("Connector Action", plugin.active_account, {"name": "Close", "connector_version": str(plugin.version)}) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - - plugin.onClosePlugin() - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def setSendMode(self, plugin): - try: - plugin.btnAction = 0 # send - color = f"color: rgb{str(SPECKLE_COLOR)};" - self.sendModeButton.setStyleSheet("border: 0px;" - f"color: rgb{str(SPECKLE_COLOR)};" - "padding: 10px;") - self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE)) - self.sendModeButton.setFlat(False) - self.receiveModeButton.setFlat(True) - self.receiveModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + " };") - self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK)) - #self.receiveModeButton.setFlat(True) - self.runButton.setProperty("text", " SEND") - self.runButton.setIcon(QIcon(ICON_SEND)) - - # enable sections only if in "saved streams" mode - if self.layerSendModeDropdown.currentIndex() == 1: self.layersWidget.setEnabled(True) - if self.layerSendModeDropdown.currentIndex() == 1: self.saveLayerSelection.setEnabled(True) - self.commitDropdown.setEnabled(False) - self.messageInput.setEnabled(True) - self.layerSendModeDropdown.setEnabled(True) - - self.runBtnStatusChanged(plugin) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def setReceiveMode(self, plugin): - try: - plugin.btnAction = 1 # receive - color = f"color: rgb{str(SPECKLE_COLOR)};" - self.receiveModeButton.setStyleSheet("border: 0px;" - f"color: rgb{str(SPECKLE_COLOR)};" - "padding: 10px;") - self.sendModeButton.setIcon(QIcon(ICON_SEND_BLACK)) - self.sendModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px;} QPushButton:hover { " + f"background-color: rgb{str(COLOR_HIGHLIGHT)};" + " };") - self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLUE)) - self.sendModeButton.setFlat(True) - self.receiveModeButton.setFlat(False) - #self.sendModeButton.setFlat(True) - self.runButton.setProperty("text", " RECEIVE") - self.runButton.setIcon(QIcon(ICON_RECEIVE)) - #self.layerSendModeChange(plugin, 1) - self.commitDropdown.setEnabled(True) - self.layersWidget.setEnabled(False) - self.messageInput.setEnabled(False) - self.saveLayerSelection.setEnabled(False) - self.layerSendModeDropdown.setEnabled(False) - - self.runBtnStatusChanged(plugin) - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def completeStreamSection(self, plugin): - self.streams_remove_button.clicked.connect( lambda: self.onStreamRemoveButtonClicked(plugin) ) - self.streamList.currentIndexChanged.connect( lambda: self.onActiveStreamChanged(plugin) ) - self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.populateActiveCommitDropdown(plugin) ) - return - - def populateUI(self, plugin): - try: - self.populateLayerSendModeDropdown() - self.populateLayerDropdown(plugin, False) - #items = [self.layersWidget.item(x).text() for x in range(self.layersWidget.count())] - self.populateProjectStreams(plugin) - self.populateSurveyPoint(plugin) - - self.runBtnStatusChanged(plugin) - self.runButton.setEnabled(False) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def runBtnStatusChanged(self, plugin): - try: - commitStr = str(self.commitDropdown.currentText()) - branchStr = str(self.streamBranchDropdown.currentText()) - - if plugin.btnAction == 1: # on receive - if commitStr == "": - self.runButton.setEnabled(False) - else: - self.runButton.setEnabled(True) - - if plugin.btnAction == 0: # on send - if branchStr == "": - self.runButton.setEnabled(False) - elif branchStr != "" and self.layerSendModeDropdown.currentIndex() == 1 and len(plugin.current_layers) == 0: # saved layers; but the list is empty - self.runButton.setEnabled(False) - else: - self.runButton.setEnabled(True) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - - def layerSendModeChange(self, plugin, runMode = None): - try: - print("Send mode changed") - - if self.layerSendModeDropdown.currentIndex() == 0 or runMode == 1: # by manual selection OR receive mode - self.current_layers = [] - self.layersWidget.setEnabled(False) - self.saveLayerSelection.setEnabled(False) - - elif self.layerSendModeDropdown.currentIndex() == 1 and (runMode == 0 or runMode is None): # by saved AND when Send mode - self.layersWidget.setEnabled(True) - self.saveLayerSelection.setEnabled(True) - - branchStr = str(self.streamBranchDropdown.currentText()) - if self.layerSendModeDropdown.currentIndex() == 0: - if branchStr == "": self.runButton.setEnabled(False) # by manual selection - else: self.runButton.setEnabled(True) # by manual selection - elif self.layerSendModeDropdown.currentIndex() == 1: self.runBtnStatusChanged(plugin) # by saved - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - - def populateLayerDropdown(self, plugin, bySelection: bool = True): - print("populate layer dropdown / clicked save selection") - if not self: return - try: - from speckle.speckle.ui.project_vars import set_project_layer_selection - except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_layer_selection - - try: - self.layersWidget.clear() - nameDisplay = [] - project = plugin.gis_project - - if bySelection is False: # read from project data - print("populate layers from saved data") - #print(project) - #print(project.activeMap) - - all_layers_ids = [l.dataSource for l in getAllProjLayers(project)] - for layer_tuple in plugin.current_layers: - if layer_tuple[1].dataSource in all_layers_ids: - listItem = self.fillLayerList(layer_tuple[1]) - self.layersWidget.addItem(listItem) - - else: # read selected layers - # Fetch selected layers - print("populate layers from selection") - - plugin.current_layers = [] - layers = getLayers(plugin, bySelection) # List[QgsLayerTreeNode] - print(layers) - for i, layer in enumerate(layers): - plugin.current_layers.append((layer.name, layer)) - listItem = self.fillLayerList(layer) - self.layersWidget.addItem(listItem) - print("populate layers from selection 2") - set_project_layer_selection(plugin) - print("populate layers from selection 3") - - self.layersWidget.setIconSize(QSize(20, 20)) - self.runBtnStatusChanged(plugin) - - return - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def fillLayerList(self, layer): - print("Fill layer list") - - try: - ICON_XXL = os.path.dirname(os.path.abspath(__file__)) + "/size-xxl.png" - ICON_RASTER = os.path.dirname(os.path.abspath(__file__)) + "/legend_raster.png" - ICON_POLYGON = os.path.dirname(os.path.abspath(__file__)) + "/legend_polygon.png" - ICON_LINE = os.path.dirname(os.path.abspath(__file__)) + "/legend_line.png" - ICON_POINT = os.path.dirname(os.path.abspath(__file__)) + "/legend_point.png" - - listItem = QListWidgetItem(layer.name) - #print(listItem) - - if layer.isRasterLayer: # and layer.width()*layer.height() > 1000000: - listItem.setIcon(QIcon(ICON_RASTER)) - - elif layer.isFeatureLayer: # and layer.featureCount() > 20000: - geomType = arcpy.Describe(layer.dataSource).shapeType - if geomType == "Polygon": listItem.setIcon(QIcon(ICON_POLYGON)) - elif geomType == "Polyline": listItem.setIcon(QIcon(ICON_LINE)) - elif geomType == "Point" or geomType == "Multipoint": listItem.setIcon(QIcon(ICON_POINT)) - else: - listItem.setIcon(QIcon(ICON_XXL)) - #else: - # icon = QgsIconUtils().iconForLayer(layer) - # listItem.setIcon(icon) - - return listItem - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def populateSurveyPoint(self, plugin): - if not self: - return - try: - self.surveyPointLat.clear() - self.surveyPointLat.setText(str(plugin.lat)) - self.surveyPointLon.clear() - self.surveyPointLon.setText(str(plugin.lon)) - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def enableElements(self, plugin): - try: - self.sendModeButton.setEnabled(plugin.is_setup) - self.receiveModeButton.setEnabled(plugin.is_setup) - self.runButton.setEnabled(plugin.is_setup) - self.streams_add_button.setEnabled(plugin.is_setup) - if plugin.is_setup is False: self.streams_remove_button.setEnabled(plugin.is_setup) - self.streamBranchDropdown.setEnabled(plugin.is_setup) - self.layerSendModeDropdown.setEnabled(plugin.is_setup) - self.commitDropdown.setEnabled(False) - self.show() - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def populateProjectStreams(self, plugin): - - try: - from speckle.speckle.ui.project_vars import set_project_streams - except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.project_vars import set_project_streams - - try: - if not self: return - self.streamList.clear() - for stream in plugin.current_streams: - self.streamList.addItems( - [f"Stream not accessible - {stream[0].stream_id}" if stream[1] is None or isinstance(stream[1], SpeckleException) else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0]}"] - ) - if len(plugin.current_streams)==0: self.streamList.addItems([""]) - self.streamList.addItems(["Create New Stream"]) - set_project_streams(plugin) - index = self.streamList.currentIndex() - if index == -1: self.streams_remove_button.setEnabled(False) - else: self.streams_remove_button.setEnabled(True) - - if len(plugin.current_streams)>0: plugin.active_stream = plugin.current_streams[0] - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - - def onActiveStreamChanged(self, plugin): - - if not self: return - try: - index = self.streamList.currentIndex() - if (len(plugin.current_streams) == 0 and index ==1) or (len(plugin.current_streams)>0 and index == len(plugin.current_streams)): - self.populateProjectStreams(plugin) - plugin.onStreamCreateClicked() - return - if len(plugin.current_streams) == 0: return - if index == -1: return - - try: plugin.active_stream = plugin.current_streams[index] - except: plugin.active_stream = None - - self.populateActiveStreamBranchDropdown(plugin) - self.populateActiveCommitDropdown(plugin) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def populateLayerSendModeDropdown(self): - if not self: return - try: - self.layerSendModeDropdown.clear() - self.layerSendModeDropdown.addItems( - ["Send visible layers", "Send saved layers"] - ) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def populateActiveStreamBranchDropdown(self, plugin): - if not self: return - if plugin.active_stream is None: return - try: - self.streamBranchDropdown.clear() - if isinstance(plugin.active_stream[1], SpeckleException): - #logger.logToUser("Some streams cannot be accessed", Qgis.Warning) - return - elif plugin.active_stream is None or plugin.active_stream[1] is None or plugin.active_stream[1].branches is None: - return - self.streamBranchDropdown.addItems( - [f"{branch.name}" for branch in plugin.active_stream[1].branches.items] - ) - self.streamBranchDropdown.addItems(["Create New Branch"]) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def populateActiveCommitDropdown(self, plugin): - if not self: return - try: - self.commitDropdown.clear() - if plugin.active_stream is None: return - branchName = self.streamBranchDropdown.currentText() - if branchName == "": return - if branchName == "Create New Branch": - self.streamBranchDropdown.setCurrentText("main") - plugin.onBranchCreateClicked() - return - branch = None - if isinstance(plugin.active_stream[1], SpeckleException): - #logger.logToUser("Some streams cannot be accessed", Qgis.Warning) - return - elif plugin.active_stream[1]: - for b in plugin.active_stream[1].branches.items: - if b.name == branchName: - branch = b - break - try: - self.commitDropdown.addItems( - [f"{commit.id}"+ " | " + f"{commit.message}" for commit in branch.commits.items] - ) - except: pass - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - - def onStreamRemoveButtonClicked(self, plugin): - try: - #from ui.project_vars import set_project_streams - if not self: return - index = self.streamList.currentIndex() - if len(plugin.current_streams) > 0: plugin.current_streams.pop(index) - plugin.active_stream = None - self.streamBranchDropdown.clear() - self.commitDropdown.clear() - #self.streamIdField.setText("") - - #set_project_streams(plugin) - self.populateProjectStreams(plugin) - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3], plugin=self) - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog_base.ui b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog_base.ui deleted file mode 100644 index 587a48e..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/speckle_qgis_dialog_base.ui +++ /dev/null @@ -1,316 +0,0 @@ - - - SpeckleQArcGISDialog - - - - 0 - 0 - 400 - 600 - - - - - - - - - - - - - - - 20 - - - 10 - - - 30 - - - 10 - - - - - - - - - - - - Stream - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 10 - 10 - - - - - - - - - - - - - Branch - - - - - - - - - - Commit - - - - - - - - - - - - - - - - - - - true - - - Send - - - - - - - Receive - - - - - - - - - - - - - - - - QAbstractItemView::NoSelection - - - - 0 - 0 - - - - QAbstractScrollArea::AdjustToContents - - - QListView::Fixed - - - QListView::ListMode - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - - - - - true - - - Set visible layers as selection - - - - - - - - - - Message - - - - - - - Sent XXX objects from ArcGIS - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - - - - - SEND - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - - - - - - - - Lat °, Lon ° - - - - - - - - - 0.0 - - - - - - - 0.0 - - - - - - - - true - - - Set as a project center - - - - - - - - - - - - - - true - - - Refresh - - - - - - - true - - - Close - - - - - - - - - - - - - - - - - - - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.py deleted file mode 100644 index c1763dd..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -from PyQt5 import QtWidgets, uic -from PyQt5.QtCore import pyqtSignal - -# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer - -ui_class = os.path.dirname(os.path.abspath(__file__)) + "/streamlist_dialog.ui" - -class StreamListDialog(QtWidgets.QWidget): - streams_add_button: QtWidgets.QPushButton - streams_reload_button: QtWidgets.QPushButton - streams_remove_button: QtWidgets.QPushButton - - - def __init__(self, parent=None): - super(StreamListDialog, self).__init__(parent) - uic.loadUi(ui_class, self) # Load the .ui file - self.show() - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.ui b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.ui deleted file mode 100644 index dfb1702..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/streamlist_dialog.ui +++ /dev/null @@ -1,42 +0,0 @@ - - - StreamListDialog - - - - 0 - 0 - 400 - 70 - - - - Form - - - - - - PushButton - - - - - - - PushButton - - - - - - - PushButton - - - - - - - - diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py deleted file mode 100644 index 9dc2a1b..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/speckle/ui/validation.py +++ /dev/null @@ -1,165 +0,0 @@ - -from typing import Union -from specklepy.api.wrapper import StreamWrapper -from specklepy.api.models import Stream, Branch, Commit -from specklepy.transports.server import ServerTransport -from specklepy.api.client import SpeckleClient -from specklepy.logging.exceptions import SpeckleException, GraphQLException - -import inspect - -import arcpy -try: - from speckle.speckle.ui.logger import logToUser -except: - from speckle_toolbox.esri.toolboxes.speckle.speckle.ui.logger import logToUser - -def tryGetStream( - sw: StreamWrapper, dataStorage, write=False, dockwidget=None -) -> Union[Stream, None]: - try: - # print("tryGetStream") - client, stream = tryGetClient(sw, dataStorage, write, dockwidget) - return stream - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3], plugin=dockwidget) - return None - - -def tryGetClient(sw: StreamWrapper, dataStorage, write=False, dockwidget=None): - # only streams with write access - try: - client = None - savedRole = None - savedStreamId = None - for acc in dataStorage.accounts: - # only check accounts on selected server - if acc.serverInfo.url in sw.server_url: - client = SpeckleClient( - acc.serverInfo.url, acc.serverInfo.url.startswith("https") - ) - try: - client.authenticate_with_account(acc) - if client.account.token is not None: - break - except SpeckleException as ex: - if "already connected" in ex.message: - logToUser( - "Dependencies versioning error.\nClick here for details.", - url="dependencies_error", - level=2, - plugin=dockwidget, - ) - return - else: - raise ex - - # if token still not found - if client is None or client.account.token is None: - for acc in dataStorage.accounts: - client = sw.get_client() - if client is not None: - break - - if client is not None: - stream = client.stream.get( - id=sw.stream_id, branch_limit=100, commit_limit=100 - ) - if isinstance(stream, Stream): - # print(stream.role) - if write == False: - # try get stream, only read access needed - # print("only read access needed") - return client, stream - else: - # check write access - # print("write access needed") - if stream.role is None or ( - isinstance(stream.role, str) and "reviewer" in stream.role - ): - savedRole = stream.role - savedStreamId = stream.id - else: - return client, stream - - if savedRole is not None and savedStreamId is not None: - logToUser( - f"You don't have write access to the stream '{savedStreamId}'. You role is '{savedRole}'", - level=2, - func=inspect.stack()[0][3], - plugin=dockwidget, - ) - - return None, None - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3], plugin=dockwidget) - return None, None - - -def validateStream(stream: Stream, dockwidget) -> Union[Stream, None]: - try: - if isinstance(stream, SpeckleException): - return None - - if stream.branches is None: - logToUser("Stream has no branches", level=1, plugin=dockwidget) - return None - return stream - except Exception as e: - logToUser(e, level=2, plugin=dockwidget) - return - - -def validateBranch(stream: Stream, branchName: str, checkCommits: bool) -> Union[Branch, None]: - try: - branch = None - if not stream.branches or not stream.branches.items: - return None - for b in stream.branches.items: - if b.name == branchName: - branch = b - break - if branch is None: - logToUser("Failed to find a branch", level=2, func = inspect.stack()[0][3]) - return None - if checkCommits == True: - if branch.commits is None: - logToUser("Failed to find a branch", level=2, func = inspect.stack()[0][3]) - return None - if len(branch.commits.items)==0: - logToUser("Branch contains no commits", level=2, func = inspect.stack()[0][3]) - return None - return branch - - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - return None - -def validateCommit(branch: Branch, commitId: str) -> Union[Commit, None]: - try: - commit = None - try: commitId = commitId.split(" | ")[0] - except: logToUser("Commit ID is not valid", level=2, func = inspect.stack()[0][3]) - - for i in branch.commits.items: - if i.id == commitId: - commit = i - break - if commit is None: - try: - commit = branch.commits.items[0] - logToUser("Failed to find a commit. Receiving Latest", level=2, func = inspect.stack()[0][3]) - except: - logToUser("Failed to find a commit", level=2, func = inspect.stack()[0][3]) - return None - return commit - except Exception as e: - logToUser(str(e), level=2, func = inspect.stack()[0][3]) - -def validateTransport(client: SpeckleClient, streamId: str) -> Union[ServerTransport, None]: - try: - transport = ServerTransport(client=client, stream_id=streamId) - return transport - except Exception as e: - logToUser("Make sure you have sufficient permissions: " + str(e), level=2, func = inspect.stack()[0][3]) - return None diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/__init__.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/logger.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/logger.py new file mode 100644 index 0000000..cf832bc --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/logger.py @@ -0,0 +1,74 @@ +from PyQt5.QtWidgets import QMessageBox +from PyQt5 import QtCore +import arcpy + +try: + from speckle.speckle.plugin_utils.helpers import splitTextIntoLines +except: + from speckle_toolbox.esri.toolboxes.speckle.speckle.plugin_utils.helpers import ( + splitTextIntoLines, + ) + +import inspect + + +def logToUser(msg: str, func=None, level: int = 2, plugin=None, url="", blue=False): + print("Log to user") + print(msg) + + msg = str(msg) + dockwidget = plugin + + try: + if url == "" and blue is False: # only for info messages + msg = addLevelSymbol(msg, level) + if func is not None: + msg += "::" + str(func) + writeToLog(msg, level) + + if dockwidget is None: + return + + new_msg = splitTextIntoLines(msg, 70) + + dockwidget.msgLog.addButton(new_msg, level=level, url=url, blue=blue) + + except Exception as e: + print(e) + return + + +def logToUserWithAction(msg: str, level: int = 0, plugin=None, url=""): + print("Log to user with action") + return + msg = str(msg) + dockwidget = plugin + if dockwidget is None: + return + try: + new_msg = splitTextIntoLines(msg, 70) + dockwidget.msgLog.addButton(new_msg, level=level, url=url) + writeToLog(new_msg, level) + except Exception as e: + print(e) + return + + +def addLevelSymbol(msg: str, level: int): + if level == 0: + msg = "🛈 " + msg + if level == 1: + msg = "⚠️ " + msg + if level == 2: + msg = "❗ " + msg + return msg + + +def writeToLog(msg: str = "", level: int = 2): + print(msg) + if level == 0: + arcpy.AddMessage(msg) + if level == 1: + arcpy.AddWarning(msg) + if level == 2: + arcpy.AddError(msg) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/panel_logging.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/panel_logging.py new file mode 100644 index 0000000..b0219d8 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/panel_logging.py @@ -0,0 +1,128 @@ +"""Logging Utility Module for Speckle QGIS""" + +import inspect +from typing import Union +import webbrowser + +import arcpy + + +def logToUser( + msg: Union[str, Exception], + func=None, + level: int = 2, + plugin=None, + url="", + blue=False, + report=False, +): + from speckle.specklepy_qt_ui.qt_ui.utils.logger import logToUser as logToUser_UI + + msg = str(msg) + print(msg) + logToUser_UI(msg, func, level, plugin, url, blue, report) + logger.writeToLog(msg.replace("\n", ". ") + " " + url, level, func) + + +class Logging: + """Holds utility methods for logging messages to QGIS""" + + qgisInterface = None + + def __init__(self, iface) -> None: + self.qgisInterface = iface + + def log(self, message: str, level: int = 0): + """Logs a specific message to the Speckle messages panel.""" + try: + if level == 0: + arcpy.AddMessage(message) + elif level == 1: + arcpy.AddWarning(message) + # elif level == 2: 3 error will quit pluging + # arcpy.AddError(message) + except Exception as e: + try: + logToUser(e, level=2, func=inspect.stack()[0][3]) + except: + pass + + def btnClicked(url): + try: + if url == "": + return + webbrowser.open(url, new=0, autoraise=True) + except Exception as e: + pass + + def logToUserWithAction( + self, + message: str, + action_text: str, + url: str = "", + level: int = 0, + duration: int = 120, + ): + self.log(message, level) + return + if not self.qgisInterface: + return + try: + from qgis.core import Qgis + from qgis.PyQt.QtWidgets import QPushButton + + if level == 0: + level = Qgis.Info + elif level == 1: + level = Qgis.Warning + elif level == 2: + level = Qgis.Critical + + widget = self.qgisInterface.messageBar().createMessage("Speckle", message) + button = QPushButton(widget) + button.setText(action_text) + button.pressed.connect(lambda: self.btnClicked(url)) + widget.layout().addWidget(button) + self.qgisInterface.messageBar().pushWidget(widget, level, duration) + except ImportError: + pass + + def logToUserPanel( + self, + message: str, + level: int = 0, + duration: int = 20, + func=None, + plugin=None, + ): + """Logs a specific message to the user in QGIS""" + return + self.log(message, level) + + if not self.qgisInterface: + return + try: + from qgis.core import Qgis + + if level == 0: + level = Qgis.Info + if level == 1: + level = Qgis.Warning + if level == 2: + level = Qgis.Critical + + if self.qgisInterface: + self.qgisInterface.messageBar().pushMessage( + "Speckle", message, level=level, duration=duration + ) + except ImportError: + pass + + def writeToLog(self, msg: str = "", level: int = 2, func=None, plugin=None): + msg = str(msg) + if func is not None and func != "None": + msg += "::" + str(func) + self.log(msg, level) + + +logger = Logging(None) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/project_vars.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/project_vars.py new file mode 100644 index 0000000..478a56d --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/project_vars.py @@ -0,0 +1,572 @@ +from typing import Any, List, Optional, Tuple, Union +import arcpy +from arcpy._mp import ArcGISProject, Map, Layer as arcLayer +from arcpy.management import CreateTable + +import os.path + +from specklepy.api.credentials import Account, get_local_accounts +from specklepy.api.client import SpeckleClient +from specklepy.logging.exceptions import ( + GraphQLException, + SpeckleException, +) +from specklepy.api.wrapper import StreamWrapper +from specklepy.api.models import Branch, Stream, Streams +from specklepy.logging import metrics + +from osgeo import osr + +import inspect + +try: + from speckle.speckle.utils.validation import tryGetStream + from speckle.speckle.speckle_arcgis import SpeckleGIS + from speckle.speckle.converter.layers import getAllProjLayers + from speckle.speckle.utils.panel_logging import logToUser +except: + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.validation import ( + tryGetStream, + ) + from speckle_toolbox.esri.toolboxes.speckle.speckle.speckle_arcgis import SpeckleGIS + from speckle_toolbox.esri.toolboxes.speckle.speckle.converter.layers import ( + getAllProjLayers, + ) + from speckle_toolbox.esri.toolboxes.speckle.speckle.utils.panel_logging import ( + logToUser, + ) + +FIELDS = ["project_streams", "project_layer_selection", "lat_lon"] + + +def get_project_streams(plugin: SpeckleGIS, content: str = None): + try: + print("GET proj streams") + project = plugin.project + table = findOrCreateSpeckleTable(project) + logToUser(table, level=0, func=inspect.stack()[0][3]) + + rows = arcpy.da.SearchCursor(table, "project_streams") + saved_streams = [] + for x in rows: + logToUser(x, level=0, func=inspect.stack()[0][3]) + saved_streams.append(x[0]) + + temp = [] + ######### need to check whether saved streams are available (account reachable) + if len(saved_streams) > 0: + for url in saved_streams: + if url == "": + continue + try: + sw = StreamWrapper(url) + try: + stream = tryGetStream(sw, plugin.dataStorage) + except SpeckleException as e: + logToUser(e.message, level=2, func=inspect.stack()[0][3]) + stream = None + # strId = stream.id # will cause exception if invalid + temp.append((sw, stream)) + except SpeckleException as e: + logToUser(e.message, level=2, func=inspect.stack()[0][3]) + # except GraphQLException as e: + # logger.logToUser(e.message, Qgis.Warning) + plugin.current_streams = temp + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + +def set_project_streams(plugin: SpeckleGIS): + try: + print("SET proj streams") + project = plugin.project + table = findOrCreateSpeckleTable(project) + print("SET proj streams 2") + + value = [ + stream[0].stream_url for stream in plugin.current_streams + ] # ",".join() + print(value) + logToUser(value, level=0, func=inspect.stack()[0][3]) + + if table is not None: + proj_layers = [] + lan_lot = "" + with arcpy.da.UpdateCursor(table, FIELDS) as cursor: + for row in cursor: # just one row + if row[1] is not None and row[1] != "": + proj_layers.append(row[1]) + if row[2] is not None and row[2] != "": + lan_lot = row[2] + cursor.deleteRow() + del cursor + if len(proj_layers) == 0: + proj_layers.append("") + if len(value) == 0: + value.append("") + + cursor = arcpy.da.InsertCursor(table, FIELDS) + length = max(len(proj_layers), len(value)) + + for i in range(length): + if i == 0: + cursor.insertRow([value[i], proj_layers[i], lan_lot]) + else: + try: + cursor.insertRow([value[i], proj_layers[i], ""]) + except: + if len(value) <= i: + cursor.insertRow(["", proj_layers[i], ""]) + if len(proj_layers) <= i: + cursor.insertRow([value[i], "", ""]) + del cursor + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + +def get_project_layer_selection(plugin: SpeckleGIS): + try: + print("GET project layer selection from the table") + project = plugin.project + table = findOrCreateSpeckleTable(project) + if table is None: + return + + rows = arcpy.da.SearchCursor(table, "project_layer_selection") + saved_layers = [] + for x in rows: + saved_layers.append(x[0]) + + temp = [] + proj_layers = getAllProjLayers(project) + ######### need to check whether saved streams are available (account reachable) + if len(saved_layers) > 0: + for layerPath in saved_layers: + if layerPath == "": + continue + found = 0 + for layer in proj_layers: + print(layer.dataSource) + if layer.dataSource == layerPath: + temp.append((layer.name, layer)) + found += 1 + break + if found == 0: + logToUser( + f'Saved layer not found: "{layerPath}"', + level=1, + func=inspect.stack()[0][3], + ) + plugin.current_layers = temp + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + +def set_project_layer_selection(plugin: SpeckleGIS): + try: + print("SET project layer selection function") + project = plugin.project + value: List[str] = [ + layer[1].dataSource for layer in plugin.current_layers + ] # ",".join([layer[1].dataSource for layer in plugin.current_layers]) + print(value) + + table = findOrCreateSpeckleTable(project) + # print(table) + if table is not None: + lan_lot = "" + proj_streams = [] + with arcpy.da.UpdateCursor(table, FIELDS) as cursor: + for row in cursor: # just one row + if row[0] is not None and row[0] != "": + proj_streams.append(row[0]) + if row[2] is not None and row[2] != "": + lan_lot = row[2] + cursor.deleteRow() + del cursor + if len(proj_streams) == 0: + proj_streams.append("") + if len(value) == 0: + value.append("") + # print(proj_streams) + + cursor = arcpy.da.InsertCursor(table, FIELDS) + length = max(len(proj_streams), len(value)) + # print(length) + for i in range(length): + # print(i) + if i == 0: + cursor.insertRow([proj_streams[i], value[i], lan_lot]) + print(i) + else: + try: + cursor.insertRow([proj_streams[i], value[i], ""]) + except: + if len(proj_streams) <= i: + cursor.insertRow(["", value[i], ""]) + if len(value) <= i: + cursor.insertRow([proj_streams[i], "", ""]) + # print(i) + del cursor + + try: + metrics.track( + "Connector Action", + plugin.active_account, + { + "name": "Save Layer Selection", + "connector_version": str(plugin.version), + }, + ) + except Exception as e: + logToUser( + e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget + ) + + # print(table) + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + print("SET project layer selection 2") + + +def get_rotation(dataStorage): + dataStorage.crs_rotation = 0 + return + try: + # get from saved project, set to local vars + proj = dataStorage.project + points = proj.readEntry("speckle-qgis", "crs_rotation", "") + if points[1] and len(points[0]) > 0: + vals: List[str] = points[0].replace(" ", "").split(";")[0] + dataStorage.crs_rotation = float(vals) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def set_rotation(dataStorage, dockwidget=None): + return + try: + # from widget (3 strings) to local vars AND memory (1 string) + proj = dataStorage.project + r = dataStorage.crs_rotation + if dataStorage.crs_rotation is None: + r = 0 + proj.writeEntry("speckle-qgis", "crs_rotation", r) + return True + + except Exception as e: + logToUser("Lat, Lon values invalid: " + str(e), level=2) + return False + +def get_crs_offsets(dataStorage): + dataStorage.crs_offset_x, dataStorage.crs_offset_y = (0,0) + return + try: + # get from saved project, set to local vars + proj = dataStorage.project + points = proj.readEntry("speckle-qgis", "crs_offsets_rotation", "") + if points[1] and len(points[0]) > 0: + vals: List[str] = points[0].replace(" ", "").split(";")[:2] + dataStorage.crs_offset_x, dataStorage.crs_offset_y = [ + float(i) for i in vals + ] + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + return + + +def set_crs_offsets(dataStorage, dockwidget=None): + return + try: + # from widget (3 strings) to local vars AND memory (1 string) + proj = dataStorage.project + x = dataStorage.crs_offset_x + y = dataStorage.crs_offset_y + + if dataStorage.crs_offset_x is None or dataStorage.crs_offset_y is None: + x = 0 + y = 0 + pt = str(x) + ";" + str(y) + proj.writeEntry("speckle-qgis", "crs_offsets_rotation", pt) + + return True + + except Exception as e: + logToUser("Lat, Lon values invalid: " + str(e), level=2) + return False + + +def get_project_saved_layers(plugin): + plugin.dataStorage.current_layers = [] + plugin.dataStorage.saved_layers = [] + return + try: + proj = plugin.project + saved_layers = proj.readEntry("speckle-qgis", "project_layer_selection", "") + temp = [] + # print(saved_layers) + if saved_layers[1] and len(saved_layers[0]) != 0: + for id in saved_layers[0].split(","): + found = 0 + for layer in proj.mapLayers().values(): + if layer.id() == id: + temp.append((layer, layer.name(), "")) + found += 1 + break + if found == 0: + logToUser( + f'Saved layer not found: "{id}"', + level=1, + func=inspect.stack()[0][3], + ) + plugin.dataStorage.current_layers = temp.copy() + plugin.dataStorage.saved_layers = temp.copy() + # print(temp) + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + + +def set_project_layer_selection(plugin): + return + try: + proj = plugin.project + # value = ",".join([x.id() for x in self.iface.layerTreeView().selectedLayers()]) #'points_qgis2_b22ed3d0_0ff9_40d2_97f2_bd17a350d698' + value = ",".join([x[0].id() for x in plugin.dataStorage.current_layers]) + + # print(value) + proj.writeEntry("speckle-qgis", "project_layer_selection", value) + try: + metrics.track( + "Connector Action", + plugin.dataStorage.active_account, + { + "name": "Save Layer Selection", + "connector_version": str(plugin.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + return + +def get_survey_point(plugin: SpeckleGIS, content=None): + try: + print("get survey point") + project = plugin.project + table = findOrCreateSpeckleTable(project) + if table is None: + return + + rows = arcpy.da.SearchCursor(table, "lat_lon") + points = "" + for x in rows: + points = x[0] + break + + if points != "": + vals: List[str] = points.replace(" ", "").split(";")[:2] + plugin.lat, plugin.lon = [float(i) for i in vals] + + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + + +def set_survey_point(plugin: SpeckleGIS): + + try: + # from widget (2 strings) to local vars + update SR of the map + print("SET survey point") + + project = plugin.project + vals = [ + str(plugin.dockwidget.surveyPointLat.text()), + str(plugin.dockwidget.surveyPointLon.text()), + ] + + plugin.lat, plugin.lon = [float(i.replace(" ", "")) for i in vals] + + if ( + plugin.lat > 180 + or plugin.lat < -180 + or plugin.lon > 180 + or plugin.lon < -180 + ): + logToUser( + "LAT LON values must be within (-180, 180). You can right-click on the canvas location to copy coordinates in WGS 84", + level=1, + plugin=self.dockwidget, + ) + return True + pt = str(plugin.lat) + ";" + str(plugin.lon) + + table = findOrCreateSpeckleTable(project) + if table is not None: + with arcpy.da.UpdateCursor(table, ["lat_lon"]) as cursor: + for row in cursor: # just one row + cursor.updateRow([pt]) + break + del cursor + + setProjectReferenceSystem(plugin) + + try: + metrics.track( + "Connector Action", + plugin.active_account, + { + "name": "Set As Center Point", + "connector_version": str(plugin.version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget) + + return True + + except Exception as e: + plugin.dockwidget.surveyPointLat.setText(str(plugin.lat)) + plugin.dockwidget.surveyPointLon.setText(str(plugin.lon)) + logToUser( + "Lat, Lon values invalid: " + str(e), level=2, func=inspect.stack()[0][3] + ) + return False + + +def setProjectReferenceSystem(plugin: SpeckleGIS): + try: + # save to project; create SR + newCrsString = ( + "+proj=tmerc +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=" + + str(plugin.lon) + + " lat_0=" + + str(plugin.lat) + + " +x_0=0 +y_0=0 +k_0=1" + ) + newCrs = osr.SpatialReference() + newCrs.ImportFromProj4(newCrsString) + newCrs.MorphToESRI() # converts the WKT to an ESRI-compatible format + + validate = True if len(newCrs.ExportToWkt()) > 10 else False + + if validate: + newProjSR = arcpy.SpatialReference() + newProjSR.loadFromString(newCrs.ExportToWkt()) + + # source = osr.SpatialReference() + # source.ImportFromWkt(plugin.project.activeMap.spatialReference.exportToString()) + # transform = osr.CoordinateTransformation(source, newCrs) + + plugin.project.activeMap.spatialReference = newProjSR + logToUser( + "Custom project Spatial Reference successfully applied", + level=0, + func=inspect.stack()[0][3], + ) + else: + logToUser( + "Custom Spatial Reference could not be created", + level=1, + func=inspect.stack()[0][3], + ) + + return True + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return False + + +def findOrCreateSpeckleTable(project: ArcGISProject) -> Union[str, None]: + try: + path = ( + arcpy.env.workspace + ) # project.filePath.replace("aprx","gdb") #"\\".join(project.filePath.split("\\")[:-1]) + "\\speckle_layers\\" #arcpy.env.workspace + "\\" # + + if "speckle_gis" not in arcpy.ListTables(): + try: + table = CreateTable(path, "speckle_gis") + arcpy.management.AddField(table, "project_streams", "TEXT") + arcpy.management.AddField(table, "project_layer_selection", "TEXT") + arcpy.management.AddField(table, "lat_lon", "TEXT") + + cursor = arcpy.da.InsertCursor(table, FIELDS) + cursor.insertRow(["", "", ""]) + del cursor + + except Exception as e: + logToUser( + "Error creating a table: " + str(e), + level=1, + func=inspect.stack()[0][3], + ) + raise e + else: + # print("table already exists") + # make sure fileds exist + table = path + "\\speckle_gis" + findOrCreateTableField(table, FIELDS[0]) + findOrCreateTableField(table, FIELDS[1]) + findOrCreateTableField(table, FIELDS[2]) + + findOrCreateRow(table, FIELDS) + + return table + + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) + return None + + +def findOrCreateTableField(table: str, field: str): + try: + with arcpy.da.UpdateCursor(table, [field]) as cursor: + value = None + for row in cursor: + value = row # tuple(val,) + if value[0] is None: + cursor.updateRow("") + break # look at the 1st row only + del cursor + + # if value is None: # if there are no rows + # cursor = arcpy.da.InsertCursor(table, [field]) + # cursor.insertRow([""]) + # del cursor + + except: # if field doesn't exist + arcpy.management.AddField(table, field, "TEXT") + # cursor = arcpy.da.InsertCursor(table, [field] ) + # cursor.insertRow([""]) + del cursor + + +def findOrCreateRow(table: str, fields: List[str]): + try: + # check if the row exists + cursor = arcpy.da.SearchCursor(table, fields) + k = -1 + for k, row in enumerate(cursor): + # print(row) + break + del cursor + + # if no rows + if k == -1: + cursor = arcpy.da.InsertCursor(table, fields) + cursor.insertRow(["", "", ""]) + del cursor + else: + with arcpy.da.UpdateCursor(table, fields) as cursor: + for row in cursor: + if None in row: + cursor.updateRow(["", "", ""]) + break # look at the 1st row only + del cursor + + except Exception as e: + logToUser(str(e), level=2, func=inspect.stack()[0][3]) diff --git a/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/validation.py b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/validation.py new file mode 100644 index 0000000..5ba6d3f --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/speckle/utils/validation.py @@ -0,0 +1,174 @@ +import inspect +from typing import Union +from specklepy.core.api.credentials import get_default_account +from specklepy.core.api.wrapper import StreamWrapper +from specklepy.core.api.models import Stream, Branch, Commit +from specklepy.transports.server import ServerTransport +from specklepy.core.api.client import SpeckleClient +from specklepy.logging.exceptions import SpeckleException, GraphQLException + +from speckle.speckle.utils.panel_logging import logToUser + + +def tryGetClient(sw: StreamWrapper, dataStorage, write=False, dockwidget=None): + # only streams with write access + client = None + savedRole = None + savedStreamId = None + for acc in dataStorage.accounts: + # only check accounts on selected server + if acc.serverInfo.url in sw.server_url: + client = SpeckleClient( + acc.serverInfo.url, acc.serverInfo.url.startswith("https") + ) + try: + client.authenticate_with_account(acc) + if client.account.token is not None: + break + except SpeckleException as ex: + if "already connected" in ex.message: + logToUser( + "Dependencies versioning error.\nClick here for details.", + url="dependencies_error", + level=2, + plugin=dockwidget, + ) + return None, None + else: + raise ex + + # if token still not found + if client is None or client.account.token is None: + client = sw.get_client() + + if client is not None: + stream = client.stream.get(id=sw.stream_id, branch_limit=100, commit_limit=100) + if isinstance(stream, Stream): + if write is False: + # try get stream, only read access needed + return client, stream + else: + # check write access + if stream.role is None: + raise Exception( + f"You don't have write access to the stream '{stream.id}'. You role is '{stream.role}'" + ) + elif isinstance(stream.role, str) and "reviewer" in stream.role: + raise Exception( + f"You don't have write access to the stream '{savedStreamId}'. You role is '{savedRole}'" + ) + else: + return client, stream + else: + return None, None + else: + return None, None + + +def tryGetStream( + sw: StreamWrapper, dataStorage, write=False, dockwidget=None +) -> Union[Stream, None]: + try: + # print("tryGetStream") + client, stream = tryGetClient(sw, dataStorage, write, dockwidget) + return stream + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=dockwidget) + return None + + +def validateStream(stream: Stream, dockwidget) -> Union[Stream, None]: + try: + # dockwidget.dataStorage.check_for_accounts() + # stream = tryGetStream(streamWrapper, dockwidget.dataStorage) + + if isinstance(stream, SpeckleException): + return None + + if stream.branches is None: + logToUser("Stream has no branches", level=1, plugin=dockwidget) + return None + return stream + except Exception as e: + logToUser(e, level=2, plugin=dockwidget) + return + + +def validateBranch( + stream: Stream, branchName: str, checkCommits: bool, dockwidget +) -> Union[Branch, None]: + try: + branch = None + if not stream.branches or not stream.branches.items: + return None + for b in stream.branches.items: + if b.name == branchName: + branch = b + break + if branch is None: + logToUser("Failed to find a branch", level=2, plugin=dockwidget) + return None + if checkCommits == True: + if branch.commits is None: + logToUser("Failed to find a branch", level=2, plugin=dockwidget) + return None + if len(branch.commits.items) == 0: + logToUser("Branch contains no commits", level=1, plugin=dockwidget) + return None + return branch + except Exception as e: + logToUser(e, level=2, plugin=dockwidget) + return + + +def validateCommit( + branch: Branch, commitId: str, dockwidget=None +) -> Union[Commit, None]: + try: + commit = None + try: + commitId = commitId.split(" | ")[0] + except: + logToUser("Commit ID is not valid", level=2, plugin=dockwidget) + + if commitId.startswith("Latest") and len(branch.commits.items) > 0: + commit = branch.commits.items[0] + else: + for i in branch.commits.items: + if i.id == commitId: + commit = i + break + if commit is None: + try: + commit = branch.commits.items[0] + logToUser( + "Failed to find a commit. Receiving Latest", + level=1, + plugin=dockwidget, + ) + except: + logToUser("Failed to find a commit", level=2, plugin=dockwidget) + return None + return commit + except Exception as e: + logToUser(e, level=2, plugin=dockwidget) + return + + +def validateTransport( + client: SpeckleClient, streamId: str +) -> Union[ServerTransport, None]: + try: + account = client.account + if not account.token: + account = get_default_account() + transport = ServerTransport(client=client, account=account, stream_id=streamId) + # print(transport) + return transport + except Exception as e: + logToUser( + "Make sure you have sufficient permissions: " + str(e), + level=1, + func=inspect.stack()[0][3], + ) + return None diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py deleted file mode 100644 index 8896faa..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/ConnectorBindings.py +++ /dev/null @@ -1,111 +0,0 @@ - -from typing import Dict, List - -from numpy import double -from import UpdateSavedStreams -from import UpdateSelectedStream - -from specklepy_qt_ui.qt_ui.ConnectorBindings import ConnectorBindings -from specklepy_qt_ui.qt_ui.Models.StreamState import StreamState - -class QGISBindings(ConnectorBindings): - - def __init__(self): - pass - - def UpdateSavedStreams(self, streams: List[StreamState]): - UpdateSavedStreams(streams) - - def UpdateSelectedStream(self): - UpdateSelectedStream() - - def Open3DView(self, viewCoordinates: List[double], viewName: str = ""): - '''Opens a 3D view in the host application - viewCoordinates: First three values are the camera position, second three the target. - viewName: Id or Name of the view''' - return - - def GetHostAppNameVersion(self)-> str: - '''Gets the current host application name with version.''' - return - - def GetHostAppName(self) -> str: - '''Gets the current host application name.''' - return - - def GetFileName(self) -> str: - '''Gets the current opened/focused file's name. - Make sure to check regarding unsaved/temporary files.''' - return - - def GetDocumentId(self) -> str: - '''Gets the current opened/focused file's id. - Generate one in here if the host app does not provide one.''' - return - - def GetDocumentLocation(self) -> str: - '''Gets the current opened/focused file's locations. - Make sure to check regarding unsaved/temporary files.''' - return - - def ResetDocument(self): - '''Clears the document state of selections and previews''' - return - - def GetActiveViewName(self) -> str: - '''Gets the current opened/focused file's view, if applicable.''' - return - - def GetStreamsInFile(self) -> List[StreamState]: - '''Returns the serialised clients present in the current open host file.''' - return - - def WriteStreamsToFile(self, streams: List[StreamState]): - '''Writes serialised clients to the current open host file.''' - return - - def AddNewStream(self, state: StreamState): - '''Adds a new client and persists the info to the host file''' - return - - def PersistAndUpdateStreamInFile(self, state: StreamState): - '''Persists the stream info to the host file; if maintaining a local in memory copy, make sure to update it too.''' - return - - def SendStream(self, state: StreamState, progress: ProgressViewModel) -> str: - '''Pushes a client's stream''' - return - - def PreviewSend(self, state: StreamState, progress: ProgressViewModel): - '''Previews a send operation''' - - def ReceiveStream(self, state: StreamState, progress: ProgressViewModel) -> StreamState: - '''Receives stream data from the server''' - - def PreviewReceive(self, state: StreamState, progress: ProgressViewModel) -> StreamState: - '''Previews a receive operation''' - - def GetSelectedObjects(self) -> List[str]: - '''Adds the current selection to the provided client.''' - - def GetObjectsInView(self) -> List[str]: - '''Gets a list of objects in the currently active view''' - - def SelectClientObjects(self, objs: List[str], deselect: bool = False): - '''clients should be able to select/preview/hover one way or another their associated objects''' - - def GetSelectionFilters(self) -> List[ISelectionFilter]: - '''Should return a list of filters that the application supports.''' - - def GetReceiveModes(self) -> List[ReceiveMode]: - '''Should return a list of receive modes that the application supports.''' - - def GetCustomStreamMenuItems(self) -> List[MenuItem]: - '''Return a list of custom menu items for stream cards.''' - - def GetSettings(self) -> List[ISetting]: - return - - def ImportFamilyCommand(self, Mapping: Dict[str, List[MappingValue]] ) -> Dict[str, List[MappingValue]] : - '''Imports family symbols in Revit''' - return diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py deleted file mode 100644 index bb2a400..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/dockwidget_main.py +++ /dev/null @@ -1,104 +0,0 @@ -import threading -from specklepy_qt_ui.qt_ui.dockwidget_main import SpeckleQGISDialog as SpeckleQGISDialog_UI -import specklepy_qt_ui.qt_ui - -from speckle.ui_widgets.widget_transforms import MappingSendDialogQGIS - -from PyQt5 import QtWidgets, uic -import os -import inspect -from specklepy.logging.exceptions import (SpeckleException, GraphQLException) -from specklepy.logging import metrics - - -from PyQt5 import QtWidgets, uic -from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget -from PyQt5.QtCore import pyqtSignal - - -from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog -from specklepy_qt_ui.qt_ui.LogWidget import LogWidget -from specklepy_qt_ui.qt_ui.utils.logger import logToUser -from specklepy_qt_ui.qt_ui.DataStorage import DataStorage - -FORM_CLASS, _ = uic.loadUiType( - os.path.join(os.path.dirname(specklepy_qt_ui.qt_ui.__file__), os.path.join("ui", "dockwidget_main.ui") ) -) - -class SpeckleQGISDialog(SpeckleQGISDialog_UI, FORM_CLASS): - - def __init__(self, parent=None): - """Constructor.""" - super(SpeckleQGISDialog_UI, self).__init__(parent) - - self.setupUi(self) - self.runAllSetup() - - def createMappingDialog(self): - - if self.mappingSendDialog is None: - self.mappingSendDialog = MappingSendDialogQGIS(None) - self.mappingSendDialog.dataStorage = self.dataStorage - - self.mappingSendDialog.runSetup() - - def completeStreamSection(self, plugin): - try: - self.streams_remove_button.clicked.connect( lambda: self.onStreamRemoveButtonClicked(plugin) ) - self.streamList.currentIndexChanged.connect( lambda: self.onActiveStreamChanged(plugin) ) - self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.populateActiveCommitDropdown(plugin) ) - return - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def onStreamRemoveButtonClicked(self, plugin): - try: - from speckle.utils.project_vars import set_project_streams - if not self: return - index = self.streamList.currentIndex() - if len(plugin.current_streams) > 0: plugin.current_streams.pop(index) - plugin.active_stream = None - self.streamBranchDropdown.clear() - self.commitDropdown.clear() - #self.streamIdField.setText("") - - set_project_streams(plugin) - self.populateProjectStreams(plugin) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def populateProjectStreams(self, plugin): - try: - from speckle.utils.project_vars import set_project_streams - if not self: return - self.streamList.clear() - for stream in plugin.current_streams: - self.streamList.addItems( - [f"Stream not accessible - {stream[0].stream_id}" if stream[1] is None or isinstance(stream[1], SpeckleException) else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0].split('/projects')[0]}"] - ) - if len(plugin.current_streams)==0: self.streamList.addItems([""]) - self.streamList.addItems(["Create New Stream"]) - set_project_streams(plugin) - index = self.streamList.currentIndex() - if index == -1: self.streams_remove_button.setEnabled(False) - else: self.streams_remove_button.setEnabled(True) - - if len(plugin.current_streams)>0: plugin.active_stream = plugin.current_streams[0] - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return - - def cancelOperations(self): - #print("____cancelOperations______") - for t in threading.enumerate(): - #print(t.name) - if 'speckle_' in t.name: - #print(f"thread to kill: {t}") - t.kill() - t.join() - # not printed if same thread - #print("Remaining threads: ") - #print(threading.enumerate()) - \ No newline at end of file diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/main_window.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/main_window.py new file mode 100644 index 0000000..b7f3545 --- /dev/null +++ b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/main_window.py @@ -0,0 +1,136 @@ +import inspect +import os +import threading +from PyQt5 import QtWidgets, uic +from specklepy.logging.exceptions import SpeckleException + +from speckle.speckle.utils.panel_logging import logToUser +import speckle.specklepy_qt_ui.qt_ui +from speckle.specklepy_qt_ui.qt_ui.mainWindow import ( + SpeckleGISDialog as SpeckleGISDialog_UI, +) + +ui_file_path = os.path.join( + os.path.dirname(speckle.specklepy_qt_ui.qt_ui.__file__), + os.path.join("ui", "mainWindow_main.ui"), +) + + +class SpeckleGISDialog(SpeckleGISDialog_UI): + def __init__(self, parent=None): + """Constructor.""" + super(SpeckleGISDialog, self).__init__( + parent + ) # , QtCore.Qt.WindowStaysOnTopHint) + uic.loadUi(ui_file_path, self) # Load the .ui file + # self.show() + self.runAllSetup() + + def populateProjectStreams(self, plugin): + try: + from speckle.speckle.utils.project_vars import set_project_streams + + if not self: + return + self.streamList.clear() + for stream in plugin.current_streams: + self.streamList.addItems( + [ + ( + f"Stream not accessible - {stream[0].stream_id}" + if stream[1] is None + or isinstance(stream[1], SpeckleException) + else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0].split('/projects')[0]}" + ) + ] + ) + if len(plugin.current_streams) == 0: + self.streamList.addItems([""]) + self.streamList.addItems(["Create New Stream"]) + set_project_streams(plugin) + index = self.streamList.currentIndex() + if index == -1: + self.streams_remove_button.setEnabled(False) + else: + self.streams_remove_button.setEnabled(True) + + if len(plugin.current_streams) > 0: + plugin.active_stream = plugin.current_streams[0] + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + return + + def completeStreamSection(self, plugin): + try: + self.streams_remove_button.clicked.connect( + lambda: self.onStreamRemoveButtonClicked(plugin) + ) + self.streamList.currentIndexChanged.connect( + lambda: self.onActiveStreamChanged(plugin) + ) + self.streamBranchDropdown.currentIndexChanged.connect( + lambda: self.populateActiveCommitDropdown(plugin) + ) + return + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + return + + def onStreamRemoveButtonClicked(self, plugin): + try: + from speckle.speckle.utils.project_vars import set_project_streams + + if not self: + return + index = self.streamList.currentIndex() + if len(plugin.current_streams) > 0: + plugin.current_streams.pop(index) + plugin.active_stream = None + self.streamBranchDropdown.clear() + self.commitDropdown.clear() + + set_project_streams(plugin) + self.populateProjectStreams(plugin) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + return + + def populateProjectStreams(self, plugin): + try: + from speckle.speckle.utils.project_vars import set_project_streams + + if not self: + return + self.streamList.clear() + for stream in plugin.current_streams: + self.streamList.addItems( + [ + ( + f"Stream not accessible - {stream[0].stream_id}" + if stream[1] is None + or isinstance(stream[1], SpeckleException) + else f"{stream[1].name}, {stream[1].id} | {stream[0].stream_url.split('/streams')[0].split('/projects')[0]}" + ) + ] + ) + if len(plugin.current_streams) == 0: + self.streamList.addItems([""]) + self.streamList.addItems(["Create New Stream"]) + set_project_streams(plugin) + index = self.streamList.currentIndex() + if index == -1: + self.streams_remove_button.setEnabled(False) + else: + self.streams_remove_button.setEnabled(True) + + if len(plugin.current_streams) > 0: + plugin.active_stream = plugin.current_streams[0] + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + return + + def cancelOperations(self): + for t in threading.enumerate(): + if "speckle_" in t.name: + t.kill() + t.join() diff --git a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py b/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py deleted file mode 100644 index d25c891..0000000 --- a/speckle_toolbox/esri/toolboxes/speckle/ui_widgets/widget_transforms.py +++ /dev/null @@ -1,404 +0,0 @@ -import inspect -import os -from typing import Any, List, Tuple, Union -from speckle.converter.layers import getAllLayers -from speckle.converter.layers.utils import getElevationLayer, getLayerGeomType -from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog -from specklepy_qt_ui.qt_ui.utils.logger import displayUserMsg -from specklepy_qt_ui.qt_ui.DataStorage import DataStorage - -from speckle.utils.panel_logging import logToUser - -from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsIconUtils - -from PyQt5 import QtWidgets, uic, QtCore -from PyQt5.QtWidgets import QListWidgetItem - -from specklepy.logging import metrics -from osgeo import gdal -import webbrowser -import specklepy_qt_ui.qt_ui - -FORM_CLASS, _ = uic.loadUiType( - os.path.join( - os.path.join( - os.path.dirname(specklepy_qt_ui.qt_ui.__file__), "ui", "transforms.ui" - ) - ) -) - - -class MappingSendDialogQGIS(MappingSendDialog, FORM_CLASS): - def __init__(self, parent=None): - super(MappingSendDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint) - self.setupUi(self) - self.runAllSetup() - - def runSetup(self): - self.attr_label.setEnabled(False) - self.attrDropdown.setEnabled(False) - self.dialog_button.setText("Apply") - - self.populateTransforms() - self.populateLayersByTransform() - self.populateSavedTransforms(self.dataStorage) - self.populateSavedElevationLayer(self.dataStorage) - - # self.elevationLayerDropdown.currentIndexChanged.connect(self.saveElevationLayer) - - def populateSavedTransforms( - self, dataStorage - ): # , savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None): - if dataStorage is not None: - self.dataStorage = dataStorage # making sure lists are synced - self.transformationsList.clear() - vals = self.dataStorage.savedTransforms - all_l_names = [l.name() for l in self.dataStorage.all_layers] - - for item in vals: - layer_name = item.split(" -> ")[0].split(" ('")[0] - transform_name = item.split(" -> ")[1] - - layer = None - for l in self.dataStorage.all_layers: - if layer_name == l.name(): - layer = l - if layer is None: - logToUser( - f"Layer '{layer_name}' not found in the project.\nTransformation is removed.", - level=2, - ) - self.dataStorage.savedTransforms.remove(item) - else: - if transform_name not in self.dataStorage.transformsCatalog: - displayUserMsg( - f"Saved transformation '{transform_name}' is not valid.\nTransformation is removed.", - level=1, - ) - self.dataStorage.savedTransforms.remove(item) - elif all_l_names.count(layer.name()) > 1: - displayUserMsg( - f"Layer name '{layer.name()}' is used for more than 1 layer in the project.\nTransformation is removed.", - level=1, - ) - self.dataStorage.savedTransforms.remove(item) - else: - listItem = QListWidgetItem(item) - icon = QgsIconUtils().iconForLayer(layer) - listItem.setIcon(icon) - - self.transformationsList.addItem(listItem) - - def onAddTransform(self): - from speckle.utils.project_vars import set_transformations - - root = self.dataStorage.project.layerTreeRoot() - self.dataStorage.all_layers = getAllLayers(root) - - if ( - len(self.layerDropdown.currentText()) > 1 - and len(self.transformDropdown.currentText()) > 1 - ): - listItem = ( - str(self.layerDropdown.currentText()) - + " -> " - + str(self.transformDropdown.currentText()) - ) - layer_name = listItem.split(" -> ")[0].split(" ('")[0] - transform_name = listItem.split(" -> ")[1].lower() - - exists = 0 - for record in self.dataStorage.savedTransforms: - current_layer_name = record.split(" -> ")[0].split(" ('")[0] - current_transf_name = record.split(" -> ")[1].lower() - if layer_name == current_layer_name: # in layers - exists += 1 - displayUserMsg( - "Selected layer already has a transformation applied", level=1 - ) - break - - if exists == 0: - layer = None - for l in self.dataStorage.all_layers: - if layer_name == l.name(): - layer = l - if layer is not None: - if ( - "attribute" in transform_name - and self.attrDropdown.currentText() != "" - ): - listItem = ( - str(self.layerDropdown.currentText()) - + " ('" - + str(self.attrDropdown.currentText()) - + "') -> " - + str(self.transformDropdown.currentText()) - ) - - self.dataStorage.savedTransforms.append(listItem) - self.populateSavedTransforms(self.dataStorage) - - try: - metrics.track( - "Connector Action", - self.dataStorage.active_account, - { - "name": "Transformation on Send Add", - "Transformation": listItem.split(" -> ")[1], - "connector_version": str( - self.dataStorage.plugin_version - ), - }, - ) - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - - set_transformations(self.dataStorage) - - def onRemoveTransform(self): - from speckle.utils.project_vars import set_transformations - - if self.transformationsList.currentItem() is not None: - listItem = self.transformationsList.currentItem().text() - # print(listItem) - - if listItem in self.dataStorage.savedTransforms: - self.dataStorage.savedTransforms.remove(listItem) - - try: - metrics.track( - "Connector Action", - self.dataStorage.active_account, - { - "name": "Transformation on Send Remove", - "Transformation": listItem.split(" -> ")[1], - "connector_version": str(self.dataStorage.plugin_version), - }, - ) - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - - self.populateSavedTransforms(self.dataStorage) - set_transformations(self.dataStorage) - - def onOkClicked(self): - try: - self.saveElevationLayer() - self.close() - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def populateLayers(self): - try: - self.layerDropdown.clear() - root = self.dataStorage.project.layerTreeRoot() - self.dataStorage.all_layers = getAllLayers(root) - for i, layer in enumerate(self.dataStorage.all_layers): - listItem = layer.name() - self.layerDropdown.addItem(listItem) - icon = QgsIconUtils().iconForLayer(layer) - self.layerDropdown.setItemIcon(i, icon) - - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def populateLayersByTransform(self): - try: - self.layerDropdown.clear() - root = self.dataStorage.project.layerTreeRoot() - self.dataStorage.all_layers = getAllLayers(root) - - transform = str(self.transformDropdown.currentText()) - layers_dropdown = [] - - for i, layer in enumerate(self.dataStorage.all_layers): - listItem = None - if "extrude" in transform.lower(): - if isinstance(layer, QgsVectorLayer): - geom_type = getLayerGeomType(layer) - if "polygon" in geom_type.lower(): - listItem = layer.name() - - elif "elevation" in transform.lower(): - if isinstance(layer, QgsRasterLayer): - # avoiding tiling layers - ds = gdal.Open(layer.source(), gdal.GA_ReadOnly) - if ds is None: - continue - - # for satellites - if "texture" in transform.lower(): - listItem = layer.name() - # for elevation to mesh - elif "mesh" in transform.lower(): - try: - if layer.bandCount() == 1: - listItem = layer.name() - except: - pass - - if listItem is not None: - layers_dropdown.append(listItem) - self.layerDropdown.addItem(listItem) - icon = QgsIconUtils().iconForLayer(layer) - self.layerDropdown.setItemIcon(len(layers_dropdown) - 1, icon) - - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def populateAttributesByLayer(self): - try: - self.attrDropdown.clear() - root = self.dataStorage.project.layerTreeRoot() - self.dataStorage.all_layers = getAllLayers(root) - - layer_name = str(self.layerDropdown.currentText()) - transform_name = self.transformDropdown.currentText() - layerForAttributes = None - for i, layer in enumerate(self.dataStorage.all_layers): - if layer_name == layer.name(): - if isinstance(layer, QgsVectorLayer): - geom_type = getLayerGeomType(layer) - if "polygon" in geom_type.lower(): - layerForAttributes = layer - break - - if layerForAttributes is not None and "attribute" in transform_name: - self.attr_label.setEnabled(True) - self.attrDropdown.setEnabled(True) - - if "ignore" not in transform_name: - self.attrDropdown.addItem("Random height") - - for field in layerForAttributes.fields(): - field_type = field.type() - if field_type in [2, 6, 10]: - self.attrDropdown.addItem(str(field.name())) - else: - self.attr_label.setEnabled(False) - self.attrDropdown.setEnabled(False) - - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def populateTransforms(self): - try: - self.transformDropdown.clear() - for item in self.dataStorage.transformsCatalog: - self.transformDropdown.addItem(item) - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def populateSavedElevationLayer( - self, dataStorage - ): # , savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None): - try: - if dataStorage is not None: - self.dataStorage = dataStorage # making sure lists are synced - elevationLayer = getElevationLayer(self.dataStorage) - - self.elevationLayerDropdown.clear() - root = self.dataStorage.project.layerTreeRoot() - self.dataStorage.all_layers = getAllLayers(root) - - self.elevationLayerDropdown.addItem("") - - setAsindex = 0 - countRaster = 1 - for i, layer in enumerate(self.dataStorage.all_layers): - if isinstance(layer, QgsRasterLayer): - # avoiding tiling layers - ds = gdal.Open(layer.source(), gdal.GA_ReadOnly) - if ds is None: - continue - elif layer.bandCount() != 1: - continue - - listItem = layer.name() - self.elevationLayerDropdown.addItem(listItem) - icon = QgsIconUtils().iconForLayer(layer) - self.elevationLayerDropdown.setItemIcon(countRaster, icon) - - if elevationLayer is not None: - if listItem == elevationLayer.name(): - setAsindex = countRaster - countRaster += 1 - self.elevationLayerDropdown.setCurrentIndex(setAsindex) - - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - return - - def saveElevationLayer(self): - # print("saveElevationLayer") - from speckle.utils.project_vars import set_elevationLayer - - root = self.dataStorage.project.layerTreeRoot() - layer = None - - if self.dataStorage is None: - return - - layerName = str(self.elevationLayerDropdown.currentText()) - try: - if self.dataStorage.elevationLayer.name() == layerName: - return - except: - pass - - if len(layerName) < 1: - layer = None - else: - self.dataStorage.all_layers = getAllLayers(root) - all_l_names = [l.name() for l in self.dataStorage.all_layers] - # print(all_l_names) - - for l in self.dataStorage.all_layers: - if layerName == l.name(): - layer = l - try: - # print(layerName) - if all_l_names.count(layer.name()) > 1: - displayUserMsg( - f"Layer name '{layer.name()}' is used for more than 1 layer in the project", - level=1, - ) - layer = None - break - else: - self.dataStorage.elevationLayer = layer - set_elevationLayer(self.dataStorage) - logToUser( - f"Elevation layer '{layerName}' successfully set", - level=0, - ) - break - except: - displayUserMsg( - f"Layer '{layer.name()}' is not found in the project", - level=1, - ) - layer = None - break - - try: - metrics.track( - "Connector Action", - self.dataStorage.active_account, - { - "name": "Add transformation on Send", - "Transformation": "Set Layer as Elevation", - "connector_version": str(self.dataStorage.plugin_version), - }, - ) - except Exception as e: - logToUser(e, level=2, func=inspect.stack()[0][3]) - - def onMoreInfo(self): - webbrowser.open("https://speckle.guide/user/qgis.html#transformations")