Init Pymesh Base
This commit is contained in:
@@ -1,39 +1,24 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
||||
{
|
||||
"name": "Python 3",
|
||||
"name": "speckle-automate-pymesh-spatial-joins",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
||||
},
|
||||
|
||||
"remoteEnv": {
|
||||
"SPECKLE_TOKEN": "foobar"
|
||||
},
|
||||
"containerEnv": {
|
||||
"SPECKLE_TOKEN": "asdfasdf"
|
||||
},
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
"dockerFile": "../Dockerfile",
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.example .env && POETRY_VIRTUALENVS_IN_PROJECT=true poetry install --no-root",
|
||||
// "postCreateCommand": "poetry install --no-root",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.python",
|
||||
"ms-python.black-formatter",
|
||||
"mikestead.dotenv",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"mikestead.dotenv"
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.black-formatter",
|
||||
"charliermarsh.ruff"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.10'
|
||||
- name: Install and configure Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
|
||||
+236
-3
@@ -1,5 +1,237 @@
|
||||
# We use the official Python 3.11 image as our base image and will add our code to it. For more details, see https://hub.docker.com/_/python
|
||||
FROM python:3.11-slim
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
# Install basic packages
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
netbase \
|
||||
wget; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install version control systems and procps
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
mercurial \
|
||||
openssh-client \
|
||||
subversion \
|
||||
procps; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install various development tools and libraries
|
||||
RUN set -ex; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
autoconf \
|
||||
automake \
|
||||
bzip2 \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
imagemagick \
|
||||
libbz2-dev \
|
||||
libc6-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libdb-dev \
|
||||
libevent-dev \
|
||||
libffi-dev \
|
||||
libgdbm-dev \
|
||||
libglib2.0-dev \
|
||||
libgmp-dev \
|
||||
libjpeg-dev \
|
||||
libkrb5-dev \
|
||||
liblzma-dev \
|
||||
libmagickcore-dev \
|
||||
libmagickwand-dev \
|
||||
libmaxminddb-dev \
|
||||
libncurses5-dev \
|
||||
libncursesw5-dev \
|
||||
libpng-dev \
|
||||
libpq-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
libwebp-dev \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
libyaml-dev \
|
||||
make \
|
||||
patch \
|
||||
unzip \
|
||||
xz-utils \
|
||||
zlib1g-dev \
|
||||
$(if apt-cache show 'default-libmysqlclient-dev' 2>/dev/null | grep -q '^Version:'; then \
|
||||
echo 'default-libmysqlclient-dev'; \
|
||||
else \
|
||||
echo 'libmysqlclient-dev'; \
|
||||
fi); \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set environment variables
|
||||
ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
# Install additional libraries
|
||||
RUN /bin/sh -c set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libbluetooth-dev \
|
||||
tk-dev \
|
||||
uuid-dev; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set GPG key and Python version
|
||||
ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
|
||||
ENV PYTHON_VERSION=3.10.13
|
||||
|
||||
# Install Python from source
|
||||
RUN /bin/sh -c set -eux; \
|
||||
wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz"; \
|
||||
wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc"; \
|
||||
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \
|
||||
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY"; \
|
||||
gpg --batch --verify python.tar.xz.asc python.tar.xz; \
|
||||
gpgconf --kill all; \
|
||||
rm -rf "$GNUPGHOME" python.tar.xz.asc; \
|
||||
mkdir -p /usr/src/python; \
|
||||
tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \
|
||||
rm python.tar.xz; \
|
||||
cd /usr/src/python; \
|
||||
gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
|
||||
./configure \
|
||||
--build="$gnuArch" \
|
||||
--enable-loadable-sqlite-extensions \
|
||||
--enable-optimizations \
|
||||
--enable-option-checking=fatal \
|
||||
--enable-shared \
|
||||
--with-lto \
|
||||
--with-system-expat \
|
||||
--without-ensurepip; \
|
||||
nproc="$(nproc)"; \
|
||||
EXTRA_CFLAGS="$(dpkg-buildflags --get CFLAGS)"; \
|
||||
LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"; \
|
||||
make -j "$nproc" \
|
||||
"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \
|
||||
"LDFLAGS=${LDFLAGS:-}" \
|
||||
"PROFILE_TASK=${PROFILE_TASK:-}"; \
|
||||
rm python; \
|
||||
make -j "$nproc" \
|
||||
"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \
|
||||
"LDFLAGS=${LDFLAGS:--Wl},-rpath='\$\$ORIGIN/../lib'" \
|
||||
"PROFILE_TASK=${PROFILE_TASK:-}" \
|
||||
python; \
|
||||
make install; \
|
||||
bin="$(readlink -ve /usr/local/bin/python3)"; \
|
||||
dir="$(dirname "$bin")"; \
|
||||
mkdir -p "/usr/share/gdb/auto-load/$dir"; \
|
||||
cp -vL Tools/gdb/libpython.py "/usr/share/gdb/auto-load/$bin-gdb.py"; \
|
||||
cd /; \
|
||||
rm -rf /usr/src/python; \
|
||||
find /usr/local -depth \
|
||||
\( \
|
||||
\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
|
||||
-o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \
|
||||
\) -exec rm -rf '{}' +; \
|
||||
ldconfig; \
|
||||
python3 --version
|
||||
|
||||
# Create symlinks for Python tools
|
||||
RUN /bin/sh -c set -eux; \
|
||||
for src in idle3 pydoc3 python3 python3-config; do \
|
||||
dst="$(echo "$src" | tr -d 3)"; \
|
||||
[ -s "/usr/local/bin/$src" ]; \
|
||||
[ ! -e "/usr/local/bin/$dst" ]; \
|
||||
ln -svT "$src" "/usr/local/bin/$dst"; \
|
||||
done
|
||||
|
||||
# Set Python pip and setuptools versions and install pip
|
||||
ENV PYTHON_PIP_VERSION=23.0.1
|
||||
ENV PYTHON_SETUPTOOLS_VERSION=65.5.1
|
||||
ENV PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/c6add47b0abf67511cdfb4734771cbab403af062/public/get-pip.py
|
||||
ENV PYTHON_GET_PIP_SHA256=22b849a10f86f5ddf7ce148ca2a31214504ee6c83ef626840fde6e5dcd809d11
|
||||
RUN /bin/sh -c set -eux; \
|
||||
wget -O get-pip.py "$PYTHON_GET_PIP_URL"; \
|
||||
echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; \
|
||||
export PYTHONDONTWRITEBYTECODE=1; \
|
||||
python get-pip.py \
|
||||
--disable-pip-version-check \
|
||||
--no-cache-dir \
|
||||
--no-compile \
|
||||
"pip==$PYTHON_PIP_VERSION" \
|
||||
"setuptools==$PYTHON_SETUPTOOLS_VERSION"; \
|
||||
rm -f get-pip.py; \
|
||||
pip --version
|
||||
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /root/
|
||||
|
||||
# Set build arguments
|
||||
ARG BRANCH=main
|
||||
ARG NUM_CORES=10
|
||||
|
||||
# Clone a git repository
|
||||
RUN /bin/sh -c "git clone --single-branch --depth 1 -b $BRANCH https://github.com/nuvolos-cloud/PyMesh.git"
|
||||
|
||||
# Set environment variables
|
||||
ENV PYMESH_PATH=/root/PyMesh
|
||||
ENV NUM_CORES=10
|
||||
|
||||
# Install additional packages
|
||||
RUN echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" >> /etc/apt/sources.list.d/unstable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
gcc-9 \
|
||||
g++-9 \
|
||||
git \
|
||||
cmake \
|
||||
libgmp-dev \
|
||||
libmpfr-dev \
|
||||
libgmpxx4ldbl \
|
||||
libboost-dev \
|
||||
libboost-thread-dev \
|
||||
zip unzip patchelf && \
|
||||
apt-get clean
|
||||
|
||||
# Build and install PyMesh
|
||||
WORKDIR /root/PyMesh
|
||||
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/cgal.git $PYMESH_PATH/third_party/cgal
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/libigl.git $PYMESH_PATH/third_party/libigl
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/carve.git $PYMESH_PATH/third_party/carve
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/cork.git $PYMESH_PATH/third_party/cork
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/tetgen.git $PYMESH_PATH/third_party/tetgen
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/qhull.git $PYMESH_PATH/third_party/qhull
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/Clipper.git $PYMESH_PATH/third_party/Clipper
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/eigen.git $PYMESH_PATH/third_party/eigen
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/pybind11.git $PYMESH_PATH/third_party/pybind11
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/geogram.git $PYMESH_PATH/third_party/geogram
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/draco.git $PYMESH_PATH/third_party/draco
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/TetWild.git $PYMESH_PATH/third_party/TetWild
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/WindingNumber.git $PYMESH_PATH/third_party/WindingNumber
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/tbb.git $PYMESH_PATH/third_party/tbb
|
||||
RUN git clone --depth 1 https://github.com/PyMesh/jigsaw.git $PYMESH_PATH/third_party/jigsaw
|
||||
RUN git clone --depth 1 https://github.com/fmtlib/fmt.git $PYMESH_PATH/third_party/fmt
|
||||
RUN git clone --depth 1 https://github.com/gabime/spdlog.git $PYMESH_PATH/third_party/spdlog
|
||||
|
||||
RUN git submodule update --init third_party/triangle
|
||||
RUN git submodule update --init third_party/quartet
|
||||
RUN git submodule update --init third_party/mmg
|
||||
RUN git submodule update --init third_party/json
|
||||
|
||||
RUN pip install -r $PYMESH_PATH/python/requirements.txt
|
||||
RUN ./setup.py bdist_wheel
|
||||
RUN rm -rf build_3.7 third_party/build
|
||||
RUN python $PYMESH_PATH/docker/patches/patch_wheel.py dist/pymesh2*.whl
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install dist/pymesh2*.whl
|
||||
|
||||
# Build third-party libraries for PyMesh
|
||||
WORKDIR /root/PyMesh/third_party
|
||||
RUN /bin/sh -c "python ./build.py mmg && python ./build.py tetgen"
|
||||
|
||||
# We install poetry to generate a list of dependencies which will be required by our application
|
||||
RUN pip install poetry
|
||||
@@ -13,4 +245,5 @@ WORKDIR /home/speckle
|
||||
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 && pip install --requirement /home/speckle/requirements.txt
|
||||
RUN poetry export --format requirements.txt --output /home/speckle/requirements.txt --without-hashes && \
|
||||
pip install --requirement /home/speckle/requirements.txt
|
||||
@@ -0,0 +1,107 @@
|
||||
from typing import Tuple, Optional
|
||||
|
||||
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
|
||||
|
||||
|
||||
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_transform_to_trimesh_matrix(transform: Transform) -> np.ndarray:
|
||||
"""
|
||||
Convert the Speckle Transform matrix to a NumPy array format suitable for trimesh.
|
||||
|
||||
Returns:
|
||||
np.ndarray: 4x4 transformation matrix in NumPy array format.
|
||||
"""
|
||||
return np.array(transform.value).reshape(4, 4)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
base_with_transforms (tuple): Contains a SpecklePy Base object and its
|
||||
associated Transform object.
|
||||
|
||||
Returns:
|
||||
Element: The resulting Element object.
|
||||
"""
|
||||
|
||||
# Unpack the tuple to get the base, speckle ID, and transform.
|
||||
base, speckle_id, transform = base_with_transforms
|
||||
|
||||
# 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]
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,16 @@
|
||||
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)
|
||||
@@ -0,0 +1,51 @@
|
||||
# Required imports
|
||||
from typing import Callable, List, Union
|
||||
|
||||
from specklepy.objects import Base
|
||||
|
||||
|
||||
# We're going to define a set of rules that will allow us to filter and
|
||||
# process parameters in our Speckle objects. These rules will be encapsulated
|
||||
# in a class called `ParameterRules`.
|
||||
|
||||
|
||||
class ElementCheckRules:
|
||||
"""A collection of rules for processing parameters in Speckle objects.
|
||||
|
||||
This class provides static methods that return lambda functions. These
|
||||
lambda functions serve as filters or conditions we can use in our main
|
||||
processing logic. By encapsulating these rules, we can easily extend
|
||||
or modify them in the future.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def rule_combiner(*rules: Callable[[Base], bool]) -> Callable[[Base], bool]:
|
||||
def combined(obj: Base) -> bool:
|
||||
return all(rule(obj) for rule in rules)
|
||||
|
||||
return combined
|
||||
|
||||
@staticmethod
|
||||
def is_displayable_rule() -> Callable[[Base], bool]:
|
||||
"""Rule: Check if a parameter is displayable."""
|
||||
return (
|
||||
lambda parameter: parameter.displayValue
|
||||
and parameter.displayValue is not None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def speckle_type_rule(
|
||||
desired_type: Union[str, List[str]]
|
||||
) -> Callable[[Base], bool]:
|
||||
"""Rule: Check if a parameter's speckle_type matches the desired type."""
|
||||
|
||||
# Convert single string to list for consistent handling
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Helper module for a simple speckle object tree flattening."""
|
||||
from typing import Tuple, Optional
|
||||
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.other import Instance, Transform
|
||||
|
||||
|
||||
# def flatten_base(base: Base) -> Iterable[Base]:
|
||||
# """Take a base and flatten it to an iterable of bases."""
|
||||
# if hasattr(base, "elements") and base["elements"] is not None:
|
||||
# for element in base["elements"]:
|
||||
# yield from flatten_base(element)
|
||||
# yield base
|
||||
|
||||
|
||||
def extract_base_and_transform(
|
||||
base: Base,
|
||||
inherited_instance_id: Optional[str] = None,
|
||||
transform_list: Optional[List[Transform]] = None,
|
||||
) -> Tuple[Base, str, Optional[List[Transform]]]:
|
||||
"""
|
||||
Traverses Speckle object hierarchies to yield `Base` objects and their transformations.
|
||||
Tailored to Speckle's AEC data structures, it covers the newer hierarchical structures
|
||||
with Collections and also with patterns found in older Revit specific data.
|
||||
|
||||
Parameters:
|
||||
- base (Base): The starting point `Base` object for traversal.
|
||||
- inherited_instance_id (str, optional): The inherited identifier for `Base` objects without a unique ID.
|
||||
- transform_list (List[Transform], optional): Accumulated list of transformations from parent to child objects.
|
||||
|
||||
Yields:
|
||||
- tuple: A `Base` object, its identifier, and a list of applicable `Transform` objects or None.
|
||||
|
||||
The id of the `Base` object is either the inherited identifier for a definition from an instance
|
||||
or the one defined in the object.
|
||||
"""
|
||||
# Derive the identifier for the current `Base` object, defaulting to an inherited one if needed.
|
||||
current_id = getattr(base, "id", inherited_instance_id)
|
||||
transform_list = transform_list or []
|
||||
|
||||
if isinstance(base, Instance):
|
||||
# Append transformation data and dive into the definition of `Instance` objects.
|
||||
if base.transform:
|
||||
transform_list.append(base.transform)
|
||||
if base.definition:
|
||||
yield from extract_base_and_transform(
|
||||
base.definition, current_id, transform_list.copy()
|
||||
)
|
||||
else:
|
||||
# Initial yield for the current `Base` object.
|
||||
yield base, current_id, transform_list
|
||||
|
||||
# Process 'elements' and '@elements', typical containers for `Base` objects in AEC models.
|
||||
elements_attr = getattr(base, "elements", []) or getattr(base, "@elements", [])
|
||||
for element in elements_attr:
|
||||
if isinstance(element, Base):
|
||||
# Recurse into each `Base` object within 'elements' or '@elements'.
|
||||
yield from extract_base_and_transform(
|
||||
element, current_id, transform_list.copy()
|
||||
)
|
||||
|
||||
# Recursively process '@'-prefixed properties that are Base objects with 'elements'.
|
||||
# This is a common pattern in older Speckle data models, such as those used for Revit commits.
|
||||
for attr_name in dir(base):
|
||||
if attr_name.startswith("@"):
|
||||
attr_value = getattr(base, attr_name)
|
||||
# If the attribute is a Base object containing 'elements', recurse into it.
|
||||
if isinstance(attr_value, Base) and hasattr(attr_value, "elements"):
|
||||
yield from extract_base_and_transform(
|
||||
attr_value, current_id, transform_list.copy()
|
||||
)
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
"""Helper module for a simple speckle object tree flattening."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
from specklepy.objects import Base
|
||||
|
||||
|
||||
def flatten_base(base: Base) -> Iterable[Base]:
|
||||
"""Take a base and flatten it to an iterable of bases."""
|
||||
if hasattr(base, "elements"):
|
||||
for element in base["elements"]:
|
||||
yield from flatten_base(element)
|
||||
yield base
|
||||
@@ -10,7 +10,7 @@ from speckle_automate import (
|
||||
execute_automate_function,
|
||||
)
|
||||
|
||||
from flatten import flatten_base
|
||||
from Utilities.flatten import flatten_base
|
||||
|
||||
|
||||
class FunctionInputs(AutomateBase):
|
||||
|
||||
Reference in New Issue
Block a user