31 Commits

Author SHA1 Message Date
dependabot[bot] e1288427bb Bump actions/checkout from 4.1.6 to 4.2.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.2.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.6...v4.2.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-24 11:33:01 +00:00
dependabot[bot] ac62d6373a Bump actions/checkout from 4.1.1 to 4.1.6 (#12)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-03 19:39:13 +01:00
dependabot[bot] b88c956583 Bump specklesystems/speckle-automate-github-composite-action (#7)
Bumps [specklesystems/speckle-automate-github-composite-action](https://github.com/specklesystems/speckle-automate-github-composite-action) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/specklesystems/speckle-automate-github-composite-action/releases)
- [Commits](https://github.com/specklesystems/speckle-automate-github-composite-action/compare/0.8.0...0.8.1)

---
updated-dependencies:
- dependency-name: specklesystems/speckle-automate-github-composite-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-09 12:54:14 +00:00
Jonathon Broughton e2bfdefc6d additional MEP elements
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2024-02-09 12:32:25 +00:00
Jonathon Broughton ce0299bccf De-optimise from bounding box clashes 2024-02-09 12:31:50 +00:00
Jonathon Broughton 0d2c827297 specklepy updated
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2024-02-09 09:21:46 +00:00
dependabot[bot] 98c4a119c7 Bump actions/checkout from 3.4.0 to 4.1.1 (#4)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.4.0 to 4.1.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.4.0...v4.1.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathon Broughton <jonathon@stardotbmp.com>
2024-02-09 08:47:53 +00:00
dependabot[bot] 61cb8d54f5 Bump actions/setup-python from 4 to 5 (#5)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathon Broughton <jonathon@stardotbmp.com>
2024-02-09 08:47:07 +00:00
dependabot[bot] 24400bfb9b Bump specklesystems/speckle-automate-github-composite-action (#6)
Bumps [specklesystems/speckle-automate-github-composite-action](https://github.com/specklesystems/speckle-automate-github-composite-action) from 0.7.4 to 0.8.0.
- [Release notes](https://github.com/specklesystems/speckle-automate-github-composite-action/releases)
- [Commits](https://github.com/specklesystems/speckle-automate-github-composite-action/compare/0.7.4...0.8.0)

---
updated-dependencies:
- dependency-name: specklesystems/speckle-automate-github-composite-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-09 08:46:08 +00:00
Jonathon Broughton 5978a90c72 Update main.yml 2024-02-08 20:58:49 +00:00
Jonathon Broughton d23f639ed2 Update main.yml
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-15 04:30:27 +00:00
Jonathon Broughton 09adbf24d3 Create LICENSE 2023-11-14 02:51:23 +00:00
Jonathon Broughton 7432f0a37d Update README.md
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 22:41:33 +00:00
Jonathon Broughton b221d2168e demo enhanced
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 21:59:38 +00:00
Jonathon Broughton 5ad615eb5b Update main.yml
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 17:08:20 +00:00
Jonathon Broughton 59bcacfd6d Update main.yml
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 13:11:08 +00:00
Jonathon Broughton d56282a140 demo ready
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 12:20:52 +00:00
Jonathon Broughton 2396810930 fixes 2023-11-13 05:28:12 +00:00
Jonathon Broughton 564b4a8012 fixes
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 04:39:51 +00:00
Jonathon Broughton a06c9023fc better mock Mesh
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 03:37:59 +00:00
Jonathon Broughton 4bcfe2cb2d mypymesh mock
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 03:32:35 +00:00
Jonathon Broughton 4f32286a9b trying a poetry install pymesh pre install
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 03:16:49 +00:00
Jonathon Broughton f765ebd6bb trying a poetry install from git method
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 03:08:49 +00:00
Jonathon Broughton 85a73cb8eb try import pymesh to stop schema check failing
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 02:43:30 +00:00
Jonathon Broughton 64c7fa7d48 gitignore
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-13 02:18:19 +00:00
Jonathon Broughton 380a2ee844 Real Clash Detection 2023-11-13 02:16:30 +00:00
Iain Sproat 7ba4467217 Docker Caching Enabled
* chore(github action): update composite action version to configure buildx

* chore(github action): use tagged release for speckle automate action
2023-11-12 19:32:15 +00:00
Iain Sproat 463b53f8c8 chore(github action): update composite action version to configure buildx (#2)
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-12 18:26:40 +00:00
Jonathon Broughton 1f908aa8d2 Merge pull request #1 from specklesystems/iain/test-github-action-with-caching
chore(github action): test action with cache
2023-11-12 18:16:07 +00:00
Iain Sproat d40821dfa6 chore(github action): test action with cache 2023-11-12 18:13:03 +00:00
Jonathon Broughton 73a44c6d5c Dependency mess
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
2023-11-12 17:43:46 +00:00
21 changed files with 1433 additions and 777 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
"dockerFile": "../Dockerfile",
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "poetry install --no-root",
"postCreateCommand": "poetry install --no-root",
// Configure tool-specific properties.
"customizations": {
-2
View File
@@ -4,8 +4,6 @@ dist/
*.egg-info/
.cache/
*.log
.ruff_cache/
.venv/
.env/
.git/
Dockerfile copy*
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
+5 -5
View File
@@ -11,8 +11,8 @@ jobs:
FUNCTION_SCHEMA_FILE_NAME: functionSchema.json
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.4.0
- uses: actions/setup-python@v4
- uses: actions/checkout@v4.2.2
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install and configure Poetry
@@ -29,12 +29,12 @@ jobs:
run: |
python main.py generate_schema ${HOME}/${{ env.FUNCTION_SCHEMA_FILE_NAME }}
- name: Speckle Automate Function - Build and Publish
uses: specklesystems/speckle-automate-github-composite-action@0.7.2
uses: specklesystems/speckle-automate-github-composite-action@0.8.1
with:
speckle_automate_url: ${{ env.SPECKLE_AUTOMATE_URL || 'https://automate.speckle.dev' }}
speckle_token: ${{ secrets.SPECKLE_FUNCTION_TOKEN }}
speckle_function_id: ${{ secrets.SPECKLE_FUNCTION_ID }}
speckle_function_input_schema_file_path: ${{ env.FUNCTION_SCHEMA_FILE_NAME }}
speckle_function_command: 'python -u main.py run'
speckle_function_recommended_cpu_m: 2000
speckle_function_recommended_memory_mi: 100
speckle_function_recommended_cpu_m: 8000
speckle_function_recommended_memory_mi: 8000
Regular → Executable
+11
View File
@@ -311,3 +311,14 @@ pyrightconfig.json
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,pycharm
Dockerfile copy
Dockerfile copy 2
.idea/git_toolbox_prj.xml
.gitignore
.idea/misc.xml
.idea/inspectionProfiles/Project_Default.xml
.gitignore
.idea/vcs.xml
.idea/speckle-automate-basic-clash-demo.iml
.idea/modules.xml
.idea/inspectionProfiles/profiles_settings.xml
+3 -1
View File
@@ -246,4 +246,6 @@ COPY . /home/speckle
# Using poetry, we generate a list of requirements, save them to requirements.txt, and then use pip to install them
RUN poetry export --format requirements.txt --output /home/speckle/requirements.txt --without-hashes && \
pip install --requirement /home/speckle/requirements.txt
pip install --requirement /home/speckle/requirements.txt
RUN poetry install --no-root
+150
View File
@@ -0,0 +1,150 @@
from collections import defaultdict
from concurrent.futures import ProcessPoolExecutor, as_completed
from typing import List, Tuple, Any, Optional
try:
import pymesh
except ImportError:
from Geometry.mocks import mypymesh
pymesh = mypymesh
from speckle_automate import AutomationContext
from Geometry.element import Element
from Geometry.mesh import cast
def detect_clashes_old(
reference_elements: List[Element], latest_elements: List[Element], _tolerance: float
) -> list[tuple[str, str, float]]:
"""
Detect clashes between two sets of mesh elements using Pymesh.
Args:
reference_elements (List[Element]): Elements from the reference model.
latest_elements (List[Element]): Elements from the latest model.
_tolerance (float): Tolerance value for clash detection. TODO: how to implement this?
Returns:
List[Tuple[str, str]]: List of tuples indicating clashes, with each tuple
containing the IDs of the clashing elements.
"""
# TODO: Spatial partitioning to reduce number of comparisons
# TODO: Tolerance
# TODO: parallel processing
clashes = []
for ref_element in reference_elements:
for latest_element in latest_elements:
for ref_mesh in ref_element.meshes:
for latest_mesh in latest_element.meshes:
# Convert Trimesh meshes to Pymesh if necessary
ref_pymesh: pymesh.Mesh = cast(ref_mesh, pymesh.Mesh)
latest_pymesh: pymesh.Mesh = cast(latest_mesh, pymesh.Mesh)
if not ref_pymesh or not latest_pymesh:
continue
intersection = pymesh.boolean(
latest_pymesh, ref_pymesh, operation="intersection"
)
if (
intersection and intersection.volume > 0
): # TODO: could tolerance relate to this?
severity = intersection.volume / min(
ref_pymesh.volume, latest_pymesh.volume
)
clashes.append((ref_element.id, latest_element.id, severity))
break
return clashes
def check_for_clash(
ref_element: Element, latest_element: Element
) -> Optional[tuple[Any, Any]]:
"""
Check for a clash between two elements and calculate the severity of the clash.
Args:
ref_element (Element): An element from the reference model.
latest_element (Element): An element from the latest model.
Returns:
Tuple[str, str, float]: A tuple containing the IDs of the clashing elements and the severity, if a clash is found.
"""
for ref_mesh in ref_element.meshes:
for latest_mesh in latest_element.meshes:
ref_pymesh = cast(ref_mesh, pymesh.Mesh)
latest_pymesh = cast(latest_mesh, pymesh.Mesh)
if not ref_pymesh or not latest_pymesh:
continue
intersection = pymesh.boolean(latest_pymesh, ref_pymesh, operation="intersection")
if intersection and intersection.volume > 0:
return ref_element.id, latest_element.id
return None
def detect_clashes(
reference_elements: List[Element], latest_elements: List[Element], _tolerance: float
) -> List[Tuple[str, str]]:
"""
Detect clashes between two sets of mesh elements using parallel processing.
Args:
reference_elements (List[Element]): Elements from the reference model.
latest_elements (List[Element]): Elements from the latest model.
_tolerance (float): Tolerance value for clash detection. TODO: how to implement this?
Returns:
List[Tuple[str, str, float]]: A list of tuples indicating clashes.
"""
clashes = []
with ProcessPoolExecutor() as executor:
future_clash = {
executor.submit(check_for_clash, ref, latest): (ref, latest)
for ref in reference_elements
for latest in latest_elements
}
for future in as_completed(future_clash):
result = future.result()
if result:
clashes.append(result)
return clashes
def detect_and_report_clashes(
reference_elements: list[Element],
latest_elements: list[Element],
tolerance: float,
automate_context: AutomationContext,
) -> list[tuple[str, str]]:
clashes = detect_clashes(reference_elements, latest_elements, tolerance)
grouped_clashes = defaultdict(list)
for ref, latest in clashes:
if not latest:
continue
grouped_clashes[ref].append(latest)
for group_number, clashing_objects in enumerate(grouped_clashes.items(), start=1):
ref_id, latest_elements = clashing_objects
all_clashing_objects = [ref_id] + [element_id for element_id in latest_elements]
automate_context.attach_error_to_objects(
category="Clash", object_ids=all_clashing_objects, message=str(group_number)
)
return clashes
+64
View File
@@ -0,0 +1,64 @@
from typing import Tuple, Optional, List
import numpy as np
import trimesh
from specklepy.objects import Base
from specklepy.objects.geometry import Mesh as SpeckleMesh
from specklepy.objects.other import Transform
from Geometry.helpers import combine_transform_matrices
from Geometry.mesh import speckle_mesh_to_trimesh
class Element:
def __init__(self, id, meshes):
"""
Initialize an Element object with an ID and a list of meshes.
Args:
id (str): The ID of the Element.
meshes (List[Trimesh]): List of trimesh Mesh objects.
"""
self.id = id
self.meshes = meshes
def speckle_to_element(
base_id_transforms: Tuple[Base, str, Optional[List[Transform]]]
) -> Element:
"""
Convert a SpecklePy Base object, its identifier, and an optional list of transforms
to an Element object.
Args:
base_id_transforms (tuple): Contains a SpecklePy Base object, its identifier,
and an optional list of Transform objects.
Returns:
Element: The resulting Element object.
"""
base, speckle_id, transforms = base_id_transforms
display_value = base.displayValue
if isinstance(display_value, SpeckleMesh):
display_value = [display_value]
element = Element(speckle_id, meshes=[])
# Combine all transforms into a single matrix
combined_transform = (
combine_transform_matrices(transforms) if transforms else np.identity(4)
)
if isinstance(display_value, list):
for mesh in display_value:
if mesh:
t_mesh = speckle_mesh_to_trimesh(mesh)
if not isinstance(t_mesh, trimesh.Trimesh):
continue
# Apply the combined transformation matrix
t_mesh.apply_transform(combined_transform)
element.meshes.append(t_mesh)
return element
+143
View File
@@ -0,0 +1,143 @@
from typing import List
import numpy as np
from specklepy.objects.geometry import Vector
from specklepy.objects.other import Transform as SpeckleTransform
def calculate_polygon_normal(vertices: List[Vector]) -> Vector:
"""
Calculate the normal vector for a polygon represented by a list of vertices.
Args:
vertices (List[Vector]): A list of vertices representing the polygon.
Returns:
Vector: The normal vector of the polygon.
"""
normal = Vector.from_list([0.0, 0.0, 0.0])
num_vertices = len(vertices)
for i in range(num_vertices):
curr, nxt = vertices[i], vertices[(i + 1) % num_vertices]
# Cross product components are accumulated to find the normal.
normal.x += (curr.y - nxt.y) * (curr.z + nxt.z)
normal.y += (curr.z - nxt.z) * (curr.x + nxt.x)
normal.z += (curr.x - nxt.x) * (curr.y + nxt.y)
# Normalize the calculated normal vector.
length = np.sqrt(normal.x**2 + normal.y**2 + normal.z**2)
normal.x, normal.y, normal.z = (
normal.x / length,
normal.y / length,
normal.z / length,
)
return normal
def is_point_within_triangle(pt: Vector, v1: Vector, v2: Vector, v3: Vector) -> bool:
"""
Check if a point is inside a given triangle.
Args:
pt (Vector): The point to check.
v1, v2, v3 (Vector): The vertices of the triangle.
Returns:
bool: True if the point is inside the triangle, False otherwise.
"""
def sign(p1, p2, p3):
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
b1 = sign(pt, v1, v2) < 0.0
b2 = sign(pt, v2, v3) < 0.0
b3 = sign(pt, v3, v1) < 0.0
return (b1 == b2) and (b2 == b3)
def triangulate_face(vertices: List[Vector]) -> List[List[int]]:
"""
Triangulate a polygon defined by a list of vertices.
Args:
vertices (List[Vector]): The vertices of the polygon.
Returns:
List[List[int]]: A list of triangles, each represented as a list of vertex indices.
"""
triangles = []
indices = list(range(len(vertices)))
normal = calculate_polygon_normal(vertices)
# The ear clipping algorithm is used for triangulation.
while len(indices) > 2:
for i in range(len(indices)):
prev, curr, nxt = (
indices[i - 1],
indices[i],
indices[(i + 1) % len(indices)],
)
if is_convex(vertices[prev], vertices[curr], vertices[nxt], normal):
triangles.append([prev, curr, nxt])
del indices[i]
break
return triangles
def is_convex(a: Vector, b: Vector, c: Vector, normal: Vector) -> bool:
"""
Check if a triangle formed by three vertices (a, b, c) is convex with respect to a given normal.
Args:
a (Vector): The first vertex of the triangle.
b (Vector): The second vertex of the triangle.
c (Vector): The third vertex of the triangle.
normal (Vector): The normal vector with respect to which convexity is checked.
Returns:
bool: True if the triangle is convex with respect to the normal, False otherwise.
"""
ab = Vector.from_list([b.x - a.x, b.y - a.y, b.z - a.z])
bc = Vector.from_list([c.x - b.x, c.y - b.y, c.z - b.z])
cross = Vector.from_list(
[
ab.y * bc.z - ab.z * bc.y,
ab.z * bc.x - ab.x * bc.z,
ab.x * bc.y - ab.y * bc.x,
]
)
# Dot product to compare with the face normal
return cross.x * normal.x + cross.y * normal.y + cross.z * normal.z > 0
def combine_transform_matrices(transforms: List[SpeckleTransform]) -> np.ndarray:
"""
Combine multiple transformation matrices into a single matrix.
Args:
transforms (List[SpeckleTransform]): A list of Speckle Transform objects.
Returns:
np.ndarray: A combined 4x4 transformation matrix.
"""
combined_matrix = np.identity(4)
for transform in transforms:
matrix = convert_speckle_transform_to_matrix(transform)
combined_matrix = np.dot(combined_matrix, matrix)
return combined_matrix
def convert_speckle_transform_to_matrix(transform: SpeckleTransform) -> np.ndarray:
"""
Convert a Speckle Transform object to a 4x4 NumPy matrix.
Args:
transform (SpeckleTransform): The Speckle Transform object.
Returns:
np.ndarray: A 4x4 transformation matrix.
"""
return np.array(transform.value).reshape(4, 4)
+84 -91
View File
@@ -1,107 +1,100 @@
from typing import Tuple, Optional
from typing import Union, Type
try:
import pymesh
except ImportError:
from Geometry.mocks import mypymesh
pymesh = mypymesh
import trimesh
from Geometry.helpers import triangulate_face
def trimesh_to_pymesh(mesh: trimesh.Trimesh) -> pymesh.Mesh:
"""
Convert a Trimesh object to a Pymesh object.
Args:
mesh (Trimesh): The Trimesh object to convert.
Returns:
pymesh.Mesh: The resulting Pymesh object.
"""
return pymesh.form_mesh(mesh.vertices, mesh.faces)
def pymesh_to_trimesh(mesh: pymesh.Mesh) -> trimesh.Trimesh:
"""
Convert a Pymesh object to a Trimesh object.
Args:
mesh (pymesh.Mesh): The Pymesh object to convert.
Returns:
trimesh.Trimesh: The resulting Trimesh object.
"""
return trimesh.Trimesh(vertices=mesh.vertices, faces=mesh.faces)
def cast(
mesh: Union[trimesh.Trimesh, pymesh.Mesh], target_type: Type
) -> Union[trimesh.Trimesh, pymesh.Mesh]:
"""
Casts a mesh object to a specified type.
Args:
mesh (Union[trimesh.Trimesh, pymesh.Mesh]): The mesh object to cast.
target_type (Type): The type to cast the mesh to.
Returns:
Union[trimesh.Trimesh, pymesh.Mesh]: The cast mesh object.
"""
if isinstance(mesh, trimesh.Trimesh) and target_type is pymesh.Mesh:
return trimesh_to_pymesh(mesh)
elif isinstance(mesh, pymesh.Mesh) and target_type is trimesh.Trimesh:
return pymesh_to_trimesh(mesh)
else:
raise TypeError("Unsupported mesh type or target type.")
import numpy as np
import trimesh
from specklepy.objects import Base
from specklepy.objects.geometry import Mesh as SpeckleMesh
from specklepy.objects.other import Transform
from trimesh import Trimesh
from specklepy.objects.geometry import Mesh as SpeckleMesh, Vector
class Element:
def __init__(self, id, meshes):
"""
Initialize an Element object with an ID and a list of meshes.
def speckle_mesh_to_trimesh(input_mesh: SpeckleMesh) -> trimesh.Trimesh:
vertices = np.array(input_mesh.vertices).reshape((-1, 3))
faces = []
Args:
id (str): The ID of the Element.
meshes (List[Trimesh]): List of trimesh Mesh objects.
"""
self.id = id
self.meshes = meshes
i = 0
while i < len(input_mesh.faces):
face_vertex_count = input_mesh.faces[i]
i += 1 # Skip the vertex count
face_vertex_indices = input_mesh.faces[i: i + face_vertex_count]
def speckle_transform_to_trimesh_matrix(transform: Transform) -> np.ndarray:
"""
Convert the Speckle Transform matrix to a NumPy array format suitable for trimesh.
face_vertices = [
Vector.from_list(vertices[idx].tolist()) for idx in face_vertex_indices
]
Returns:
np.ndarray: 4x4 transformation matrix in NumPy array format.
"""
return np.array(transform.value).reshape(4, 4)
if face_vertex_count == 3:
faces.append(face_vertex_indices)
else:
triangulated = triangulate_face(face_vertices)
faces.extend(
[[face_vertex_indices[idx] for idx in tri] for tri in triangulated]
)
i += face_vertex_count
def speckle_to_element(
base_with_transforms: Tuple[Base, str, Optional[Transform]]
) -> Element:
"""
Convert a SpecklePy Base object and its associated Transform to an Element object.
t_mesh = trimesh.Trimesh(vertices=vertices, faces=np.array(faces))
Args:
base_with_transforms (tuple): Contains a SpecklePy Base object and its
associated Transform object.
return t_mesh
Returns:
Element: The resulting Element object.
"""
# code below speeds up the process but is not used in the current implementation.
# Bounding boxes could be used for a 2-pass approach to speed up the process.
# Unpack the tuple to get the base, speckle ID, and transform.
base, speckle_id, transform = base_with_transforms
# obbox = t_mesh.bounding_box_oriented
# To convert the Base object to a trimesh Mesh, use the displayValue property.
# This property provides the display mesh, expected to be an iterable of
# SpecklePy Mesh objects. However, legacy objects might be a single mesh.
display_value = base.displayValue
if isinstance(display_value, SpeckleMesh):
display_value = [display_value]
# obbox_mesh = obbox.to_mesh()
if isinstance(display_value, list):
# Initialize an Element with an empty list of meshes.
element = Element(speckle_id, meshes=[])
for mesh in display_value:
if mesh:
# Convert the SpecklePy Mesh to a trimesh Mesh.
t_mesh = speckle_to_trimesh(mesh)
if not isinstance(t_mesh, Trimesh):
continue
# If there's a transform, apply it to the trimesh Mesh.
if transform is not None:
trimesh_matrix = speckle_transform_to_trimesh_matrix(transform)
t_mesh.apply_transform(trimesh_matrix)
# Append the trimesh Mesh to the Element's list of meshes.
element.meshes.append(t_mesh)
return element
def speckle_to_trimesh(speckle_mesh: SpeckleMesh) -> Trimesh:
"""
Convert a SpecklePy Mesh to a trimesh Mesh object.
Args:
speckle_mesh: The SpecklePy Mesh to convert.
Returns:
trimesh.Trimesh: The resulting trimesh Mesh object.
"""
# Convert the list of vertices to a numpy array. Reshape it to
# (num_vertices, 3) to fit the trimesh format.
vertices_array = np.array(speckle_mesh.vertices).reshape((-1, 3))
# Faces are expected to be triangular. Reshape the faces list accordingly.
# Convert the faces list to a numpy array
faces_array_raw = np.array(speckle_mesh.faces)
# Remove the leading 3s by skipping every 4th value
faces_cleaned = np.delete(faces_array_raw, np.arange(0, faces_array_raw.size, 4))
# Reshape the array into (-1, 3) shape
faces_array = faces_cleaned.reshape((-1, 3))
# Return a new trimesh object using the reshaped vertices and faces.
return trimesh.Trimesh(vertices=vertices_array, faces=faces_array)
# return obbox_mesh
+17
View File
@@ -0,0 +1,17 @@
class mypymesh:
class Mesh:
def __init__(self, vertices, faces):
self.vertices = vertices
self.faces = faces
@property
def volume(self):
return 0
@staticmethod
def boolean(mesh_a, mesh_b, operation):
return mypymesh.Mesh([], [])
@staticmethod
def form_mesh(vertices, faces):
return mypymesh.Mesh(vertices, faces)
-16
View File
@@ -1,16 +0,0 @@
import pymesh
vertices = [
[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0]
]
faces = [
[0, 1, 2],
[0, 2, 3]
]
mesh = pymesh.form_mesh(vertices, faces)
+208
View File
@@ -0,0 +1,208 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 AEC Systems
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
NOTICE: Unless otherwise described, the code in this repository is
licensed under the license above. Some modules, extensions or code herein
might be otherwise licensed. This is indicated either in the root of the
containing folder under a different license file, or in the respective
file's header. If you have any questions, don't hesitate to get in touch
with us via [email](mailto:hello@speckle.systems).
+7 -1
View File
@@ -3,7 +3,13 @@
# Speckle Automate Function: Basic Clash Analysis Demo
## Overview
This repository hosts the Basic Clash Analysis Demo for Speckle Automate, tailored for the AEC industry. It demonstrates automated clash detection using complex third-party libraries to analyze elements from both a static reference model and a dynamic model.
This repository hosts the Basic Clash Analysis Demo for Speckle Automate, which is tailored for the AEC industry. It demonstrates automated clash detection using complex third-party libraries to analyze elements from static and dynamic reference models.
<img src="https://github.com/specklesystems/speckle_automate-basic_clash_demo/assets/760691/b4df8124-e859-4fca-bdda-7bdcecd9f81b" height="159px" title="Clash Detected!">
<img src="https://github.com/specklesystems/speckle_automate-basic_clash_demo/assets/760691/3672947a-3e22-490d-b0a2-070384ca88d7" height="150px" title="Location in Context">
<img height="150" alt="Screenshot 2023-11-13 222423" src="https://github.com/specklesystems/speckle_automate-basic_clash_demo/assets/760691/9b0c8006-3ea9-4b96-8c23-01fb136131c5">
<img height="150" alt="Screenshot 2023-11-13 222407" src="https://github.com/specklesystems/speckle_automate-basic_clash_demo/assets/760691/a40150b1-97bb-4b49-964e-89d4f2f55b6a">
<img src="https://github.com/specklesystems/speckle_automate-basic_clash_demo/assets/760691/c5472806-651e-461e-b902-fb93536ab8ab" height="150px" title="Runs on every Publisj Event">
## ⚠️ Disclaimer: Conceptual Demonstration Only
**IMPORTANT: This function is a conceptual model and is not intended for actual use in production environments. It serves as a demonstration to illustrate automated clash detection principles in Speckle Automate and the use of third-party libraries for advanced analysis.**
+3 -5
View File
@@ -30,12 +30,12 @@ class ElementCheckRules:
"""Rule: Check if a parameter is displayable."""
return (
lambda parameter: parameter.displayValue
and parameter.displayValue is not None
and parameter.displayValue is not None
)
@staticmethod
def speckle_type_rule(
desired_type: Union[str, List[str]]
desired_type: Union[str, List[str]]
) -> Callable[[Base], bool]:
"""Rule: Check if a parameter's speckle_type matches the desired type."""
@@ -43,9 +43,7 @@ class ElementCheckRules:
if isinstance(desired_type, str):
desired_type = [desired_type]
print(desired_type)
return (
lambda speckle_object: getattr(speckle_object, "speckle_type", None)
in desired_type
in desired_type
)
+1 -1
View File
@@ -1,5 +1,5 @@
"""Helper module for a simple speckle object tree flattening."""
from typing import Tuple, Optional
from typing import Tuple, Optional, List
from specklepy.objects import Base
from specklepy.objects.other import Instance, Transform
+68 -84
View File
@@ -2,7 +2,9 @@
use the automation_context module to wrap your function in an Automate context helper
"""
from typing import List, Optional, Tuple
from collections import defaultdict
from typing import Optional
from pydantic import Field
from speckle_automate import (
@@ -16,9 +18,9 @@ from specklepy.objects import Base
from specklepy.objects.other import Transform
from specklepy.objects.units import Units
from specklepy.transports.server import ServerTransport
from trimesh import Trimesh
from Geometry.mesh import speckle_to_element, Element
from Geometry.clash import detect_and_report_clashes
from Geometry.element import speckle_to_element
from Rules.checks import ElementCheckRules
from Utilities.flatten import extract_base_and_transform
@@ -31,23 +33,29 @@ class FunctionInputs(AutomateBase):
https://docs.pydantic.dev/latest/usage/models/
"""
tolerance: float = Field(
default=25.0,
title="Tolerance",
description="Specify the tolerance value for the analysis. \
Negative values relaxes the test, positive values make it more strict.",
)
tolerance_unit: str = Field( # Using the SpecklePy Units enum here
default=Units.mm,
json_schema_extra={"examples": ["mm", "cm", "m"]},
title="Tolerance Unit",
description="Unit of the tolerance value.",
)
static_model_name: str = Field(
...,
title="Static Model Name",
description="Name of the static structural model.",
)
tolerance: float = Field(
default=25.0,
title="Tolerance",
description="Specify the tolerance value for the analysis. \
Negative values relaxes the test, positive values make it more strict.",
json_schema_extra={
"readOnly": True,
},
)
tolerance_unit: str = Field( # Using the SpecklePy Units enum here
default=Units.mm,
title="Tolerance Unit",
description="Unit of the tolerance value.",
json_schema_extra={
"examples": ["mm", "cm", "m"],
"readOnly": True
},
)
def automate_function(
@@ -67,8 +75,11 @@ def automate_function(
changed_model_version = automate_context.receive_version()
try:
reference_model_version = get_reference_model(
automate_context, function_inputs.static_model_name
reference_model_version, reference_model_id, reference_model_version_id = (
get_reference_model(automate_context, function_inputs.static_model_name)
)
print(
f"Reference model id: {reference_model_id}, version id: {reference_model_version_id}"
)
except Exception as ex:
@@ -95,6 +106,8 @@ def automate_function(
"Objects.BuiltElements.Duct",
"Objects.BuiltElements.Duct:Objects.BuiltElements.Revit.RevitDuct",
"Objects.BuiltElements.Duct:Objects.BuiltElements.Revit.RevitDuct:Objects.BuiltElements.Revit.RevitFlexDuct",
"Objects.Other.Revit.RevitInstance:Objects.BuiltElements.Revit.RevitMEPFamilyInstance",
"Objects.BuiltElements.Revit.RevitElementType:Objects.BuiltElements.Revit.RevitSymbolElementType"
]
visible_beams_rule = element_rules.rule_combiner(
@@ -112,6 +125,7 @@ def automate_function(
for base_obj, id, transform in reference_objects
if visible_beams_rule(base_obj)
]
latest_displayable_objects = [
(base_obj, id, transform)
for base_obj, id, transform in latest_objects
@@ -125,84 +139,54 @@ def automate_function(
speckle_to_element(obj) for obj in latest_displayable_objects
]
# using trimesh library process all these meshes in the form of A vs B
# and get the clashes
tolerance = function_inputs.tolerance
clashes = detect_clashes(
reference_mesh_elements, latest_mesh_elements, function_inputs.tolerance
if len(reference_mesh_elements) == 0 or len(latest_mesh_elements) == 0:
automate_context.mark_run_failed(
status_message="Clash detection failed. No objects to compare."
)
return
clashes = detect_and_report_clashes(
reference_mesh_elements, latest_mesh_elements, tolerance, automate_context
)
print(len(clashes))
percentage_reference_objects_clashing = (
len(set([ref_id for ref_id, latest_id in clashes]))
/ len(reference_mesh_elements)
* 100
)
percentage_latest_objects_clashing = (
len(set([latest_id for ref_id, latest_id in clashes]))
/ len(latest_mesh_elements)
* 100
)
automate_context.mark_run_success(status_message="Clash detection completed.")
# all clashes count
all_objects_count = len(reference_mesh_elements) + len(latest_mesh_elements)
all_clashes_count = len(clashes)
clash_report_message = (
f"Clash detection report: {all_clashes_count} clashes found "
f"between {all_objects_count} objects. "
f"Percentage of reference objects clashing: "
f"{percentage_reference_objects_clashing}%. "
f"Percentage of latest objects clashing: "
f"{percentage_latest_objects_clashing}%."
)
reference_view = [f"{reference_model_id}@{reference_model_version_id}"]
def detect_clashes(
elements_a: List[Element], elements_b: List[Element], length_tolerance: float
) -> List[Tuple[Element, Element]]:
"""
Detects clashes between two sets of elements with a specified tolerance.
automate_context.set_context_view(reference_view)
This function checks each combination of elements from `elements_a` and `elements_b`
to see if any of their respective meshes intersect within the specified tolerance.
If a clash is detected between any mesh from an element in `elements_a` and any mesh
from an element in `elements_b`, the pair of elements is added to the results.
Args:
- elements_a (List[Element]): A list of `Element` objects to be checked for clashes.
- elements_b (List[Element]): A second list of `Element` objects to be checked for clashes against `elements_a`.
- length_tolerance (float): The distance to offset mesh vertices for intersection check.
Returns:
- List[Tuple[Element, Element]]: A list of tuples where each tuple contains a pair of `Element` objects that clash.
"""
# Use list comprehension to get pairs of elements that have clashing meshes
clashes = [
(element_a, element_b)
for element_a in elements_a
for element_b in elements_b
if any(
check_intersection_with_tolerance(mesh_a, mesh_b, length_tolerance)
for mesh_a in element_a.meshes
for mesh_b in element_b.meshes
)
]
return clashes
def check_intersection_with_tolerance(
mesh_a: Trimesh, mesh_b: Trimesh, tolerance: float
) -> bool:
"""
Checks for intersections between two meshes within a specified tolerance.
Args:
- mesh_a: The first mesh to check.
- mesh_b: The second mesh to check.
- tolerance (float): The distance to offset mesh vertices for intersection check.
Positive values expand the mesh, negative values contract it.
Returns:
- bool: True if the meshes intersect within the specified tolerance, otherwise False.
"""
half_tolerance = tolerance / 2.0 # TODO: how to shrink bloat mesh?
offset_mesh_a: Trimesh = mesh_a # mesh_a.offset_mesh(half_tolerance)
offset_mesh_b: Trimesh = mesh_b # mesh_b.offset_mesh(half_tolerance)
# return offset_mesh_a.intersection(offset_mesh_b).volume > 0 TODO: Install Blender as the engine
# return a random boolean for testing - significantly favouring false
import random
return random.random() < 0.05
automate_context.mark_run_success(
status_message="Clash detection completed. " + clash_report_message
)
def get_reference_model(
automate_context: AutomationContext, static_model_name: str
) -> Base:
) -> tuple[Base, Optional[str], Optional[str]]:
# the static reference model will be retrieved from the project using model name stored in the inputs
speckle_client = automate_context.speckle_client
project_id = automate_context.automation_run_data.project_id
@@ -236,7 +220,7 @@ def get_reference_model(
remote_transport,
) # receive the static model
return latest_reference_model_version
return latest_reference_model_version, model.id, reference_model_commits[0].id
# make sure to call the function with the executor
Generated
+589 -546
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -7,14 +7,15 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
specklepy = "2.17.11"
specklepy = "2.17.17"
trimesh = "^4.0.4"
pytest = "^7.4.2"
python-dotenv = "^1.0.0"
[tool.poetry.group.dev.dependencies]
black = "^23.3.0"
mypy = "^1.3.0"
ruff = "^0.0.271"
pytest = "^7.4.2"
[build-system]
requires = ["poetry-core"]
@@ -22,11 +23,11 @@ build-backend = "poetry.core.masonry.api"
[tool.ruff]
select = [
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade
"D", # pydocstyle
"I", # isort
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade
"D", # pydocstyle
"I", # isort
]
[tool.ruff.pydocstyle]
+48
View File
@@ -643,3 +643,51 @@ yarl==1.9.2 ; python_version >= "3.10" and python_version < "4.0" \
--hash=sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7 \
--hash=sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78 \
--hash=sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7
yarl~=1.9.2
gql~=3.4.1
backoff~=2.2.1
multidict~=6.0.4
requests~=2.31.0
websockets~=10.4
pytest~=7.4.2
h11~=0.14.0
pip~=23.3.1
attrs~=23.1.0
wheel~=0.40.0
mypy~=1.6.1
idna~=3.4
sniffio~=1.3.0
black~=23.10.0
platformdirs~=3.11.0
packaging~=23.2
pathspec~=0.11.2
click~=8.1.5
httpcore~=1.0.2
certifi~=2023.7.22
setuptools~=65.5.0
numpy~=1.25.2
pluggy~=1.3.0
Deprecated~=1.2.14
iniconfig~=2.0.0
trimesh~=4.0.4
anyio~=4.0.0
pydantic~=2.4.2
urllib3~=1.26.18
specklepy~=2.17.11
stringcase~=1.2.0
ujson~=5.8.0
wrapt~=1.16.0
httpx~=0.25.1
Pillow~=10.1.0
docutils~=0.20.1
sphinx~=7.0.1
Jinja2~=3.1.2
filelock~=3.12.2
Pygments~=2.15.1
pytz~=2023.3.post1
networkx~=3.1
scipy~=1.11.1
psutil~=5.9.5
python-dotenv~=1.0.0
pymesh~=1.0.2
+17 -17
View File
@@ -23,12 +23,12 @@ def crypto_random_string(length: int) -> str:
def register_new_automation(
project_id: str,
model_id: str,
speckle_client: SpeckleClient,
automation_id: str,
automation_name: str,
automation_revision_id: str,
project_id: str,
model_id: str,
speckle_client: SpeckleClient,
automation_id: str,
automation_name: str,
automation_revision_id: str,
):
"""Register a new automation in the speckle server."""
query = gql(
@@ -97,13 +97,13 @@ def test_object() -> Base:
# fixture to mock the AutomationRunData that would be generated by a full Automation Run
def fake_automation_run_data(request, test_client: SpeckleClient) -> AutomationRunData:
server_url = request.config.SPECKLE_SERVER_URL
project_id = "4f064f09e6"
model_id = "5a16cf52af"
project_id = "7d8e96669a"
model_id = "efeb71387b"
function_name = "Clash Test"
automation_id = crypto_random_string(10)
automation_name = "Local Test Automation"
automation_name = "Long running clash test"
automation_revision_id = crypto_random_string(10)
register_new_automation(
@@ -119,7 +119,7 @@ def fake_automation_run_data(request, test_client: SpeckleClient) -> AutomationR
project_id=project_id,
model_id=model_id,
branch_name="main",
version_id="2b16327448",
version_id="2eb06c1034",
speckle_server_url=server_url,
# These ids would be available with a valid registered Automation definition.
automation_id=automation_id,
@@ -142,7 +142,7 @@ def test_function_run(fake_automation_run_data: AutomationRunData, speckle_token
context,
automate_function,
FunctionInputs(
tolerance=0.1, tolerance_unit="mm", static_model_name="structural"
tolerance=0.1, tolerance_unit="mm", static_model_name="simple beams"
),
)
@@ -156,26 +156,26 @@ def context(fake_automation_run_data: AutomationRunData, speckle_token: str):
def test_non_existent_model(context, test_client: SpeckleClient):
with pytest.raises(
Exception, match="The static model named does not exist, skipping the function."
Exception, match="The static model named does not exist, skipping the function."
):
get_reference_model(context, "Fake Name")
def test_model_with_no_versions(context, test_client: SpeckleClient):
with pytest.raises(
Exception, match="The static model has no versions, skipping the function."
Exception, match="The static model has no versions, skipping the function."
):
get_reference_model(context, "blank")
def test_same_as_changed_model(context, test_client: SpeckleClient):
with pytest.raises(
Exception,
match="The static model is the same as the changed model, skipping the function.",
Exception,
match="The static model is the same as the changed model, skipping the function.",
):
get_reference_model(context, "hvac")
get_reference_model(context, "clash simple")
def test_valid_reference_model(context, test_client: SpeckleClient):
reference_model = get_reference_model(context, "structural")
reference_model = get_reference_model(context, "simple beams")
assert reference_model is not None