support ICurves and new GisFeatures, and (possible) hatches

This commit is contained in:
KatKatKateryna
2024-08-22 16:05:54 +01:00
parent a144de5874
commit d6a06734be
3 changed files with 368 additions and 120 deletions
+2 -2
View File
@@ -370,7 +370,7 @@ class SpeckleProvider(BaseProvider):
def traverse_data(self, commit_obj) -> Dict:
"""Traverse Speckle commit and return geojson with features."""
from specklepy.objects.geometry import Point, Line, Polyline, Curve, Mesh, Brep
from specklepy.objects.geometry import Point, Line, Curve, Arc, Circle, Ellipse, Polyline, Polycurve, Mesh, Brep
from specklepy.objects.GIS.layers import VectorLayer
from specklepy.objects.GIS.geometry import GisPolygonElement
from specklepy.objects.GIS.GisFeature import GisFeature
@@ -382,7 +382,7 @@ class SpeckleProvider(BaseProvider):
from pygeoapi.provider.speckle_utils.feature_utils import create_features
from pygeoapi.provider.speckle_utils.display_utils import set_default_color
supported_classes = [GisFeature, GisPolygonElement, Mesh, Brep, Point, Line, Polyline, Curve]
supported_classes = [GisFeature, GisPolygonElement, Mesh, Brep, Point, Line, Polyline, Curve, Arc, Circle, Ellipse, Polycurve]
supported_types = [y().speckle_type for y in supported_classes]
supported_types.extend([
"Objects.Other.Revit.RevitInstance",
+346 -110
View File
@@ -1,13 +1,248 @@
from typing import Dict, List
import math
from typing import Dict, List, Tuple
def assign_geometry(feature: Dict, f_base) -> ( List[List[List[float]]], List[List[None| List[int]]] ):
def convert_point(f_base: "Point", coords, coord_counts):
"""Convert Point."""
coords.append([f_base.x, f_base.y, f_base.z])
coord_counts.append([1])
def convert_line(f_base: "Line", coords, coord_counts):
"""Convert Line."""
start = [f_base.start.x, f_base.start.y, f_base.start.z]
end = [f_base.end.x, f_base.end.y, f_base.end.z]
coords.extend([start, end])
coord_counts.append([2])
def convert_polyline(f_base: "Polyline", coords, coord_counts):
"""Convert Polyline."""
coord_counts.append([])
local_poly_count = 0
for pt in f_base.as_points():
coords.append([pt.x, pt.y, pt.z])
local_poly_count += 1
# closing point
if local_poly_count>2 and f_base.closed is True and coords[0] != coords[-1]:
coords.append(coords[0])
local_poly_count += 1
coord_counts[-1].append(local_poly_count)
def convert_arc(f_base: "Arc", coords, coord_counts):
"""Convert Arc."""
if f_base.plane is None or f_base.plane.normal.z == 0:
normal = 1
else:
normal = f_base.plane.normal.z
# calculate angles and interval
interval, angle1, angle2 = getArcRadianAngle(f_base)
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)
# set a (random) point density: 24 per 1 rad
pointsNum = math.floor(abs(interval)) * 24
if pointsNum < 4:
pointsNum = 4
# assign coordinates
coord_counts.append([])
local_poly_count = 0
for i in range(0, pointsNum + 1):
k = i / pointsNum # reset values to fraction
angle = angle1 + k * interval * normal
x=f_base.plane.origin.x + f_base.radius * math.cos(angle)
y=f_base.plane.origin.y + f_base.radius * math.sin(angle)
z=f_base.plane.origin.z
coords.append([x, y, z])
local_poly_count += 1
coord_counts[-1].append(local_poly_count)
def convert_circle(f_base: "Circle", coords, coord_counts):
"""Convert Circle."""
if f_base.plane is None or f_base.plane.normal.z == 0:
normal = 1
else:
normal = f_base.plane.normal.z
# set a (random) point density: 24 per 1 rad
interval = 2 * math.pi
pointsNum = math.floor(abs(interval)) * 24
if pointsNum < 4:
pointsNum = 4
# assign coordinates
coord_counts.append([])
local_poly_count = 0
for i in range(0, pointsNum + 1):
k = i / pointsNum # reset values to fraction
angle = k * interval * normal
x=f_base.plane.origin.x + f_base.radius * math.cos(angle)
y=f_base.plane.origin.y + f_base.radius * math.sin(angle)
z=f_base.plane.origin.z
coords.append([x, y, z])
local_poly_count += 1
coord_counts[-1].append(local_poly_count)
def convert_polycurve(f_base: "Polycurve", coords, coord_counts):
"""Convert Polycurve."""
flat_coords = []
flat_coord_count = [0]
# put together results from all segment conversions
for segm in f_base.segments:
convert_icurve(segm, coords, coord_counts)
if len(coord_counts)==0:
continue
flat_coords.extend(coords)
flat_coord_count[-1] += coord_counts[-1][-1]
coords = flat_coords
coord_counts = flat_coord_count
def convert_curve(f_base: "Curve", coords, coord_counts):
"""Convert Curve using its Polyline displayValue."""
return convert_polyline(f_base.displayValue, coords, coord_counts)
def convert_icurve(f_base: "Base", coords, coord_counts):
"""Convert any ICurve."""
from specklepy.objects.geometry import Line, Polyline, Arc, Curve, Circle, Polycurve, Mesh, Brep
if isinstance(f_base, Line):
convert_line(f_base, coords, coord_counts)
elif isinstance(f_base, Polyline):
convert_polyline(f_base, coords, coord_counts)
elif isinstance(f_base, Curve):
convert_curve(f_base, coords, coord_counts)
elif isinstance(f_base, Arc):
convert_arc(f_base, coords, coord_counts)
elif isinstance(f_base, Circle):
convert_circle(f_base, coords, coord_counts)
elif isinstance(f_base, Polycurve):
convert_polycurve(f_base, coords, coord_counts)
def convert_mesh_or_brep(f_base: "Base", coords, coord_counts):
"""Convert Mesh object or Mesh derived from Brep display value."""
from specklepy.objects.geometry import Mesh, Brep
faces = []
vertices = []
# get faces and vertices
if isinstance(f_base, Mesh):
faces = f_base.faces
vertices = f_base.vertices
elif isinstance(f_base, Brep):
if f_base.displayValue is None or (
isinstance(f_base.displayValue, list)
and len(f_base.displayValue) == 0
):
geometry = {}
return
elif isinstance(f_base.displayValue, list):
faces = f_base.displayValue[0].faces
vertices = f_base.displayValue[0].vertices
else:
faces = f_base.displayValue.faces
vertices = f_base.displayValue.vertices
# add coordinates
count: int = 0
for i, pt_count in enumerate(faces):
if i != count:
continue
# old encoding
if pt_count == 0:
pt_count = 3
elif pt_count == 1:
pt_count = 4
coord_counts.append([pt_count])
for vertex_index in faces[count + 1 : count + 1 + pt_count]:
x = vertices[vertex_index * 3]
y = vertices[vertex_index * 3 + 1]
z = vertices[vertex_index * 3 + 2]
coords.append([x, y, z])
count += pt_count + 1
def convert_polygon(polygon: "Base", coords, coord_counts):
"""Convert GisPolygonGeometry."""
coord_counts.append([])
boundary_count = 0
for pt in polygon.boundary.as_points():
coords.append([pt.x, pt.y, pt.z])
boundary_count += 1
coord_counts[-1].append(boundary_count)
for void in polygon.voids:
void_count = 0
for pt_void in void.as_points():
coords.append([pt_void.x, pt_void.y, pt_void.z])
void_count += 1
coord_counts[-1].append(void_count)
def convert_hatch(hatch: "Base", coords, coord_counts):
"""Convert Hatch."""
coord_counts.append([])
boundary_count = 0
loops: list = hatch["loops"]
boundary = None
voids = []
for loop in loops:
print(loop)
print(loop.get_member_names())
if len(loops)==1 or loop["type"] == "Outer":
boundary = loop["curve"]
else:
voids.append(loop["curve"])
if boundary is None:
return
# record coordinates
convert_icurve(boundary, coords, coord_counts)
for void in voids:
convert_icurve(void, coords, coord_counts)
def assign_geometry(feature: Dict, f_base) -> Tuple[ List[List[List[float]]], List[List[None| List[int]]] ]:
"""Assign geom type and convert object coords into flat lists of coordinates and schema."""
from specklepy.objects.geometry import Point, Line, Polyline, Curve, Mesh, Brep
from specklepy.objects.geometry import Point, Line, Polyline, Arc, Curve, Circle, Polycurve, Mesh, Brep
from specklepy.objects.GIS.geometry import GisPolygonGeometry
from specklepy.objects.GIS.GisFeature import GisFeature
geometry = feature["geometry"]
coords = []
@@ -15,131 +250,132 @@ def assign_geometry(feature: Dict, f_base) -> ( List[List[List[float]]], List[Li
if isinstance(f_base, Point):
geometry["type"] = "MultiPoint"
coord_counts.append(None)
coord_counts.append(None) # as an indicator of a Multi..type
convert_point(f_base, coords, coord_counts)
coords.append([f_base.x, f_base.y, f_base.z])
coord_counts.append([1])
elif (isinstance(f_base, Line) or
isinstance(f_base, Polyline) or
isinstance(f_base, Curve) or
isinstance(f_base, Arc) or
isinstance(f_base, Circle) or
isinstance(f_base, Polycurve)):
geometry["type"] = "LineString"
convert_icurve(f_base, coords, coord_counts)
elif f_base.speckle_type.endswith(".Hatch"):
geometry["type"] = "MultiPolygon"
coord_counts.append(None)
convert_hatch(f_base, coords, coord_counts)
elif isinstance(f_base, Mesh) or isinstance(f_base, Brep):
geometry["type"] = "MultiPolygon"
coord_counts.append(None) # as an indicator of a MultiPolygon
geometry["type"] = "MultiPolygon"
coord_counts.append(None) # as an indicator of a Multi..type
convert_mesh_or_brep(f_base, coords, coord_counts)
faces = []
vertices = []
if isinstance(f_base, Mesh):
faces = f_base.faces
vertices = f_base.vertices
elif isinstance(f_base, Brep):
if f_base.displayValue is None or (
isinstance(f_base.displayValue, list)
and len(f_base.displayValue) == 0
):
geometry = {}
return
elif isinstance(f_base.displayValue, list):
faces = f_base.displayValue[0].faces
vertices = f_base.displayValue[0].vertices
else:
faces = f_base.displayValue.faces
vertices = f_base.displayValue.vertices
count: int = 0
for i, pt_count in enumerate(faces):
if i != count:
continue
# old encoding
if pt_count == 0:
pt_count = 3
elif pt_count == 1:
pt_count = 4
coord_counts.append([pt_count])
for vertex_index in faces[count + 1 : count + 1 + pt_count]:
x = vertices[vertex_index * 3]
y = vertices[vertex_index * 3 + 1]
z = vertices[vertex_index * 3 + 2]
coords.append([x, y, z])
count += pt_count + 1
elif f_base.speckle_type.endswith(".GisFeature") and len(f_base["geometry"]) > 0: # isinstance(f_base, GisFeature) and len(f_base.geometry) > 0:
elif f_base.speckle_type.endswith("Feature") and len(f_base["geometry"]) > 0: # isinstance(f_base, GisFeature) and len(f_base.geometry) > 0:
# GisFeature doesn't deserialize properly, need to check for speckle_type
if isinstance(f_base.geometry[0], Point):
if isinstance(f_base["geometry"][0], Point):
geometry["type"] = "MultiPoint"
coord_counts.append(None)
coord_counts.append(None) # as an indicator of a Multi..type
for geom in f_base.geometry:
coords.append([geom.x, geom.y, geom.z])
coord_counts.append([1])
for geom in f_base["geometry"]:
convert_point(geom, coords, coord_counts)
elif isinstance(f_base.geometry[0], Polyline):
elif isinstance(f_base["geometry"][0], Polyline):
geometry["type"] = "MultiLineString"
coord_counts.append(None)
for geom in f_base.geometry:
coord_counts.append([])
local_poly_count = 0
for geom in f_base["geometry"]:
convert_polyline(geom, coords, coord_counts)
for pt in geom.as_points():
coords.append([pt.x, pt.y, pt.z])
local_poly_count += 1
if len(coords)>2 and geom.closed is True and coords[0] != coords[-1]:
coords.append(coords[0])
local_poly_count += 1
coord_counts[-1].append(local_poly_count)
elif isinstance(f_base.geometry[0], GisPolygonGeometry):
elif isinstance(f_base["geometry"][0], GisPolygonGeometry):
geometry["type"] = "MultiPolygon"
coord_counts.append(None)
for polygon in f_base.geometry:
coord_counts.append([])
boundary_count = 0
for pt in polygon.boundary.as_points():
coords.append([pt.x, pt.y, pt.z])
boundary_count += 1
coord_counts[-1].append(boundary_count)
for void in polygon.voids:
void_count = 0
for pt_void in void.as_points():
coords.append([pt_void.x, pt_void.y, pt_void.z])
void_count += 1
coord_counts[-1].append(void_count)
elif isinstance(f_base, Line):
geometry["type"] = "LineString"
start = [f_base.start.x, f_base.start.y, f_base.start.z]
end = [f_base.end.x, f_base.end.y, f_base.end.z]
coords.extend([start, end])
coord_counts.append([2])
elif isinstance(f_base, Polyline):
geometry["type"] = "LineString"
for pt in f_base.as_points():
coords.append([pt.x, pt.y, pt.z])
if len(coords)>2 and f_base.closed is True and coords[0] != coords[-1]:
coords.append(coords[0])
coord_counts.append([len(coords)])
elif isinstance(f_base, Curve):
geometry["type"] = "LineString"
for pt in f_base.displayValue.as_points():
coords.append([pt.x, pt.y, pt.z])
if len(coords)>2 and f_base.displayValue.closed is True and coords[0] != coords[-1]:
coords.append(coords[0])
coord_counts.append([len(coords)])
for geom in f_base["geometry"]:
convert_polygon(geom, coords, coord_counts)
else:
geometry = {}
# print(f"Unsupported geometry type: {f_base.speckle_type}")
return coords, coord_counts
def getArcRadianAngle(arc: "Arc") -> List[float]:
"""Calculate start & end angle, and interval of an Arc."""
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
def getArcAngles(poly: "Arc") -> Tuple[float | None]:
if poly.startPoint.x == poly.plane.origin.x:
angle1 = math.pi / 2
else:
angle1 = math.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 = math.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
@@ -6,7 +6,7 @@ DEFAULT_COLOR = (255 << 24) + (150 << 16) + (150 << 8) + 150
def find_display_obj(obj) -> Tuple["Base", "Base"]:
"""Get displayable object."""
from specklepy.objects.geometry import Base, Mesh
from specklepy.objects.geometry import Point, Line, Arc, Circle, Curve, Polycurve, Mesh, Brep
displayVal = obj
displayValForColor = obj
@@ -52,8 +52,20 @@ def find_display_obj(obj) -> Tuple["Base", "Base"]:
displayVal = displayValForColor
# if not searching for colored object, return GisFeatures as is
if obj.speckle_type.endswith(".GisFeature"):
# keep reading color from GisFeature Meshes
if not obj.speckle_type.endswith("Feature"):
displayValForColor = obj
# return known types as is
if (obj.speckle_type.endswith("Feature") or
isinstance(obj, Point) or
isinstance(obj, Line) or
isinstance(obj, Arc) or
isinstance(obj, Circle) or
isinstance(obj, Curve) or
isinstance(obj, Polycurve) or
isinstance(obj, Mesh) or
isinstance(obj, Brep)):
displayVal = obj
return displayVal, displayValForColor
@@ -107,14 +119,14 @@ def assign_color(obj_display, props) -> None:
color = DEFAULT_COLOR
try:
if hasattr(obj_display, 'renderMaterial'):
color = obj_display['renderMaterial']['diffuse']
elif hasattr(obj_display, '@renderMaterial'):
color = obj_display['@renderMaterial']['diffuse']
elif hasattr(obj_display, 'displayStyle'):
if hasattr(obj_display, 'displayStyle'):
color = obj_display['displayStyle']['color']
elif hasattr(obj_display, '@displayStyle'):
color = obj_display['@displayStyle']['color']
elif hasattr(obj_display, 'renderMaterial'):
color = obj_display['renderMaterial']['diffuse']
elif hasattr(obj_display, '@renderMaterial'):
color = obj_display['@renderMaterial']['diffuse']
elif isinstance(obj_display, Mesh) and isinstance(obj_display.colors, List):
sameColors = True
color1 = obj_display.colors[0]