Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88b17db901 | |||
| 405972f681 | |||
| 0fbfff54d4 | |||
| 826dadc8c8 | |||
| b9e4ee2b23 | |||
| 78c55b787f | |||
| 34f2dc2ab6 | |||
| a658e12cda | |||
| 85aa938fc2 | |||
| 010fb83ea6 | |||
| 7a291ce2f6 | |||
| 989c975c86 | |||
| 516eff4d8b | |||
| 0650210601 | |||
| b0b8140363 | |||
| d25f30b20d | |||
| b4e2f37b7f | |||
| b7ba2196f3 | |||
| 17cbcc38ba | |||
| 9afb2c5c1c | |||
| eb13c9bc70 | |||
| a33588f3af | |||
| 970cf62e50 | |||
| 513594c49f | |||
| 37c8e6dfb1 | |||
| 3859a88c4b | |||
| dfa8fc99d9 | |||
| ee97f3b718 | |||
| e0b48f6123 | |||
| 6fb6418d16 | |||
| ce104adb50 | |||
| fe0a8eb9f5 | |||
| 6279dd3885 | |||
| 811c5843a9 | |||
| 035cd089e2 | |||
| 6daef049bb | |||
| d526c8ce3e | |||
| 4c91032718 | |||
| ffb80457bc | |||
| d380e6eaf8 | |||
| ace7c390c1 | |||
| c052dfad46 | |||
| 66802726b9 | |||
| b8f4150fb7 | |||
| 255133010f | |||
| aea9bb3e1d | |||
| 5ca5334730 | |||
| ba5f40a749 | |||
| 04fc0fa715 | |||
| 2e80646d2c | |||
| fe6c18e97b | |||
| 7c9058172f | |||
| a82187589f | |||
| d811b010ff | |||
| e1e5d9dbb6 | |||
| b17423b282 | |||
| 166b0f5e87 | |||
| cac34120a9 | |||
| 55c4c68cf3 | |||
| be850d5ea9 | |||
| c9a5badac1 | |||
| 118fa07e37 | |||
| d71b616e2b | |||
| 35750f12c5 | |||
| 5730cdcb43 | |||
| 82b6dbbe78 | |||
| 883be4b27b | |||
| 37e2711a76 | |||
| 8dcc67fb31 | |||
| ed84820995 | |||
| 5c3dcb7bc0 | |||
| 92732e3c76 | |||
| 903951547d | |||
| 82c3dc9ffb | |||
| a0e10aae99 | |||
| bbea2a0d76 | |||
| a05ac3479b | |||
| 0bd972945e | |||
| f200544065 | |||
| 68ce9823ae | |||
| 24bfb6718e | |||
| e63f4b8636 | |||
| 47c6bd89af |
+7
-100
@@ -1,108 +1,15 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
codecov: codecov/codecov@3.3.0
|
||||
|
||||
# Define the jobs we want to run for this project
|
||||
jobs:
|
||||
pre-commit:
|
||||
parameters:
|
||||
config_file:
|
||||
default: ./.pre-commit-config.yaml
|
||||
description: Optional, path to pre-commit config file.
|
||||
type: string
|
||||
cache_prefix:
|
||||
default: ''
|
||||
description: |
|
||||
Optional cache prefix to be used on CircleCI. Can be used for cache busting or to ensure multiple jobs use different caches.
|
||||
type: string
|
||||
build:
|
||||
docker:
|
||||
- image: speckle/pre-commit-runner:latest
|
||||
resource_class: medium
|
||||
- image: cimg/base:2023.03
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cache-pre-commit-<<parameters.cache_prefix>>-{{ checksum "<<parameters.config_file>>" }}
|
||||
- run:
|
||||
name: Install pre-commit hooks
|
||||
command: pre-commit install-hooks --config <<parameters.config_file>>
|
||||
- save_cache:
|
||||
key: cache-pre-commit-<<parameters.cache_prefix>>-{{ checksum "<<parameters.config_file>>" }}
|
||||
paths:
|
||||
- ~/.cache/pre-commit
|
||||
- run:
|
||||
name: Run pre-commit
|
||||
command: pre-commit run --all-files
|
||||
- run:
|
||||
command: git --no-pager diff
|
||||
name: git diff
|
||||
when: on_fail
|
||||
|
||||
test:
|
||||
machine:
|
||||
image: ubuntu-2204:2023.02.1
|
||||
docker_layer_caching: false
|
||||
resource_class: medium
|
||||
parameters:
|
||||
tag:
|
||||
default: "3.11"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install python
|
||||
command: |
|
||||
pyenv install -s << parameters.tag >>
|
||||
pyenv global << parameters.tag >>
|
||||
- run:
|
||||
name: Startup the Speckle Server
|
||||
command: docker compose -f docker-compose.yml up -d
|
||||
- run:
|
||||
name: Install Poetry
|
||||
command: |
|
||||
pip install poetry
|
||||
- run:
|
||||
name: Install packages
|
||||
command: poetry install
|
||||
- run:
|
||||
name: Run tests
|
||||
command: poetry run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
- store_test_results:
|
||||
path: reports
|
||||
- store_artifacts:
|
||||
path: reports
|
||||
- codecov/upload
|
||||
|
||||
deploy:
|
||||
docker:
|
||||
- image: "cimg/python:3.8"
|
||||
steps:
|
||||
- checkout
|
||||
- run: python patch_version.py $CIRCLE_TAG
|
||||
- run: poetry build
|
||||
- run: poetry publish -u __token__ -p $PYPI_TOKEN
|
||||
- run: echo "so long and thanks for all the fish"
|
||||
|
||||
# Orchestrate our job run sequence
|
||||
workflows:
|
||||
main:
|
||||
build_and_test:
|
||||
jobs:
|
||||
- pre-commit:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test:
|
||||
matrix:
|
||||
parameters:
|
||||
tag: ["3.11"]
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- deploy:
|
||||
context: pypi
|
||||
requires:
|
||||
- pre-commit
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/ # For testing only! /ci\/.*/
|
||||
- build
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: "Specklepy test and build"
|
||||
on:
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - 'v3-dev'
|
||||
push:
|
||||
branches:
|
||||
- "gergo/uvSetup"
|
||||
jobs:
|
||||
ci:
|
||||
name: continuous-integration
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit/
|
||||
key: ${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
||||
- name: Run pre-commit
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
# - name: Run Speckle Server
|
||||
# run: docker compose up -d
|
||||
|
||||
# - name: Run tests
|
||||
# run: uv run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
|
||||
# - uses: codecov/codecov-action@v5
|
||||
# if: matrix.python-version == 3.13
|
||||
# with:
|
||||
# fail_ci_if_error: true # optional (default = false)
|
||||
# files: ./reports/test-results.xml # optional
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Minimize uv cache
|
||||
run: uv cache prune --ci
|
||||
@@ -0,0 +1,36 @@
|
||||
# Publish a release to PyPI.
|
||||
# name: 'Publish to PyPI'
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - 'gergo/uvSetup'
|
||||
|
||||
# jobs:
|
||||
# pypi-publish:
|
||||
# name: Upload to PyPI
|
||||
# runs-on: ubuntu-latest
|
||||
# environment:
|
||||
# name: release
|
||||
# permissions:
|
||||
# # For PyPI's trusted publishing.
|
||||
# id-token: write
|
||||
# steps:
|
||||
# - name: 'Install uv'
|
||||
# uses: astral-sh/setup-uv@v5
|
||||
# - uses: actions/checkout@v4
|
||||
# with:
|
||||
# # This is necessary so that we have the tags.
|
||||
# fetch-depth: 0
|
||||
# - uses: mtkennerly/dunamai-action@v1
|
||||
# with:
|
||||
# env-var: MY_VERSION
|
||||
# args: --style semver
|
||||
# - run: echo $MY_VERSION
|
||||
# - name: 'Build artifacts'
|
||||
# run: uv build
|
||||
# - name: Publish to PyPi
|
||||
# run: uv publish --publish-url https://test.pypi.org/simple/
|
||||
|
||||
# - name: Test package install
|
||||
# run: uv run --with specklepy --no-project -- python -c "import specklepy"
|
||||
+15
-17
@@ -1,33 +1,31 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
- repo: local
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
rev: v0.1.6
|
||||
name: ruff lint
|
||||
entry: uv run ruff check --force-exclude
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
name: ruff format
|
||||
entry: uv run ruff format --force-exclude
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
|
||||
|
||||
- repo: https://github.com/commitizen-tools/commitizen
|
||||
hooks:
|
||||
- id: commitizen
|
||||
- id: commitizen-branch
|
||||
stages:
|
||||
- push
|
||||
- pre-push
|
||||
rev: v3.13.0
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
# It is recommended to specify the latest version of Python
|
||||
# supported by your project here, or alternatively use
|
||||
# pre-commit's default_language_version, see
|
||||
# https://pre-commit.com/#top_level-default_language_version
|
||||
# language_version: python3.11
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
Vendored
+3
-5
@@ -4,11 +4,9 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
@@ -16,9 +14,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Pytest",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "pytest",
|
||||
"module": "pytest",
|
||||
"args": [],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true
|
||||
|
||||
@@ -25,25 +25,25 @@ Head to the [**📚 specklepy docs**](https://speckle.guide/dev/python.html) for
|
||||
|
||||
### Installation
|
||||
|
||||
This project uses python-poetry for dependency management, make sure you follow the official [docs](https://python-poetry.org/docs/#installation) to get poetry.
|
||||
This project uses uv for dependency management, make sure you follow the official [docs](https://docs.astral.sh/uv/) to get it.
|
||||
|
||||
To bootstrap the project environment run `$ poetry install`. This will create a new virtual-env for the project and install both the package and dev dependencies.
|
||||
To create a new virtual environment with uv run `$ uv venv` and follow the instructions on the screen to activate the virtual environment.
|
||||
To bootstrap the project environment run `$ uv sync`. This will install both the package and dev dependencies.
|
||||
|
||||
If this is your first time using poetry and you're used to creating your venvs within the project directory, run `poetry config virtualenvs.in-project true` to configure poetry to do the same.
|
||||
To execute any python script run `$ uv run python my_script.py`
|
||||
|
||||
To execute any python script run `$ poetry run python my_script.py`
|
||||
|
||||
> Alternatively you may roll your own virtual-env with either venv, virtualenv, pyenv-virtualenv etc. Poetry will play along an recognize if it is invoked from inside a virtual environment.
|
||||
> Alternatively you may roll your own virtual-env with either venv, virtualenv, pyenv-virtualenv etc. Uv will play along an recognize if it is invoked from inside a virtual environment.
|
||||
|
||||
### Style guide
|
||||
|
||||
All our repo wide styling linting and other rules are checked and enforced by `pre-commit`, which is included in the dev dependencies.
|
||||
All our repo wide styling linting and other rules are checked and enforced by `pre-commit`, which is included in the dev dependencies.
|
||||
It is recommended to set up `pre-commit` after installing the dependencies by running `$ pre-commit install`.
|
||||
Commiting code that doesn't adhere to the given rules, will fail the checks in our CI system.
|
||||
|
||||
### Local Data Paths
|
||||
|
||||
It may be helpful to know where the local accounts and object cache dbs are stored. Depending on on your OS, you can find the dbs at:
|
||||
|
||||
- Windows: `APPDATA` or `<USER>\AppData\Roaming\Speckle`
|
||||
- Linux: `$XDG_DATA_HOME` or by default `~/.local/share/Speckle`
|
||||
- Mac: `~/.config/Speckle`
|
||||
|
||||
+1
-7
@@ -6,7 +6,7 @@ services:
|
||||
# Speckle Server dependencies
|
||||
#######
|
||||
postgres:
|
||||
image: "postgres:14.5-alpine"
|
||||
image: "postgres:16-alpine"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: speckle
|
||||
@@ -53,12 +53,6 @@ services:
|
||||
# Speckle Server
|
||||
#######
|
||||
|
||||
speckle-frontend:
|
||||
image: speckle/speckle-frontend-2:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "0.0.0.0:8080:8080"
|
||||
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
restart: always
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.objects.geometry import Base
|
||||
from specklepy.objects.units import Units
|
||||
from specklepy.objects_v2.geometry import Base
|
||||
from specklepy.objects_v2.units import Units
|
||||
|
||||
dct = {
|
||||
"id": "1234abcd",
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def patch(tag):
|
||||
print(f"Patching version: {tag}")
|
||||
|
||||
with open("pyproject.toml", "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
if "version" not in lines[2]:
|
||||
raise Exception("Invalid pyproject.toml. Could not patch version.")
|
||||
|
||||
lines[2] = f'version = "{tag}"\n'
|
||||
with open("pyproject.toml", "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
return
|
||||
|
||||
tag = sys.argv[1]
|
||||
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
|
||||
raise ValueError(f"Invalid tag provided: {tag}")
|
||||
|
||||
patch(tag)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Generated
-2040
File diff suppressed because it is too large
Load Diff
+56
-61
@@ -1,75 +1,70 @@
|
||||
[tool.poetry]
|
||||
[project]
|
||||
dynamic = ["version"]
|
||||
name = "specklepy"
|
||||
version = "2.17.14"
|
||||
description = "The Python SDK for Speckle 2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Speckle Systems <devops@speckle.systems>"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/specklesystems/speckle-py"
|
||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||
homepage = "https://speckle.systems/"
|
||||
packages = [
|
||||
{ include = "specklepy", from = "src" },
|
||||
{ include = "speckle_automate", from = "src" },
|
||||
authors = [{ name = "Speckle Systems", email = "devops@speckle.systems" }]
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.10.0, <4.0"
|
||||
dependencies = [
|
||||
"appdirs>=1.4.4",
|
||||
"attrs>=24.3.0",
|
||||
"deprecated>=1.2.15",
|
||||
"gql[requests,websockets]>=3.5.0",
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.10.5",
|
||||
"pydantic-settings>=2.7.1",
|
||||
"stringcase>=1.2.0",
|
||||
"ujson>=5.10.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"commitizen>=4.1.0",
|
||||
"devtools>=0.12.2",
|
||||
"pre-commit>=4.0.1",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-asyncio>=0.25.2",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-ordering>=0.6",
|
||||
"ruff>=0.9.2",
|
||||
"types-deprecated>=1.2.15.20241117",
|
||||
"types-requests>=2.32.0.20241016",
|
||||
"types-ujson>=5.10.0.20240515",
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8.0, <4.0"
|
||||
pydantic = "^2.5"
|
||||
appdirs = "^1.4.4"
|
||||
gql = { extras = ["requests", "websockets"], version = "^3.3.0" }
|
||||
ujson = "^5.3.0"
|
||||
Deprecated = "^1.2.13"
|
||||
stringcase = "^1.2.0"
|
||||
attrs = "^23.1.0"
|
||||
httpx = "^0.25.0"
|
||||
[project.urls]
|
||||
repository = "https://github.com/specklesystems/specklepy"
|
||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||
homepage = "https://speckle.systems/"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "23.11.0"
|
||||
isort = "^5.7.0"
|
||||
pytest = "^7.1.3"
|
||||
pytest-asyncio = "^0.23.0"
|
||||
pytest-ordering = "^0.6"
|
||||
pytest-cov = "^3.0.0"
|
||||
devtools = "^0.8.0"
|
||||
pylint = "^2.14.4"
|
||||
pydantic-settings = "^2.3.0"
|
||||
mypy = "^0.982"
|
||||
pre-commit = "^2.20.0"
|
||||
commitizen = "^2.38.0"
|
||||
ruff = "^0.4.4"
|
||||
types-deprecated = "^1.2.9"
|
||||
types-ujson = "^5.6.0.0"
|
||||
types-requests = "^2.28.11.5"
|
||||
|
||||
[tool.black]
|
||||
exclude = '''
|
||||
/(
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
include = '\.pyi?$'
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39", "py310", "py311"]
|
||||
[build-system]
|
||||
requires = ["setuptools>=64", "setuptools-scm>=8"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "2.9.2"
|
||||
tag_format = "$version"
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
[tool.ruff]
|
||||
exclude = [".venv", "**/*.yml"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# pycodestyle
|
||||
"E",
|
||||
# Pyflakes
|
||||
"F",
|
||||
# pyupgrade
|
||||
"UP",
|
||||
# flake8-bugbear
|
||||
"B",
|
||||
# flake8-simplify
|
||||
"SIM",
|
||||
# isort
|
||||
"I",
|
||||
]
|
||||
ignore = ["UP006", "UP007", "UP035"]
|
||||
|
||||
@@ -96,13 +96,23 @@ class AutomationContext:
|
||||
|
||||
def receive_version(self) -> Base:
|
||||
"""Receive the Speckle project version that triggered this automation run."""
|
||||
# TODO: this is a quick hack to keep implementation consistency. Move to proper receive many versions
|
||||
# TODO: this is a quick hack to keep implementation consistency.
|
||||
# Move to proper receive many versions
|
||||
version_id = self.automation_run_data.triggers[0].payload.version_id
|
||||
commit = self.speckle_client.commit.get(
|
||||
self.automation_run_data.project_id, version_id
|
||||
)
|
||||
if not commit.referencedObject:
|
||||
raise ValueError("The commit has no referencedObject, cannot receive it.")
|
||||
if not commit or not commit.referencedObject:
|
||||
raise ValueError(
|
||||
f"""\
|
||||
Could not receive specified version.
|
||||
{"The commit has no referencedObject." if not commit.referencedObject else ""}
|
||||
Is your environment configured correctly?
|
||||
project_id: {self.automation_run_data.project_id}
|
||||
model_id: {self.automation_run_data.triggers[0].payload.model_id}
|
||||
version_id: {self.automation_run_data.triggers[0].payload.version_id}
|
||||
"""
|
||||
)
|
||||
base = operations.receive(
|
||||
commit.referencedObject, self._server_transport, self._memory_transport
|
||||
)
|
||||
@@ -264,7 +274,8 @@ class AutomationContext:
|
||||
|
||||
if not path_obj.exists():
|
||||
raise ValueError("The given file path doesn't exist")
|
||||
files = {path_obj.name: open(str(path_obj), "rb")}
|
||||
|
||||
files = {path_obj.name: path_obj.open("rb")}
|
||||
|
||||
url = (
|
||||
f"{self.automation_run_data.speckle_server_url}api/stream/"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Some useful helpers for working with automation data."""
|
||||
|
||||
import secrets
|
||||
import string
|
||||
|
||||
|
||||
@@ -61,15 +61,13 @@ def _parse_input_data(
|
||||
def execute_automate_function(
|
||||
automate_function: AutomateFunction[T],
|
||||
input_schema: type[T],
|
||||
) -> None:
|
||||
...
|
||||
) -> None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def execute_automate_function(
|
||||
automate_function: AutomateFunctionWithoutInputs,
|
||||
) -> None:
|
||||
...
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class AutomateGenerateJsonSchema(GenerateJsonSchema):
|
||||
@@ -130,7 +128,8 @@ def execute_automate_function(
|
||||
automate_function, # type: ignore
|
||||
)
|
||||
|
||||
# if we've gotten this far, the execution should technically be completed as expected
|
||||
# if we've gotten this far,
|
||||
# the execution should technically be completed as expected
|
||||
# thus exiting with 0 is the schemantically correct thing to do
|
||||
exit_code = (
|
||||
1 if automation_context.run_status == AutomationStatus.EXCEPTION else 0
|
||||
@@ -146,16 +145,14 @@ def run_function(
|
||||
automation_context: AutomationContext,
|
||||
automate_function: AutomateFunction[T],
|
||||
inputs: T,
|
||||
) -> AutomationContext:
|
||||
...
|
||||
) -> AutomationContext: ...
|
||||
|
||||
|
||||
@overload
|
||||
def run_function(
|
||||
automation_context: AutomationContext,
|
||||
automate_function: AutomateFunctionWithoutInputs,
|
||||
) -> AutomationContext:
|
||||
...
|
||||
) -> AutomationContext: ...
|
||||
|
||||
|
||||
def run_function(
|
||||
@@ -194,4 +191,4 @@ def run_function(
|
||||
if not automation_context.context_view:
|
||||
automation_context.set_context_view()
|
||||
automation_context.report_run_status()
|
||||
return automation_context
|
||||
return automation_context
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from specklepy import objects
|
||||
# from specklepy import objects
|
||||
|
||||
__all__ = ["objects"]
|
||||
# __all__ = ["objects"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import contextlib
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.credentials import Account
|
||||
@@ -7,12 +9,12 @@ from specklepy.api.resources import (
|
||||
OtherUserResource,
|
||||
ProjectInviteResource,
|
||||
ProjectResource,
|
||||
ServerResource,
|
||||
SubscriptionResource,
|
||||
VersionResource,
|
||||
branch,
|
||||
commit,
|
||||
object,
|
||||
server,
|
||||
stream,
|
||||
subscriptions,
|
||||
user,
|
||||
@@ -40,7 +42,8 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with an account (account has been added in Speckle Manager)
|
||||
# authenticate the client with an account
|
||||
# (account has been added in Speckle Manager)
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
@@ -69,15 +72,14 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
self.account = Account()
|
||||
|
||||
def _init_resources(self) -> None:
|
||||
self.server = server.Resource(
|
||||
self.server = ServerResource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
|
||||
server_version = None
|
||||
try:
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
server_version = self.server.version()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.other_user = OtherUserResource(
|
||||
account=self.account,
|
||||
|
||||
@@ -2,8 +2,11 @@ from typing import List, Optional
|
||||
|
||||
# following imports seem to be unnecessary, but they need to stay
|
||||
# to not break the scripts using these functions as non-core
|
||||
from specklepy.core.api.credentials import StreamWrapper # noqa: F401
|
||||
from specklepy.core.api.credentials import Account, UserInfo # noqa: F401
|
||||
from specklepy.core.api.credentials import ( # noqa: F401
|
||||
Account,
|
||||
StreamWrapper, # noqa: F401
|
||||
UserInfo,
|
||||
)
|
||||
from specklepy.core.api.credentials import (
|
||||
get_account_from_token as core_get_account_from_token,
|
||||
)
|
||||
|
||||
@@ -53,7 +53,9 @@ def receive(
|
||||
return _untracked_receive(obj_id, remote_transport, local_transport)
|
||||
|
||||
|
||||
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
|
||||
def serialize(
|
||||
base: Base, write_transports: List[AbstractTransport] | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Serialize a base object. If no write transports are provided,
|
||||
the object will be serialized
|
||||
@@ -67,6 +69,8 @@ def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str
|
||||
Returns:
|
||||
str -- the serialized object
|
||||
"""
|
||||
if not write_transports:
|
||||
write_transports = []
|
||||
metrics.track(metrics.SDK, custom_props={"name": "Serialize"})
|
||||
return core_serialize(base, write_transports)
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ from typing import List, Optional, overload
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.core.api.inputs.project_inputs import UserProjectsFilter
|
||||
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.models import (
|
||||
PendingStreamCollaborator,
|
||||
Project,
|
||||
@@ -44,12 +43,10 @@ class ActiveUserResource(CoreResource):
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
) -> User:
|
||||
...
|
||||
) -> User: ...
|
||||
|
||||
@overload
|
||||
def update(self, *, input: UserUpdateInput) -> User:
|
||||
...
|
||||
def update(self, *, input: UserUpdateInput) -> User: ...
|
||||
|
||||
def update(
|
||||
self,
|
||||
@@ -141,7 +138,8 @@ class ActiveUserResource(CoreResource):
|
||||
token (Optional[str]): The token of the invite to look for (optional).
|
||||
|
||||
Returns:
|
||||
Optional[PendingStreamCollaborator]: The invite for the given stream, or None if not found.
|
||||
Optional[PendingStreamCollaborator]: The invite for the given stream,
|
||||
or None if not found.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Active Invite Get"})
|
||||
return super().get_pending_invite(stream_id, token)
|
||||
|
||||
@@ -20,8 +20,10 @@ from specklepy.logging.exceptions import SpeckleException
|
||||
class OtherUserResource(CoreResource):
|
||||
"""
|
||||
Provides API access to other users' profiles and activities on the platform.
|
||||
This class enables fetching limited information about users, searching for users by name or email,
|
||||
and accessing user activity logs with appropriate privacy and access control measures in place.
|
||||
This class enables fetching limited information about users,
|
||||
searching for users by name or email,
|
||||
and accessing user activity logs with appropriate privacy
|
||||
and access control measures in place.
|
||||
"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
@@ -64,7 +66,8 @@ class OtherUserResource(CoreResource):
|
||||
limit (int): Maximum number of search results to return.
|
||||
|
||||
Returns:
|
||||
Union[List[LimitedUser], SpeckleException]: A list of users matching the search
|
||||
Union[List[LimitedUser], SpeckleException]:
|
||||
A list of users matching the search
|
||||
query or an exception if the query is too short.
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
@@ -86,8 +89,8 @@ class OtherUserResource(CoreResource):
|
||||
cursor: Optional[datetime] = None,
|
||||
) -> ActivityCollection:
|
||||
"""
|
||||
Retrieves a collection of activities for a specified user, with optional filters for activity type,
|
||||
time frame, and pagination.
|
||||
Retrieves a collection of activities for a specified user,
|
||||
with optional filters for activity type, time frame, and pagination.
|
||||
|
||||
Args:
|
||||
user_id (str): The ID of the user whose activities are being requested.
|
||||
@@ -98,7 +101,8 @@ class OtherUserResource(CoreResource):
|
||||
cursor (Optional[datetime]): Timestamp for pagination cursor.
|
||||
|
||||
Returns:
|
||||
ActivityCollection: A collection of user activities filtered according to specified criteria.
|
||||
ActivityCollection: A collection of user activities filtered
|
||||
according to specified criteria.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"})
|
||||
return super().activity(user_id, limit, action_type, before, after, cursor)
|
||||
|
||||
@@ -31,7 +31,8 @@ class ServerResource(CoreResource):
|
||||
the server version in the format (major, minor, patch, (tag, build))
|
||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||
"""
|
||||
# not tracking as it will be called along with other mutations / queries as a check
|
||||
# not tracking as it will be called along with other
|
||||
# mutations / queries as a check
|
||||
return super().version()
|
||||
|
||||
def apps(self) -> Dict:
|
||||
|
||||
@@ -4,6 +4,6 @@ from specklepy.api.resources import ServerResource
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
|
||||
|
||||
@deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION)
|
||||
@deprecated(reason="Renamed to ServerResource", version=FE1_DEPRECATION_VERSION)
|
||||
class Resource(ServerResource):
|
||||
"""Renamed to ServerResource"""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import contextlib
|
||||
import re
|
||||
from typing import Dict
|
||||
from warnings import warn
|
||||
@@ -49,7 +50,8 @@ class SpeckleClient:
|
||||
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with an account (account has been added in Speckle Manager)
|
||||
# authenticate the client with an account
|
||||
# (account has been added in Speckle Manager)
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
@@ -102,7 +104,8 @@ class SpeckleClient:
|
||||
|
||||
self._init_resources()
|
||||
|
||||
# ? Check compatibility with the server - i think we can skip this at this point? save a request
|
||||
# ? Check compatibility with the server
|
||||
# - i think we can skip this at this point? save a request
|
||||
# try:
|
||||
# server_info = self.server.get()
|
||||
# if isinstance(server_info, Exception):
|
||||
@@ -187,9 +190,10 @@ class SpeckleClient:
|
||||
if ex.exception.code == 403:
|
||||
warn(
|
||||
SpeckleWarning(
|
||||
"Possibly invalid token - could not authenticate Speckle Client"
|
||||
f" for server {self.url}"
|
||||
)
|
||||
"Possibly invalid token - could not authenticate "
|
||||
f"Speckle Client for server {self.url}"
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
raise ex
|
||||
@@ -203,10 +207,8 @@ class SpeckleClient:
|
||||
)
|
||||
|
||||
server_version = None
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
server_version = self.server.version()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.other_user = OtherUserResource(
|
||||
account=self.account,
|
||||
@@ -283,7 +285,7 @@ class SpeckleClient:
|
||||
return attr.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
except AttributeError:
|
||||
except AttributeError as ex:
|
||||
raise SpeckleException(
|
||||
f"Method {name} is not supported by the SpeckleClient class"
|
||||
)
|
||||
) from ex
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from specklepy.core.api.inputs.model_inputs import (
|
||||
CreateModelInput,
|
||||
DeleteModelInput,
|
||||
ModelVersionsFilter,
|
||||
UpdateModelInput,
|
||||
)
|
||||
from specklepy.core.api.inputs.project_inputs import (
|
||||
ProjectCreateInput,
|
||||
ProjectInviteCreateInput,
|
||||
ProjectInviteUseInput,
|
||||
ProjectModelsFilter,
|
||||
ProjectUpdateInput,
|
||||
ProjectUpdateRoleInput,
|
||||
)
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.inputs.version_inputs import (
|
||||
CreateVersionInput,
|
||||
DeleteVersionsInput,
|
||||
MarkReceivedVersionInput,
|
||||
MoveVersionsInput,
|
||||
UpdateVersionInput,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CreateModelInput",
|
||||
"DeleteModelInput",
|
||||
"UpdateModelInput",
|
||||
"ModelVersionsFilter",
|
||||
"ProjectCreateInput",
|
||||
"ProjectInviteCreateInput",
|
||||
"ProjectInviteUseInput",
|
||||
"ProjectModelsFilter",
|
||||
"ProjectUpdateInput",
|
||||
"ProjectUpdateRoleInput",
|
||||
"UserProjectsFilter",
|
||||
"UserUpdateInput",
|
||||
"UpdateVersionInput",
|
||||
"MoveVersionsInput",
|
||||
"DeleteVersionsInput",
|
||||
"CreateVersionInput",
|
||||
"MarkReceivedVersionInput",
|
||||
]
|
||||
@@ -45,8 +45,3 @@ class ProjectUpdateRoleInput(BaseModel):
|
||||
userId: str
|
||||
projectId: str
|
||||
role: Optional[str]
|
||||
|
||||
|
||||
class UserProjectsFilter(BaseModel):
|
||||
search: str
|
||||
onlyWithRoles: Optional[Sequence[str]] = None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -8,3 +8,8 @@ class UserUpdateInput(BaseModel):
|
||||
bio: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class UserProjectsFilter(BaseModel):
|
||||
search: str
|
||||
onlyWithRoles: Optional[Sequence[str]] = None
|
||||
|
||||
@@ -55,7 +55,8 @@ class ServerConfiguration(BaseModel):
|
||||
objectSizeLimitBytes: int
|
||||
|
||||
|
||||
# Keeping this one all Optionals at the minute, because its used both as a deserialization model for GQL and Account Management
|
||||
# Keeping this one all Optionals at the minute,
|
||||
# because its used both as a deserialization model for GQL and Account Management
|
||||
class ServerInfo(BaseModel):
|
||||
name: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
@@ -126,7 +127,7 @@ class Version(BaseModel):
|
||||
|
||||
|
||||
class Model(BaseModel):
|
||||
author: LimitedUser
|
||||
author: Optional[LimitedUser]
|
||||
createdAt: datetime
|
||||
description: Optional[str]
|
||||
displayName: str
|
||||
|
||||
@@ -4,7 +4,10 @@ from typing import List, Optional
|
||||
from deprecated import deprecated
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
FE1_DEPRECATION_REASON = "Stream/Branch/Commit API is now deprecated, Use the new Project/Model/Version API functions in Client}"
|
||||
FE1_DEPRECATION_REASON = (
|
||||
"Stream/Branch/Commit API is now deprecated, "
|
||||
"Use the new Project/Model/Version API functions in Client"
|
||||
)
|
||||
FE1_DEPRECATION_VERSION = "2.20"
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ def receive(
|
||||
|
||||
serializer = BaseObjectSerializer(read_transport=local_transport)
|
||||
|
||||
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialization using the local transport
|
||||
# try local transport first. if the parent is there, we assume all the children
|
||||
# are there and continue with deserialization using the local transport
|
||||
obj_string = local_transport.get_object(obj_id)
|
||||
if obj_string:
|
||||
return serializer.read_json(obj_string=obj_string)
|
||||
@@ -90,7 +91,9 @@ def receive(
|
||||
return serializer.read_json(obj_string=obj_string)
|
||||
|
||||
|
||||
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
|
||||
def serialize(
|
||||
base: Base, write_transports: List[AbstractTransport] | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Serialize a base object. If no write transports are provided,
|
||||
the object will be serialized
|
||||
@@ -104,6 +107,8 @@ def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str
|
||||
Returns:
|
||||
str -- the serialized object
|
||||
"""
|
||||
if not write_transports:
|
||||
write_transports = []
|
||||
serializer = BaseObjectSerializer(write_transports=write_transports)
|
||||
|
||||
return serializer.write_json(base)[1]
|
||||
|
||||
@@ -18,7 +18,7 @@ from specklepy.transports.sqlite import SQLiteTransport
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
class ResourceBase:
|
||||
def __init__(
|
||||
self,
|
||||
account: Account,
|
||||
@@ -101,7 +101,8 @@ class ResourceBase(object):
|
||||
parse_response: bool = True,
|
||||
) -> Any:
|
||||
"""Executes the GraphQL query"""
|
||||
# This method has quite complex and ambiguous typing, and counter-intuitive error handling
|
||||
# This method has quite complex and ambiguous typing,
|
||||
# and counter-intuitive error handling
|
||||
# We are going to phase it out in favour of `make_request_and_parse_response`
|
||||
try:
|
||||
with self.__lock:
|
||||
|
||||
@@ -4,8 +4,7 @@ from typing import List, Optional, overload
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.inputs.project_inputs import UserProjectsFilter
|
||||
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
PendingStreamCollaborator,
|
||||
@@ -38,10 +37,12 @@ class ActiveUserResource(ResourceBase):
|
||||
self.schema = User
|
||||
|
||||
def get(self) -> Optional[User]:
|
||||
"""Gets the currently active user profile (as extracted from the authorization header)
|
||||
"""Gets the currently active user profile
|
||||
(as extracted from the authorization header)
|
||||
|
||||
Returns:
|
||||
User -- the requested user, or none if no authentication token is provided to the Client
|
||||
User -- the requested user, or none if no authentication token
|
||||
is provided to the Client
|
||||
"""
|
||||
QUERY = gql(
|
||||
"""
|
||||
@@ -100,12 +101,10 @@ class ActiveUserResource(ResourceBase):
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
) -> User:
|
||||
...
|
||||
) -> User: ...
|
||||
|
||||
@overload
|
||||
def update(self, *, input: UserUpdateInput) -> User:
|
||||
...
|
||||
def update(self, *, input: UserUpdateInput) -> User: ...
|
||||
|
||||
def update(
|
||||
self,
|
||||
|
||||
@@ -76,14 +76,24 @@ class ModelResource(ResourceBase):
|
||||
) -> ModelWithVersions:
|
||||
QUERY = gql(
|
||||
"""
|
||||
query ModelGetWithVersions($modelId: String!, $projectId: String!, $versionsLimit: Int!, $versionsCursor: String, $versionsFilter: ModelVersionsFilter) {
|
||||
query ModelGetWithVersions(
|
||||
$modelId: String!,
|
||||
$projectId: String!,
|
||||
$versionsLimit: Int!,
|
||||
$versionsCursor: String,
|
||||
$versionsFilter: ModelVersionsFilter
|
||||
) {
|
||||
data:project(id: $projectId) {
|
||||
data:model(id: $modelId) {
|
||||
id
|
||||
name
|
||||
previewUrl
|
||||
updatedAt
|
||||
versions(limit: $versionsLimit, cursor: $versionsCursor, filter: $versionsFilter) {
|
||||
versions(
|
||||
limit: $versionsLimit,
|
||||
cursor: $versionsCursor,
|
||||
filter: $versionsFilter
|
||||
) {
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
@@ -127,9 +137,11 @@ class ModelResource(ResourceBase):
|
||||
"modelId": model_id,
|
||||
"versionsLimit": versions_limit,
|
||||
"versionsCursor": versions_cursor,
|
||||
"versionsFilter": versions_filter.model_dump(warnings="error")
|
||||
if versions_filter
|
||||
else None,
|
||||
"versionsFilter": (
|
||||
versions_filter.model_dump(warnings="error")
|
||||
if versions_filter
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
@@ -146,9 +158,18 @@ class ModelResource(ResourceBase):
|
||||
) -> ResourceCollection[Model]:
|
||||
QUERY = gql(
|
||||
"""
|
||||
query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) {
|
||||
query ProjectGetWithModels(
|
||||
$projectId: String!,
|
||||
$modelsLimit: Int!,
|
||||
$modelsCursor: String,
|
||||
$modelsFilter: ProjectModelsFilter
|
||||
) {
|
||||
data:project(id: $projectId) {
|
||||
data:models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) {
|
||||
data:models(
|
||||
limit: $modelsLimit,
|
||||
cursor: $modelsCursor,
|
||||
filter: $modelsFilter
|
||||
) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
@@ -179,9 +200,9 @@ class ModelResource(ResourceBase):
|
||||
"projectId": project_id,
|
||||
"modelsLimit": models_limit,
|
||||
"modelsCursor": models_cursor,
|
||||
"modelsFilter": models_filter.model_dump(warnings="error")
|
||||
if models_filter
|
||||
else None,
|
||||
"modelsFilter": (
|
||||
models_filter.model_dump(warnings="error") if models_filter else None
|
||||
),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -74,7 +74,9 @@ class OtherUserResource(ResourceBase):
|
||||
archived: bool = False,
|
||||
emailOnly: bool = False,
|
||||
) -> UserSearchResultCollection:
|
||||
"""Searches for a user on the server, by name or email. The search query must be at least
|
||||
"""
|
||||
Searches for a user on the server, by name or email.
|
||||
The search query must be at least
|
||||
3 characters long
|
||||
|
||||
Arguments:
|
||||
@@ -89,8 +91,20 @@ class OtherUserResource(ResourceBase):
|
||||
|
||||
QUERY = gql(
|
||||
"""
|
||||
query UserSearch($query: String!, $limit: Int!, $cursor: String, $archived: Boolean, $emailOnly: Boolean) {
|
||||
data:userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived, emailOnly: $emailOnly) {
|
||||
query UserSearch(
|
||||
$query: String!,
|
||||
$limit: Int!,
|
||||
$cursor: String,
|
||||
$archived: Boolean,
|
||||
$emailOnly: Boolean
|
||||
) {
|
||||
data:userSearch(
|
||||
query: $query,
|
||||
limit: $limit,
|
||||
cursor: $cursor,
|
||||
archived: $archived,
|
||||
emailOnly: $emailOnly
|
||||
) {
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
|
||||
@@ -37,7 +37,10 @@ class ProjectInviteResource(ResourceBase):
|
||||
) -> ProjectWithTeam:
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation ProjectInviteCreate($projectId: ID!, $input: ProjectInviteCreateInput!) {
|
||||
mutation ProjectInviteCreate(
|
||||
$projectId: ID!,
|
||||
$input: ProjectInviteCreateInput!
|
||||
) {
|
||||
data:projectMutations {
|
||||
data:invites {
|
||||
data:create(projectId: $projectId, input: $input) {
|
||||
|
||||
@@ -65,7 +65,12 @@ class ProjectResource(ResourceBase):
|
||||
) -> ProjectWithModels:
|
||||
QUERY = gql(
|
||||
"""
|
||||
query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) {
|
||||
query ProjectGetWithModels(
|
||||
$projectId: String!,
|
||||
$modelsLimit: Int!,
|
||||
$modelsCursor: String,
|
||||
$modelsFilter: ProjectModelsFilter
|
||||
) {
|
||||
data:project(id: $projectId) {
|
||||
id
|
||||
name
|
||||
@@ -77,7 +82,11 @@ class ProjectResource(ResourceBase):
|
||||
updatedAt
|
||||
sourceApps
|
||||
workspaceId
|
||||
models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) {
|
||||
models(
|
||||
limit: $modelsLimit,
|
||||
cursor: $modelsCursor,
|
||||
filter: $modelsFilter
|
||||
) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
@@ -108,9 +117,9 @@ class ProjectResource(ResourceBase):
|
||||
"projectId": project_id,
|
||||
"modelsLimit": models_limit,
|
||||
"modelsCursor": models_cursor,
|
||||
"modelsFilter": models_filter.model_dump(warnings="error")
|
||||
if models_filter
|
||||
else None,
|
||||
"modelsFilter": (
|
||||
models_filter.model_dump(warnings="error") if models_filter else None
|
||||
),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -80,7 +80,8 @@ class ServerResource(ResourceBase):
|
||||
the server version in the format (major, minor, patch, (tag, build))
|
||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||
"""
|
||||
# not tracking as it will be called along with other mutations / queries as a check
|
||||
# not tracking as it will be called along with other mutations / queries
|
||||
# as a check
|
||||
query = gql(
|
||||
"""
|
||||
query Server {
|
||||
|
||||
@@ -76,7 +76,13 @@ class VersionResource(ResourceBase):
|
||||
) -> ResourceCollection[Version]:
|
||||
QUERY = gql(
|
||||
"""
|
||||
query VersionGetVersions($projectId: String!, $modelId: String!, $limit: Int!, $cursor: String, $filter: ModelVersionsFilter) {
|
||||
query VersionGetVersions(
|
||||
$projectId: String!,
|
||||
$modelId: String!,
|
||||
$limit: Int!,
|
||||
$cursor: String,
|
||||
$filter: ModelVersionsFilter
|
||||
) {
|
||||
data:project(id: $projectId) {
|
||||
data:model(id: $modelId) {
|
||||
data:versions(limit: $limit, cursor: $cursor, filter: $filter) {
|
||||
|
||||
@@ -111,7 +111,7 @@ class Resource(ResourceBase):
|
||||
query = gql(
|
||||
"""
|
||||
query User($stream_limit: Int!) {
|
||||
user {
|
||||
activeUser {
|
||||
id
|
||||
bio
|
||||
name
|
||||
@@ -149,7 +149,7 @@ class Resource(ResourceBase):
|
||||
params = {"stream_limit": stream_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["user", "streams", "items"]
|
||||
query=query, params=params, return_type=["activeUser", "streams", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
|
||||
@@ -159,11 +159,12 @@ class StreamWrapper:
|
||||
try:
|
||||
self.branch_name = project["project"]["model"]["name"]
|
||||
except KeyError as ke:
|
||||
raise SpeckleException("Project model name is not found", ke)
|
||||
raise SpeckleException("Project model name is not found", ke) from ke
|
||||
|
||||
if not self.stream_id:
|
||||
raise SpeckleException(
|
||||
f"Cannot parse {url} into a stream wrapper class - no {key_stream} id found."
|
||||
f"Cannot parse {url} into a stream wrapper class - no {key_stream} ",
|
||||
"id found.",
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -213,7 +214,11 @@ class StreamWrapper:
|
||||
self._client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
|
||||
|
||||
if self._account.token is None and token is None:
|
||||
warn(f"No local account found for server {self.host}", SpeckleWarning)
|
||||
warn(
|
||||
f"No local account found for server {self.host}",
|
||||
SpeckleWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._client
|
||||
|
||||
if self._account.token:
|
||||
@@ -266,14 +271,20 @@ class StreamWrapper:
|
||||
if use_fe2 is False or (use_fe2 is True and not self.model_id):
|
||||
base_url = f"{self.server_url}{key_streams}{self.stream_id}"
|
||||
else: # fe2 is True and model_id available
|
||||
base_url = f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
|
||||
base_url = (
|
||||
f"{self.server_url}{key_streams}"
|
||||
f"{self.stream_id}{key_branches}{value_branch}"
|
||||
)
|
||||
|
||||
if wrapper_type == "object":
|
||||
return f"{base_url}{key_objects}{self.object_id}"
|
||||
elif wrapper_type == "commit":
|
||||
return f"{base_url}{key_commits}{self.commit_id}"
|
||||
elif wrapper_type == "branch":
|
||||
return f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
|
||||
return (
|
||||
f"{self.server_url}{key_streams}{self.stream_id}"
|
||||
f"{key_branches}{value_branch}"
|
||||
)
|
||||
elif wrapper_type == "stream":
|
||||
return f"{self.server_url}{key_streams}{self.stream_id}"
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Provides uniform and consistent path helpers for `specklepy`
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -98,7 +99,7 @@ def user_application_data_path() -> Path:
|
||||
except Exception as ex:
|
||||
raise SpeckleException(
|
||||
message="Failed to initialize user application data path.", exception=ex
|
||||
)
|
||||
) from ex
|
||||
|
||||
|
||||
def user_speckle_folder_path() -> Path:
|
||||
|
||||
@@ -86,7 +86,8 @@ def track(
|
||||
|
||||
METRICS_TRACKER.queue.put_nowait(event_params)
|
||||
except Exception as ex:
|
||||
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
|
||||
# wrapping this whole thing in a try except as we never want a failure here
|
||||
# to annoy users!
|
||||
LOG.debug(f"Error queueing metrics request: {str(ex)}")
|
||||
|
||||
|
||||
@@ -106,7 +107,7 @@ class Singleton(type):
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
|
||||
class CRS(Base, speckle_type="Objects.GIS.CRS"):
|
||||
"""A Coordinate Reference System stored in wkt format"""
|
||||
|
||||
name: Optional[str] = None
|
||||
authority_id: Optional[str] = None
|
||||
wkt: Optional[str] = None
|
||||
units_native: Optional[str] = None
|
||||
offset_x: Optional[float] = None
|
||||
offset_y: Optional[float] = None
|
||||
rotation: Optional[float] = None
|
||||
@@ -1,24 +0,0 @@
|
||||
"""Builtin Speckle object kit."""
|
||||
|
||||
from specklepy.objects.GIS.CRS import CRS
|
||||
from specklepy.objects.GIS.geometry import (
|
||||
GisLineElement,
|
||||
GisPointElement,
|
||||
GisPolygonElement,
|
||||
GisPolygonGeometry,
|
||||
GisRasterElement,
|
||||
PolygonGeometry,
|
||||
)
|
||||
from specklepy.objects.GIS.layers import RasterLayer, VectorLayer
|
||||
|
||||
__all__ = [
|
||||
"VectorLayer",
|
||||
"RasterLayer",
|
||||
"GisPolygonGeometry",
|
||||
"PolygonGeometry",
|
||||
"GisPolygonElement",
|
||||
"GisLineElement",
|
||||
"GisPointElement",
|
||||
"GisRasterElement",
|
||||
"CRS",
|
||||
]
|
||||
@@ -1,74 +0,0 @@
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry import (
|
||||
Arc,
|
||||
Circle,
|
||||
Line,
|
||||
Mesh,
|
||||
Point,
|
||||
Polycurve,
|
||||
Polyline,
|
||||
)
|
||||
|
||||
|
||||
class PolygonGeometry(Base, speckle_type="Objects.GIS.PolygonGeometry"):
|
||||
"""GIS Polygon Geometry"""
|
||||
|
||||
boundary: Optional[Polyline]
|
||||
voids: Optional[List[Polyline]]
|
||||
|
||||
|
||||
GisPolygonGeometry = PolygonGeometry
|
||||
|
||||
|
||||
class GisPolygonElement(Base, speckle_type="Objects.GIS.PolygonElement"):
|
||||
"""GIS Polygon element"""
|
||||
|
||||
geometry: Optional[List[GisPolygonGeometry]] = None
|
||||
attributes: Optional[Base] = None
|
||||
|
||||
|
||||
class GisLineElement(Base, speckle_type="Objects.GIS.LineElement"):
|
||||
"""GIS Polyline element"""
|
||||
|
||||
geometry: Optional[List[Union[Polyline, Arc, Line, Circle, Polycurve]]] = None
|
||||
attributes: Optional[Base] = None
|
||||
|
||||
|
||||
class GisPointElement(Base, speckle_type="Objects.GIS.PointElement"):
|
||||
"""GIS Point element"""
|
||||
|
||||
geometry: Optional[List[Point]] = None
|
||||
attributes: Optional[Base] = None
|
||||
|
||||
|
||||
class GisRasterElement(
|
||||
Base, speckle_type="Objects.GIS.RasterElement", detachable={"displayValue"}
|
||||
):
|
||||
"""GIS Raster element"""
|
||||
|
||||
band_count: Optional[int] = None
|
||||
band_names: Optional[List[str]] = None
|
||||
x_origin: Optional[float] = None
|
||||
y_origin: Optional[float] = None
|
||||
x_size: Optional[int] = None
|
||||
y_size: Optional[int] = None
|
||||
x_resolution: Optional[float] = None
|
||||
y_resolution: Optional[float] = None
|
||||
noDataValue: Optional[List[float]] = None
|
||||
displayValue: Optional[List[Mesh]] = None
|
||||
|
||||
|
||||
class GisTopography(
|
||||
GisRasterElement,
|
||||
speckle_type="Objects.GIS.GisTopography",
|
||||
detachable={"displayValue"},
|
||||
):
|
||||
"""GIS Raster element with 3d Topography representation"""
|
||||
|
||||
|
||||
class GisNonGeometryElement(Base, speckle_type="Objects.GIS.NonGeometryElement"):
|
||||
"""GIS Table feature"""
|
||||
|
||||
attributes: Optional[Base] = None
|
||||
@@ -1,142 +0,0 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.GIS.CRS import CRS
|
||||
from specklepy.objects.other import Collection
|
||||
|
||||
|
||||
@deprecated(version="2.15", reason="Use VectorLayer or RasterLayer instead")
|
||||
class Layer(Base, detachable={"features"}):
|
||||
"""A GIS Layer"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
crs: Optional[CRS] = None,
|
||||
units: str = "m",
|
||||
features: Optional[List[Base]] = None,
|
||||
layerType: str = "None",
|
||||
geomType: str = "None",
|
||||
renderer: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.name = name
|
||||
self.crs = crs
|
||||
self.units = units
|
||||
self.type = layerType
|
||||
self.features = features or []
|
||||
self.geomType = geomType
|
||||
self.renderer = renderer or {}
|
||||
|
||||
|
||||
@deprecated(version="2.16", reason="Use VectorLayer or RasterLayer instead")
|
||||
class VectorLayer(
|
||||
Collection,
|
||||
detachable={"elements"},
|
||||
speckle_type="VectorLayer",
|
||||
serialize_ignore={"features"},
|
||||
):
|
||||
"""GIS Vector Layer"""
|
||||
|
||||
name: Optional[str] = None
|
||||
crs: Optional[Union[CRS, Base]] = None
|
||||
units: Optional[str] = None
|
||||
elements: Optional[List[Base]] = None
|
||||
attributes: Optional[Base] = None
|
||||
geomType: Optional[str] = "None"
|
||||
renderer: Optional[Dict[str, Any]] = None
|
||||
collectionType = "VectorLayer"
|
||||
|
||||
@property
|
||||
@deprecated(version="2.14", reason="Use elements")
|
||||
def features(self) -> Optional[List[Base]]:
|
||||
return self.elements
|
||||
|
||||
@features.setter
|
||||
def features(self, value: Optional[List[Base]]) -> None:
|
||||
self.elements = value
|
||||
|
||||
|
||||
@deprecated(version="2.16", reason="Use VectorLayer or RasterLayer instead")
|
||||
class RasterLayer(
|
||||
Collection,
|
||||
detachable={"elements"},
|
||||
speckle_type="RasterLayer",
|
||||
serialize_ignore={"features"},
|
||||
):
|
||||
"""GIS Raster Layer"""
|
||||
|
||||
name: Optional[str] = None
|
||||
crs: Optional[Union[CRS, Base]] = None
|
||||
units: Optional[str] = None
|
||||
rasterCrs: Optional[Union[CRS, Base]] = None
|
||||
elements: Optional[List[Base]] = None
|
||||
geomType: Optional[str] = "None"
|
||||
renderer: Optional[Dict[str, Any]] = None
|
||||
collectionType = "RasterLayer"
|
||||
|
||||
@property
|
||||
@deprecated(version="2.14", reason="Use elements")
|
||||
def features(self) -> Optional[List[Base]]:
|
||||
return self.elements
|
||||
|
||||
@features.setter
|
||||
def features(self, value: Optional[List[Base]]) -> None:
|
||||
self.elements = value
|
||||
|
||||
|
||||
class VectorLayer( # noqa: F811
|
||||
Collection,
|
||||
detachable={"elements"},
|
||||
speckle_type="Objects.GIS.VectorLayer",
|
||||
serialize_ignore={"features"},
|
||||
):
|
||||
"""GIS Vector Layer"""
|
||||
|
||||
name: Optional[str] = None
|
||||
crs: Optional[Union[CRS, Base]] = None
|
||||
units: Optional[str] = None
|
||||
elements: Optional[List[Base]] = None
|
||||
attributes: Optional[Base] = None
|
||||
geomType: Optional[str] = "None"
|
||||
renderer: Optional[Dict[str, Any]] = None
|
||||
collectionType = "VectorLayer"
|
||||
|
||||
@property
|
||||
@deprecated(version="2.14", reason="Use elements")
|
||||
def features(self) -> Optional[List[Base]]:
|
||||
return self.elements
|
||||
|
||||
@features.setter
|
||||
def features(self, value: Optional[List[Base]]) -> None:
|
||||
self.elements = value
|
||||
|
||||
|
||||
class RasterLayer( # noqa: F811
|
||||
Collection,
|
||||
detachable={"elements"},
|
||||
speckle_type="Objects.GIS.RasterLayer",
|
||||
serialize_ignore={"features"},
|
||||
):
|
||||
"""GIS Raster Layer"""
|
||||
|
||||
name: Optional[str] = None
|
||||
crs: Optional[Union[CRS, Base]] = None
|
||||
units: Optional[str] = None
|
||||
rasterCrs: Optional[Union[CRS, Base]] = None
|
||||
elements: Optional[List[Base]] = None
|
||||
geomType: Optional[str] = "None"
|
||||
renderer: Optional[Dict[str, Any]] = None
|
||||
collectionType = "RasterLayer"
|
||||
|
||||
@property
|
||||
@deprecated(version="2.14", reason="Use elements")
|
||||
def features(self) -> Optional[List[Base]]:
|
||||
return self.elements
|
||||
|
||||
@features.setter
|
||||
def features(self, value: Optional[List[Base]]) -> None:
|
||||
self.elements = value
|
||||
@@ -1,23 +1,6 @@
|
||||
"""Builtin Speckle object kit."""
|
||||
|
||||
from specklepy.objects import (
|
||||
GIS,
|
||||
encoding,
|
||||
geometry,
|
||||
other,
|
||||
primitive,
|
||||
structural,
|
||||
units,
|
||||
)
|
||||
from specklepy.objects.base import Base
|
||||
from .data_objects import DataObject, QgisObject
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"encoding",
|
||||
"geometry",
|
||||
"other",
|
||||
"units",
|
||||
"structural",
|
||||
"primitive",
|
||||
"GIS",
|
||||
"DataObject",
|
||||
"QgisObject",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import contextlib
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from inspect import isclass
|
||||
from typing import (
|
||||
@@ -18,8 +19,7 @@ from warnings import warn
|
||||
|
||||
from stringcase import pascalcase
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
||||
from specklepy.objects.units import Units
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.transports.memory import MemoryTransport
|
||||
|
||||
PRIMITIVES = (int, float, str, bool)
|
||||
@@ -65,7 +65,6 @@ REMOVE_FROM_DIR = {
|
||||
"_handle_object_count",
|
||||
"_type_check",
|
||||
"_type_registry",
|
||||
"_units",
|
||||
"add_chunkable_attrs",
|
||||
"add_detachable_attrs",
|
||||
"get_children_count",
|
||||
@@ -116,7 +115,7 @@ class _RegisteringBase:
|
||||
@classmethod
|
||||
def _determine_speckle_type(cls) -> str:
|
||||
"""
|
||||
This method brings the speckle_type construction in par with peckle-sharp/Core.
|
||||
This method brings the speckle_type construction in par with Speckle-sharp/Core.
|
||||
|
||||
The implementation differs, because in Core the basis of the speckle_type if
|
||||
type.FullName, which includes the dotnet namespace name too.
|
||||
@@ -168,8 +167,11 @@ class _RegisteringBase:
|
||||
initialization. This is reused to register each subclassing type into a class
|
||||
level dictionary.
|
||||
"""
|
||||
# if not speckle_type:
|
||||
# raise Exception("no type")
|
||||
cls._speckle_type_override = speckle_type
|
||||
cls.speckle_type = cls._determine_speckle_type()
|
||||
# cls.speckle_type = speckle_type
|
||||
if cls._full_name() in cls._type_registry:
|
||||
raise ValueError(
|
||||
f"The speckle_type: {speckle_type} is already registered for type: "
|
||||
@@ -222,7 +224,7 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
|
||||
if isinstance(t, ForwardRef):
|
||||
return True, value
|
||||
|
||||
origin = getattr(t, "__origin__")
|
||||
origin = t.__origin__
|
||||
# below is what in nicer for >= py38
|
||||
# origin = get_origin(t)
|
||||
|
||||
@@ -287,7 +289,7 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
|
||||
if len(args) != len(value):
|
||||
return False, value
|
||||
values = []
|
||||
for t_item, v_item in zip(args, value):
|
||||
for t_item, v_item in zip(args, value, strict=True):
|
||||
item_valid, item_value = _validate_type(t_item, v_item)
|
||||
if not item_valid:
|
||||
return False, value
|
||||
@@ -319,22 +321,17 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
|
||||
return False, value
|
||||
|
||||
|
||||
class Base(_RegisteringBase):
|
||||
@dataclass(kw_only=True)
|
||||
class Base(_RegisteringBase, speckle_type="Base"):
|
||||
id: Union[str, None] = None
|
||||
totalChildrenCount: Union[int, None] = None
|
||||
# totalChildrenCount: Union[int, None] = None
|
||||
applicationId: Union[str, None] = None
|
||||
_units: Union[None, str] = None
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__()
|
||||
for k, v in kwargs.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}(id: {self.id}, "
|
||||
f"speckle_type: {self.speckle_type}, "
|
||||
f"totalChildrenCount: {self.totalChildrenCount})"
|
||||
# f"totalChildrenCount: {self.totalChildrenCount})"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -375,7 +372,8 @@ class Base(_RegisteringBase):
|
||||
if name == "speckle_type":
|
||||
# not sure if we should raise an exception here??
|
||||
# raise SpeckleException(
|
||||
# "Cannot override the `speckle_type`. This is set manually by the class or on deserialisation"
|
||||
# "Cannot override the `speckle_type`."
|
||||
# "This is set manually by the class or on deserialisation"
|
||||
# )
|
||||
return
|
||||
# if value is not None:
|
||||
@@ -403,7 +401,10 @@ class Base(_RegisteringBase):
|
||||
try:
|
||||
cls._attr_types = get_type_hints(cls)
|
||||
except Exception as e:
|
||||
warn(f"Could not update forward refs for class {cls.__name__}: {e}")
|
||||
warn(
|
||||
f"Could not update forward refs for class {cls.__name__}: {e}",
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_prop_name(cls, name: str) -> None:
|
||||
@@ -462,21 +463,22 @@ class Base(_RegisteringBase):
|
||||
"""
|
||||
self._detachable = self._detachable.union(names)
|
||||
|
||||
@property
|
||||
def units(self) -> Union[str, None]:
|
||||
return self._units
|
||||
# @property
|
||||
# def units(self) -> Union[str, None]:
|
||||
# return self._units
|
||||
|
||||
@units.setter
|
||||
def units(self, value: Union[str, Units, None]):
|
||||
"""While this property accepts any string value, geometry expects units to be specific strings (see Units enum)"""
|
||||
if isinstance(value, str) or value is None:
|
||||
self._units = value
|
||||
elif isinstance(value, Units):
|
||||
self._units = value.value
|
||||
else:
|
||||
raise SpeckleInvalidUnitException(
|
||||
f"Unknown type {type(value)} received for units"
|
||||
)
|
||||
# @units.setter
|
||||
# def units(self, value: Union[str, Units, None]):
|
||||
# """While this property accepts any string value,
|
||||
# geometry expects units to be specific strings (see Units enum)"""
|
||||
# if isinstance(value, str) or value is None:
|
||||
# self._units = value
|
||||
# elif isinstance(value, Units):
|
||||
# self._units = value.value
|
||||
# else:
|
||||
# raise SpeckleInvalidUnitException(
|
||||
# f"Unknown type {type(value)} received for units"
|
||||
# )
|
||||
|
||||
def get_member_names(self) -> List[str]:
|
||||
"""Get all of the property names on this object, dynamic or not"""
|
||||
@@ -568,9 +570,6 @@ class Base(_RegisteringBase):
|
||||
Base.update_forward_refs()
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DataChunk(Base, speckle_type="Speckle.Core.Models.DataChunk"):
|
||||
data: Union[List[Any], None] = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.data = []
|
||||
data: List[Any] = field(default_factory=list)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IDataObject, IGisObject, IHasUnits
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DataObject(
|
||||
Base,
|
||||
IDataObject,
|
||||
speckle_type="Objects.Data.DataObject",
|
||||
detachable={"displayValue"},
|
||||
):
|
||||
|
||||
name: str
|
||||
properties: Dict[str, object]
|
||||
displayValue: List[Base]
|
||||
_name: str = field(repr=False, init=False)
|
||||
_properties: Dict[str, object] = field(repr=False, init=False)
|
||||
_displayValue: List[Base] = field(repr=False, init=False)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def properties(self) -> Dict[str, object]:
|
||||
return self._properties
|
||||
|
||||
@property
|
||||
def displayValue(self) -> List[Base]:
|
||||
return self._displayValue
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
if isinstance(value, str):
|
||||
self._name = value
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"'name' value should be string, received {type(value)}"
|
||||
)
|
||||
|
||||
@properties.setter
|
||||
def properties(self, value: dict):
|
||||
if isinstance(value, dict):
|
||||
self._properties = value
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"'properties' value should be Dict, received {type(value)}"
|
||||
)
|
||||
|
||||
@displayValue.setter
|
||||
def displayValue(self, value: list):
|
||||
if isinstance(value, list):
|
||||
self._displayValue = value
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"'displayValue' value should be List, received {type(value)}"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class QgisObject(
|
||||
DataObject, IGisObject, IHasUnits, speckle_type="Objects.Data.QgisObject"
|
||||
):
|
||||
type: str
|
||||
_type: str = field(repr=False, init=False)
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, value: str):
|
||||
if isinstance(value, str):
|
||||
self._type = value
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"'type' value should be string, received {type(value)}"
|
||||
)
|
||||
@@ -1,131 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, List, Optional, Type
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
|
||||
class CurveTypeEncoding(int, Enum):
|
||||
Arc = 0
|
||||
Circle = 1
|
||||
Curve = 2
|
||||
Ellipse = 3
|
||||
Line = 4
|
||||
Polyline = 5
|
||||
Polycurve = 6
|
||||
|
||||
@property
|
||||
def object_class(self) -> Type:
|
||||
from . import geometry
|
||||
|
||||
if self == self.Arc:
|
||||
return geometry.Arc
|
||||
elif self == self.Circle:
|
||||
return geometry.Circle
|
||||
elif self == self.Curve:
|
||||
return geometry.Curve
|
||||
elif self == self.Ellipse:
|
||||
return geometry.Ellipse
|
||||
elif self == self.Line:
|
||||
return geometry.Line
|
||||
elif self == self.Polyline:
|
||||
return geometry.Polyline
|
||||
elif self == self.Polycurve:
|
||||
return geometry.Polycurve
|
||||
raise SpeckleException(
|
||||
f"No corresponding object class for CurveTypeEncoding: {self}"
|
||||
)
|
||||
|
||||
|
||||
def curve_from_list(args: List[float]):
|
||||
curve_type = CurveTypeEncoding(args[0])
|
||||
return curve_type.object_class.from_list(args)
|
||||
|
||||
|
||||
class ObjectArray:
|
||||
def __init__(self, data: Optional[list] = None) -> None:
|
||||
self.data = data or []
|
||||
|
||||
@classmethod
|
||||
def from_objects(cls, objects: List[Base]) -> "ObjectArray":
|
||||
data_list = cls()
|
||||
if not objects:
|
||||
return data_list
|
||||
|
||||
speckle_type = objects[0].speckle_type
|
||||
|
||||
for obj in objects:
|
||||
if speckle_type != obj.speckle_type:
|
||||
raise SpeckleException(
|
||||
"All objects in chunk should have the same speckle_type. "
|
||||
f"Found {speckle_type} and {obj.speckle_type}"
|
||||
)
|
||||
data_list.encode_object(obj=obj)
|
||||
|
||||
return data_list
|
||||
|
||||
@staticmethod
|
||||
def decode_data(
|
||||
data: List[Any], decoder: Callable[[List[Any]], Base], **kwargs: Dict[str, Any]
|
||||
) -> List[Base]:
|
||||
bases: List[Base] = []
|
||||
if not data:
|
||||
return bases
|
||||
index = 0
|
||||
while index < len(data):
|
||||
item_length = int(data[index])
|
||||
item_start = index + 1
|
||||
item_end = item_start + item_length
|
||||
item_data = data[item_start:item_end]
|
||||
index = item_end
|
||||
decoded_data = decoder(item_data, **kwargs)
|
||||
bases.append(decoded_data)
|
||||
|
||||
return bases
|
||||
|
||||
def decode(self, decoder: Callable[[List[Any]], Any], **kwargs: Dict[str, Any]):
|
||||
return self.decode_data(data=self.data, decoder=decoder, **kwargs)
|
||||
|
||||
def encode_object(self, obj: Base):
|
||||
encoded = obj.to_list()
|
||||
encoded.insert(0, len(encoded))
|
||||
self.data.extend(encoded)
|
||||
|
||||
|
||||
class CurveArray(ObjectArray):
|
||||
@classmethod
|
||||
def from_curve(cls, curve: Base) -> "CurveArray":
|
||||
crv_array = cls()
|
||||
crv_array.data = curve.to_list()
|
||||
return crv_array
|
||||
|
||||
@classmethod
|
||||
def from_curves(cls, curves: List[Base]) -> "CurveArray":
|
||||
data = []
|
||||
for curve in curves:
|
||||
curve_list = curve.to_list()
|
||||
curve_list.insert(0, len(curve_list))
|
||||
data.extend(curve_list)
|
||||
crv_array = cls()
|
||||
crv_array.data = data
|
||||
return crv_array
|
||||
|
||||
@staticmethod
|
||||
def curve_from_list(args: List[float]) -> Base:
|
||||
curve_type = CurveTypeEncoding(args[0])
|
||||
return curve_type.object_class.from_list(args)
|
||||
|
||||
@property
|
||||
def type(self) -> CurveTypeEncoding:
|
||||
return CurveTypeEncoding(self.data[0])
|
||||
|
||||
def to_curve(self) -> Base:
|
||||
return self.type.object_class.from_list(self.data)
|
||||
|
||||
@classmethod
|
||||
def _curve_decoder(cls, data: List[float]) -> Base:
|
||||
crv_array = cls(data)
|
||||
return crv_array.to_curve()
|
||||
|
||||
def to_curves(self) -> List[Base]:
|
||||
return self.decode(decoder=self._curve_decoder)
|
||||
@@ -1,946 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.encoding import CurveArray, CurveTypeEncoding, ObjectArray
|
||||
from specklepy.objects.primitive import Interval
|
||||
from specklepy.objects.units import get_encoding_from_units, get_units_from_encoding
|
||||
|
||||
GEOMETRY = "Objects.Geometry."
|
||||
|
||||
|
||||
class Point(Base, speckle_type=GEOMETRY + "Point"):
|
||||
x: float = 0.0
|
||||
y: float = 0.0
|
||||
z: float = 0.0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, id:"
|
||||
f" {self.id}, speckle_type: {self.speckle_type})"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[float]) -> "Point":
|
||||
"""
|
||||
Create a new Point from a list of three floats
|
||||
representing the x, y, and z coordinates
|
||||
"""
|
||||
return cls(x=args[0], y=args[1], z=args[2])
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [self.x, self.y, self.z]
|
||||
|
||||
@classmethod
|
||||
def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0):
|
||||
"""Create a new Point from x, y, and z values"""
|
||||
pt = Point()
|
||||
pt.x, pt.y, pt.z = x, y, z
|
||||
return pt
|
||||
|
||||
|
||||
class Pointcloud(
|
||||
Base,
|
||||
speckle_type=GEOMETRY + "Pointcloud",
|
||||
chunkable={"points": 31250, "colors": 62500, "sizes": 62500},
|
||||
):
|
||||
points: Optional[List[float]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
sizes: Optional[List[float]] = None
|
||||
bbox: Optional["Box"] = None
|
||||
|
||||
|
||||
class Vector(Base, speckle_type=GEOMETRY + "Vector"):
|
||||
x: float = 0.0
|
||||
y: float = 0.0
|
||||
z: float = 0.0
|
||||
applicationId: Optional[str] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__} "
|
||||
"(x: {self.x}, y: {self.y}, z: {self.z}, id: {self.id}, "
|
||||
"speckle_type: {self.speckle_type})"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[float]) -> "Vector":
|
||||
"""
|
||||
Create from a list of three floats representing the x, y, and z coordinates.
|
||||
"""
|
||||
return cls(x=args[0], y=args[1], z=args[2])
|
||||
|
||||
def to_list(self) -> List[float]:
|
||||
return [self.x, self.y, self.z]
|
||||
|
||||
@classmethod
|
||||
def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> "Vector":
|
||||
"""Create a new Point from x, y, and z values"""
|
||||
v = Vector()
|
||||
v.x, v.y, v.z = x, y, z
|
||||
return v
|
||||
|
||||
|
||||
class ControlPoint(Point, speckle_type=GEOMETRY + "ControlPoint"):
|
||||
weight: Optional[float] = None
|
||||
|
||||
|
||||
class Plane(Base, speckle_type=GEOMETRY + "Plane"):
|
||||
origin: Point = Point()
|
||||
normal: Vector = Vector()
|
||||
xdir: Vector = Vector()
|
||||
ydir: Vector = Vector()
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Plane":
|
||||
return cls(
|
||||
origin=Point.from_list(args[:3]),
|
||||
normal=Vector.from_list(args[3:6]),
|
||||
xdir=Vector.from_list(args[6:9]),
|
||||
ydir=Vector.from_list(args[9:12]),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
*self.origin.to_list(),
|
||||
*self.normal.to_list(),
|
||||
*self.xdir.to_list(),
|
||||
*self.ydir.to_list(),
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Box(Base, speckle_type=GEOMETRY + "Box"):
|
||||
basePlane: Plane = Plane()
|
||||
xSize: Interval = Interval()
|
||||
ySize: Interval = Interval()
|
||||
zSize: Interval = Interval()
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
|
||||
|
||||
class Line(Base, speckle_type=GEOMETRY + "Line"):
|
||||
start: Point = Point()
|
||||
end: Optional[Point] = None
|
||||
domain: Optional[Interval] = None
|
||||
bbox: Optional[Box] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Line":
|
||||
return cls(
|
||||
start=Point.from_list(args[1:4]),
|
||||
end=Point.from_list(args[4:7]),
|
||||
domain=Interval.from_list(args[7:10]),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
domain = self.domain.to_list() if self.domain else [0, 1]
|
||||
return [
|
||||
CurveTypeEncoding.Line.value,
|
||||
*self.start.to_list(),
|
||||
*self.end.to_list(),
|
||||
*domain,
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Arc(Base, speckle_type=GEOMETRY + "Arc"):
|
||||
radius: Optional[float] = None
|
||||
startAngle: Optional[float] = None
|
||||
endAngle: Optional[float] = None
|
||||
angleRadians: Optional[float] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
startPoint: Optional[Point] = None
|
||||
midPoint: Optional[Point] = None
|
||||
endPoint: Optional[Point] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Arc":
|
||||
return cls(
|
||||
radius=args[1],
|
||||
startAngle=args[2],
|
||||
endAngle=args[3],
|
||||
angleRadians=args[4],
|
||||
domain=Interval.from_list(args[5:7]),
|
||||
plane=Plane.from_list(args[7:20]),
|
||||
startPoint=Point.from_list(args[20:23]),
|
||||
midPoint=Point.from_list(args[23:26]),
|
||||
endPoint=Point.from_list(args[26:29]),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
CurveTypeEncoding.Arc.value,
|
||||
self.radius,
|
||||
self.startAngle,
|
||||
self.endAngle,
|
||||
self.angleRadians,
|
||||
*self.domain.to_list(),
|
||||
*self.plane.to_list(),
|
||||
*self.startPoint.to_list(),
|
||||
*self.midPoint.to_list(),
|
||||
*self.endPoint.to_list(),
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Circle(Base, speckle_type=GEOMETRY + "Circle"):
|
||||
radius: Optional[float] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Circle":
|
||||
return cls(
|
||||
radius=args[1],
|
||||
domain=Interval.from_list(args[2:4]),
|
||||
plane=Plane.from_list(args[4:17]),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
CurveTypeEncoding.Circle.value,
|
||||
self.radius,
|
||||
*self.domain.to_list(),
|
||||
*self.plane.to_list(),
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
|
||||
firstRadius: Optional[float] = None
|
||||
secondRadius: Optional[float] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
trimDomain: Optional[Interval] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Ellipse":
|
||||
return cls(
|
||||
firstRadius=args[1],
|
||||
secondRadius=args[2],
|
||||
domain=Interval.from_list(args[3:5]),
|
||||
plane=Plane.from_list(args[5:18]),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
CurveTypeEncoding.Ellipse.value,
|
||||
self.firstRadius,
|
||||
self.secondRadius,
|
||||
*self.domain.to_list(),
|
||||
*self.plane.to_list(),
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 20000}):
|
||||
value: Optional[List[float]] = None
|
||||
closed: Optional[bool] = None
|
||||
domain: Optional[Interval] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_points(cls, points: List[Point]):
|
||||
"""Create a new Polyline from a list of Points"""
|
||||
polyline = cls()
|
||||
polyline.units = points[0].units
|
||||
polyline.value = []
|
||||
for point in points:
|
||||
polyline.value.extend([point.x, point.y, point.z])
|
||||
return polyline
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Polyline":
|
||||
point_count = args[4]
|
||||
return cls(
|
||||
closed=bool(args[1]),
|
||||
domain=Interval.from_list(args[2:4]),
|
||||
value=args[5 : 5 + point_count],
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
CurveTypeEncoding.Polyline.value,
|
||||
int(self.closed),
|
||||
*self.domain.to_list(),
|
||||
len(self.value),
|
||||
*self.value,
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
def as_points(self) -> List[Point]:
|
||||
"""Converts the `value` attribute to a list of Points"""
|
||||
if not self.value:
|
||||
return
|
||||
|
||||
if len(self.value) % 3:
|
||||
raise ValueError("Points array malformed: length%3 != 0.")
|
||||
|
||||
values = iter(self.value)
|
||||
return [
|
||||
Point(x=v, y=next(values), z=next(values), units=self.units) for v in values
|
||||
]
|
||||
|
||||
|
||||
class SpiralType(Enum):
|
||||
Biquadratic = 0
|
||||
BiquadraticParabola = 1
|
||||
Bloss = 2
|
||||
Clothoid = 3
|
||||
Cosine = 4
|
||||
Cubic = 5
|
||||
CubicParabola = 6
|
||||
Radioid = 7
|
||||
Sinusoid = 8
|
||||
Unknown = 9
|
||||
|
||||
|
||||
class Spiral(Base, speckle_type=GEOMETRY + "Spiral", detachable={"displayValue"}):
|
||||
startPoint: Optional[Point] = None
|
||||
endPoint: Optional[Point]
|
||||
plane: Optional[Plane]
|
||||
turns: Optional[float]
|
||||
pitchAxis: Optional[Vector] = Vector()
|
||||
pitch: float = 0
|
||||
spiralType: Optional[SpiralType] = None
|
||||
displayValue: Optional[Polyline] = None
|
||||
bbox: Optional[Box] = None
|
||||
length: Optional[float] = None
|
||||
domain: Optional[Interval] = None
|
||||
|
||||
|
||||
class Curve(
|
||||
Base,
|
||||
speckle_type=GEOMETRY + "Curve",
|
||||
chunkable={"points": 20000, "weights": 20000, "knots": 20000},
|
||||
):
|
||||
degree: Optional[int] = None
|
||||
periodic: Optional[bool] = None
|
||||
rational: Optional[bool] = None
|
||||
points: Optional[List[float]] = None
|
||||
weights: Optional[List[float]] = None
|
||||
knots: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
displayValue: Optional[Polyline] = None
|
||||
closed: Optional[bool] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
def as_points(self) -> List[Point]:
|
||||
"""Converts the `value` attribute to a list of Points"""
|
||||
if not self.points:
|
||||
return
|
||||
|
||||
if len(self.points) % 3:
|
||||
raise ValueError("Points array malformed: length%3 != 0.")
|
||||
|
||||
values = iter(self.points)
|
||||
return [
|
||||
Point(x=v, y=next(values), z=next(values), units=self.units) for v in values
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Curve":
|
||||
point_count = int(args[7])
|
||||
weights_count = int(args[8])
|
||||
knots_count = int(args[9])
|
||||
|
||||
points_start = 10
|
||||
weights_start = 10 + point_count
|
||||
knots_start = weights_start + weights_count
|
||||
knots_end = knots_start + knots_count
|
||||
|
||||
return cls(
|
||||
degree=int(args[1]),
|
||||
periodic=bool(args[2]),
|
||||
rational=bool(args[3]),
|
||||
closed=bool(args[4]),
|
||||
domain=Interval.from_list(args[5:7]),
|
||||
points=args[points_start:weights_start],
|
||||
weights=args[weights_start:knots_start],
|
||||
knots=args[knots_start:knots_end],
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
CurveTypeEncoding.Curve.value,
|
||||
self.degree,
|
||||
int(self.periodic),
|
||||
int(self.rational),
|
||||
int(self.closed),
|
||||
*self.domain.to_list(),
|
||||
len(self.points),
|
||||
len(self.weights),
|
||||
len(self.knots),
|
||||
*self.points,
|
||||
*self.weights,
|
||||
*self.knots,
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
|
||||
segments: Optional[List[Base]] = None
|
||||
domain: Optional[Interval] = None
|
||||
closed: Optional[bool] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Polycurve":
|
||||
curve_arrays = CurveArray(args[5:-1])
|
||||
return cls(
|
||||
closed=bool(args[1]),
|
||||
domain=Interval.from_list(args[2:4]),
|
||||
segments=curve_arrays.to_curves(),
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
curve_array = CurveArray.from_curves(self.segments).data
|
||||
return [
|
||||
CurveTypeEncoding.Polycurve.value,
|
||||
int(self.closed),
|
||||
*self.domain.to_list(),
|
||||
len(curve_array),
|
||||
*curve_array,
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class Extrusion(Base, speckle_type=GEOMETRY + "Extrusion"):
|
||||
capped: Optional[bool] = None
|
||||
profile: Optional[Base] = None
|
||||
pathStart: Optional[Point] = None
|
||||
pathEnd: Optional[Point] = None
|
||||
pathCurve: Optional[Base] = None
|
||||
pathTangent: Optional[Base] = None
|
||||
profiles: Optional[List[Base]] = None
|
||||
length: Optional[float] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
bbox: Optional[Box] = None
|
||||
|
||||
|
||||
class Mesh(
|
||||
Base,
|
||||
speckle_type=GEOMETRY + "Mesh",
|
||||
chunkable={
|
||||
"vertices": 2000,
|
||||
"faces": 2000,
|
||||
"colors": 2000,
|
||||
"textureCoordinates": 2000,
|
||||
},
|
||||
):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
vertices: List[float],
|
||||
faces: List[int],
|
||||
colors: Optional[List[int]] = None,
|
||||
texture_coordinates: Optional[List[float]] = None,
|
||||
) -> "Mesh":
|
||||
"""
|
||||
Create a new Mesh from lists representing its vertices, faces,
|
||||
colors (optional), and texture coordinates (optional).
|
||||
|
||||
This will initialise empty lists for colors and texture coordinates
|
||||
if you do not provide any.
|
||||
"""
|
||||
return cls(
|
||||
vertices=vertices,
|
||||
faces=faces,
|
||||
colors=colors or [],
|
||||
textureCoordinates=texture_coordinates or [],
|
||||
)
|
||||
|
||||
|
||||
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
||||
degreeU: Optional[int] = None
|
||||
degreeV: Optional[int] = None
|
||||
rational: Optional[bool] = None
|
||||
area: Optional[float] = None
|
||||
pointData: Optional[List[float]] = None
|
||||
countU: Optional[int] = None
|
||||
countV: Optional[int] = None
|
||||
bbox: Optional[Box] = None
|
||||
closedU: Optional[bool] = None
|
||||
closedV: Optional[bool] = None
|
||||
domainU: Optional[Interval] = None
|
||||
domainV: Optional[Interval] = None
|
||||
knotsU: Optional[List[float]] = None
|
||||
knotsV: Optional[List[float]] = None
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Surface":
|
||||
point_count = int(args[11])
|
||||
knots_u_count = int(args[12])
|
||||
knots_v_count = int(args[13])
|
||||
|
||||
start_point_data = 14
|
||||
start_knots_u = start_point_data + point_count
|
||||
start_knots_v = start_knots_u + knots_u_count
|
||||
|
||||
return cls(
|
||||
degreeU=int(args[0]),
|
||||
degreeV=int(args[1]),
|
||||
countU=int(args[2]),
|
||||
countV=int(args[3]),
|
||||
rational=bool(args[4]),
|
||||
closedU=bool(args[5]),
|
||||
closedV=bool(args[6]),
|
||||
domainU=Interval(start=args[7], end=args[8]),
|
||||
domainV=Interval(start=args[9], end=args[10]),
|
||||
pointData=args[start_point_data:start_knots_u],
|
||||
knotsU=args[start_knots_u:start_knots_v],
|
||||
knotsV=args[start_knots_v : start_knots_v + knots_v_count],
|
||||
units=get_units_from_encoding(args[-1]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
self.degreeU,
|
||||
self.degreeV,
|
||||
self.countU,
|
||||
self.countV,
|
||||
int(self.rational),
|
||||
int(self.closedU),
|
||||
int(self.closedV),
|
||||
*self.domainU.to_list(),
|
||||
*self.domainV.to_list(),
|
||||
len(self.pointData),
|
||||
len(self.knotsU),
|
||||
len(self.knotsV),
|
||||
*self.pointData,
|
||||
*self.knotsU,
|
||||
*self.knotsV,
|
||||
get_encoding_from_units(self._units),
|
||||
]
|
||||
|
||||
|
||||
class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"):
|
||||
_Brep: Optional["Brep"] = None
|
||||
SurfaceIndex: Optional[int] = None
|
||||
OuterLoopIndex: Optional[int] = None
|
||||
OrientationReversed: Optional[bool] = None
|
||||
LoopIndices: Optional[List[int]] = None
|
||||
|
||||
@property
|
||||
def _outer_loop(self):
|
||||
return self._Brep.Loops[self.OuterLoopIndex] # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def _surface(self):
|
||||
return self._Brep.Surfaces[self.SurfaceIndex] # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def _loops(self):
|
||||
if self.LoopIndices:
|
||||
# pylint: disable=not-an-iterable, no-member
|
||||
return [self._Brep.Loops[i] for i in self.LoopIndices]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any], brep: "Brep" = None) -> "BrepFace":
|
||||
return cls(
|
||||
_Brep=brep,
|
||||
SurfaceIndex=args[0],
|
||||
OuterLoopIndex=args[1],
|
||||
OrientationReversed=bool(args[2]),
|
||||
LoopIndices=args[3:],
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
self.SurfaceIndex,
|
||||
self.OuterLoopIndex,
|
||||
int(self.OrientationReversed),
|
||||
*self.LoopIndices,
|
||||
]
|
||||
|
||||
|
||||
class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
|
||||
_Brep: Optional["Brep"] = None
|
||||
Curve3dIndex: Optional[int] = None
|
||||
TrimIndices: Optional[List[int]] = None
|
||||
StartIndex: Optional[int] = None
|
||||
EndIndex: Optional[int] = None
|
||||
ProxyCurveIsReversed: Optional[bool] = None
|
||||
Domain: Optional[Interval] = None
|
||||
|
||||
@property
|
||||
def _start_vertex(self):
|
||||
return self._Brep.Vertices[self.StartIndex]
|
||||
|
||||
@property
|
||||
def _end_vertex(self):
|
||||
return self._Brep.Vertices[self.EndIndex]
|
||||
|
||||
@property
|
||||
def _trims(self):
|
||||
if self.TrimIndices:
|
||||
# pylint: disable=not-an-iterable
|
||||
return [self._Brep.Trims[i] for i in self.TrimIndices]
|
||||
|
||||
@property
|
||||
def _curve(self):
|
||||
return self._Brep.Curve3D[self.Curve3dIndex]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any], brep: "Brep" = None) -> "BrepEdge":
|
||||
domain_start = args[4]
|
||||
domain_end = args[5]
|
||||
domain = (
|
||||
Interval(start=domain_start, end=domain_end)
|
||||
if None not in (domain_start, domain_end)
|
||||
else None
|
||||
)
|
||||
return cls(
|
||||
_Brep=brep,
|
||||
Curve3dIndex=int(args[0]),
|
||||
TrimIndices=[int(t) for t in args[6:]],
|
||||
StartIndex=int(args[1]),
|
||||
EndIndex=int(args[2]),
|
||||
ProxyCurveIsReversed=bool(args[3]),
|
||||
Domain=domain,
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
self.Curve3dIndex,
|
||||
self.StartIndex,
|
||||
self.EndIndex,
|
||||
int(self.ProxyCurveIsReversed),
|
||||
self.Domain.start,
|
||||
self.Domain.end,
|
||||
*self.TrimIndices,
|
||||
]
|
||||
|
||||
|
||||
class BrepLoopType(int, Enum):
|
||||
Unknown = 0
|
||||
Outer = 1
|
||||
Inner = 2
|
||||
Slit = 3
|
||||
CurveOnSurface = 4
|
||||
PointOnSurface = 5
|
||||
|
||||
|
||||
class BrepLoop(Base, speckle_type=GEOMETRY + "BrepLoop"):
|
||||
_Brep: Optional["Brep"] = None
|
||||
FaceIndex: Optional[Optional[int]] = None
|
||||
TrimIndices: Optional[List[int]] = None
|
||||
Type: Optional[BrepLoopType] = None
|
||||
|
||||
@property
|
||||
def _face(self):
|
||||
return self._Brep.Faces[self.FaceIndex]
|
||||
|
||||
@property
|
||||
def _trims(self):
|
||||
if self.TrimIndices:
|
||||
# pylint: disable=not-an-iterable
|
||||
return [self._Brep.Trims[i] for i in self.TrimIndices]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[any], brep: "Brep" = None):
|
||||
return cls(
|
||||
_Brep=brep,
|
||||
FaceIndex=args[0],
|
||||
Type=BrepLoopType(args[1]),
|
||||
TrimIndices=args[2:],
|
||||
)
|
||||
|
||||
def to_list(self) -> List[int]:
|
||||
return [
|
||||
self.FaceIndex,
|
||||
self.Type.value,
|
||||
*self.TrimIndices,
|
||||
]
|
||||
|
||||
|
||||
class BrepTrimType(int, Enum):
|
||||
Unknown = 0
|
||||
Boundary = 1
|
||||
Mated = 2
|
||||
Seam = 3
|
||||
Singular = 4
|
||||
CurveOnSurface = 5
|
||||
PointOnSurface = 6
|
||||
Slit = 7
|
||||
|
||||
|
||||
class BrepTrim(Base, speckle_type=GEOMETRY + "BrepTrim"):
|
||||
_Brep: Optional["Brep"] = None
|
||||
EdgeIndex: Optional[int] = None
|
||||
StartIndex: Optional[int] = None
|
||||
EndIndex: Optional[int] = None
|
||||
FaceIndex: Optional[int] = None
|
||||
LoopIndex: Optional[int] = None
|
||||
CurveIndex: Optional[int] = None
|
||||
IsoStatus: Optional[int] = None
|
||||
TrimType: Optional[BrepTrimType] = None
|
||||
IsReversed: Optional[bool] = None
|
||||
Domain: Optional[Interval] = None
|
||||
|
||||
@property
|
||||
def _face(self):
|
||||
if self._Brep:
|
||||
return self._Brep.Faces[self.FaceIndex] # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def _loop(self):
|
||||
if self._Brep:
|
||||
return self._Brep.Loops[self.LoopIndex] # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def _edge(self):
|
||||
if self._Brep:
|
||||
# pylint: disable=no-member
|
||||
return self._Brep.Edges[self.EdgeIndex] if self.EdgeIndex != -1 else None
|
||||
|
||||
@property
|
||||
def _curve_2d(self):
|
||||
if self._Brep:
|
||||
return self._Brep.Curve2D[self.CurveIndex] # pylint: disable=no-member
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any], brep: "Brep" = None) -> "BrepTrim":
|
||||
return cls(
|
||||
_Brep=brep,
|
||||
EdgeIndex=args[0],
|
||||
StartIndex=args[1],
|
||||
EndIndex=args[2],
|
||||
FaceIndex=args[3],
|
||||
LoopIndex=args[4],
|
||||
CurveIndex=args[5],
|
||||
IsoStatus=args[6],
|
||||
TrimType=BrepTrimType(args[7]),
|
||||
IsReversed=bool(args[8]),
|
||||
)
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [
|
||||
self.EdgeIndex,
|
||||
self.StartIndex,
|
||||
self.EndIndex,
|
||||
self.FaceIndex,
|
||||
self.LoopIndex,
|
||||
self.CurveIndex,
|
||||
self.IsoStatus,
|
||||
self.TrimType.value,
|
||||
int(self.IsReversed),
|
||||
]
|
||||
|
||||
|
||||
class Brep(
|
||||
Base,
|
||||
speckle_type=GEOMETRY + "Brep",
|
||||
chunkable={
|
||||
"SurfacesValue": 31250,
|
||||
"Curve3DValues": 31250,
|
||||
"Curve2DValues": 31250,
|
||||
"VerticesValue": 31250,
|
||||
"EdgesValue": 62500,
|
||||
"LoopsValue": 62500,
|
||||
"FacesValue": 62500,
|
||||
"TrimsValue": 62500,
|
||||
},
|
||||
detachable={"displayValue"},
|
||||
serialize_ignore={
|
||||
"Surfaces",
|
||||
"Curve3D",
|
||||
"Curve2D",
|
||||
"Vertices",
|
||||
"Trims",
|
||||
"Edges",
|
||||
"Loops",
|
||||
"Faces",
|
||||
},
|
||||
):
|
||||
provenance: Optional[str] = None
|
||||
bbox: Optional[Box] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
_displayValue: Optional[List[Mesh]] = None
|
||||
Surfaces: Optional[List[Surface]] = None
|
||||
Curve3D: Optional[List[Base]] = None
|
||||
Curve2D: Optional[List[Base]] = None
|
||||
Vertices: Optional[List[Point]] = None
|
||||
Edges: Optional[List[BrepEdge]] = None
|
||||
Loops: Optional[List[BrepLoop]] = None
|
||||
Faces: Optional[List[BrepFace]] = None
|
||||
Trims: Optional[List[BrepTrim]] = None
|
||||
IsClosed: Optional[bool] = None
|
||||
Orientation: Optional[int] = None
|
||||
|
||||
def _inject_self_into_children(self, children: Optional[List[Base]]) -> List[Base]:
|
||||
if children is None:
|
||||
return children
|
||||
|
||||
for child in children:
|
||||
child._Brep = self # pylint: disable=protected-access
|
||||
return children
|
||||
|
||||
# set as prop for now for backwards compatibility
|
||||
@property
|
||||
def displayValue(self) -> List[Mesh]:
|
||||
return self._displayValue
|
||||
|
||||
@displayValue.setter
|
||||
def displayValue(self, value):
|
||||
if isinstance(value, Mesh):
|
||||
self._displayValue = [value]
|
||||
elif isinstance(value, list):
|
||||
self._displayValue = value
|
||||
|
||||
@property
|
||||
def EdgesValue(self) -> List[BrepEdge]:
|
||||
return None if self.Edges is None else ObjectArray.from_objects(self.Edges).data
|
||||
|
||||
@EdgesValue.setter
|
||||
def EdgesValue(self, value: List[float]):
|
||||
if not value:
|
||||
return
|
||||
|
||||
self.Edges = ObjectArray.decode_data(value, BrepEdge.from_list, brep=self)
|
||||
|
||||
@property
|
||||
def LoopsValue(self) -> List[BrepLoop]:
|
||||
return None if self.Loops is None else ObjectArray.from_objects(self.Loops).data
|
||||
|
||||
@LoopsValue.setter
|
||||
def LoopsValue(self, value: List[int]):
|
||||
if not value:
|
||||
return
|
||||
|
||||
self.Loops = ObjectArray.decode_data(value, BrepLoop.from_list, brep=self)
|
||||
|
||||
@property
|
||||
def FacesValue(self) -> List[int]:
|
||||
return None if self.Faces is None else ObjectArray.from_objects(self.Faces).data
|
||||
|
||||
@FacesValue.setter
|
||||
def FacesValue(self, value: List[int]):
|
||||
if not value:
|
||||
return
|
||||
|
||||
self.Faces = ObjectArray.decode_data(value, BrepFace.from_list, brep=self)
|
||||
|
||||
@property
|
||||
def SurfacesValue(self) -> List[float]:
|
||||
return (
|
||||
None
|
||||
if self.Surfaces is None
|
||||
else ObjectArray.from_objects(self.Surfaces).data
|
||||
)
|
||||
|
||||
@SurfacesValue.setter
|
||||
def SurfacesValue(self, value: List[float]):
|
||||
if not value:
|
||||
return
|
||||
|
||||
self.Surfaces = ObjectArray.decode_data(value, Surface.from_list)
|
||||
|
||||
@property
|
||||
def Curve3DValues(self) -> List[float]:
|
||||
return (
|
||||
None if self.Curve3D is None else CurveArray.from_curves(self.Curve3D).data
|
||||
)
|
||||
|
||||
@Curve3DValues.setter
|
||||
def Curve3DValues(self, value: List[float]):
|
||||
crv_array = CurveArray(value)
|
||||
self.Curve3D = crv_array.to_curves()
|
||||
|
||||
@property
|
||||
def Curve2DValues(self) -> List[Base]:
|
||||
return (
|
||||
None if self.Curve2D is None else CurveArray.from_curves(self.Curve2D).data
|
||||
)
|
||||
|
||||
@Curve2DValues.setter
|
||||
def Curve2DValues(self, value: List[float]):
|
||||
crv_array = CurveArray(value)
|
||||
self.Curve2D = crv_array.to_curves()
|
||||
|
||||
@property
|
||||
def VerticesValue(self) -> List[Point]:
|
||||
if self.Vertices is None:
|
||||
return None
|
||||
encoded_unit = get_encoding_from_units(self.Vertices[0].units)
|
||||
values = [encoded_unit]
|
||||
for vertex in self.Vertices:
|
||||
values.extend(vertex.to_list())
|
||||
return values
|
||||
|
||||
@VerticesValue.setter
|
||||
def VerticesValue(self, value: List[float]):
|
||||
value = value.copy()
|
||||
units = get_units_from_encoding(value.pop(0))
|
||||
|
||||
vertices = []
|
||||
|
||||
for i in range(0, len(value), 3):
|
||||
vertex = Point.from_list(value[i : i + 3])
|
||||
vertex.units = units
|
||||
vertices.append(vertex)
|
||||
|
||||
self.Vertices = vertices
|
||||
|
||||
# TODO: can this be consistent with loops, edges, faces, curves, etc and prepend with the chunk list? needs to happen in sharp first
|
||||
@property
|
||||
def TrimsValue(self) -> List[float]:
|
||||
# return None if self.Trims is None else ObjectArray.from_objects(self.Trims).data
|
||||
if not self.Trims:
|
||||
return
|
||||
value = []
|
||||
for trim in self.Trims:
|
||||
value.extend(trim.to_list())
|
||||
return value
|
||||
|
||||
@TrimsValue.setter
|
||||
def TrimsValue(self, value: List[float]):
|
||||
if not value:
|
||||
return
|
||||
|
||||
# self.Trims = ObjectArray.decode_data(value, BrepTrim.from_list, brep=self)
|
||||
self.Trims = [
|
||||
BrepTrim.from_list(value[i : i + 9], self) for i in range(0, len(value), 9)
|
||||
]
|
||||
|
||||
|
||||
BrepEdge.update_forward_refs()
|
||||
BrepLoop.update_forward_refs()
|
||||
BrepTrim.update_forward_refs()
|
||||
BrepFace.update_forward_refs()
|
||||
@@ -0,0 +1,18 @@
|
||||
from specklepy.objects.geometry.arc import Arc
|
||||
from specklepy.objects.geometry.line import Line
|
||||
from specklepy.objects.geometry.mesh import Mesh
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.geometry.polyline import Polyline
|
||||
from specklepy.objects.geometry.vector import Vector
|
||||
|
||||
# re-export them at the geometry package level
|
||||
__all__ = [
|
||||
"Arc",
|
||||
"Line",
|
||||
"Mesh",
|
||||
"Plane",
|
||||
"Point",
|
||||
"Polyline",
|
||||
"Vector"
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import ICurve, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc"):
|
||||
plane: Plane
|
||||
startPoint: Point
|
||||
midPoint: Point
|
||||
endPoint: Point
|
||||
|
||||
@property
|
||||
def radius(self) -> float:
|
||||
return self.startPoint.distance_to(self.plane.origin)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
start_to_mid = self.startPoint.distance_to(self.midPoint)
|
||||
mid_to_end = self.midPoint.distance_to(self.endPoint)
|
||||
r = self.radius
|
||||
angle = (2 * math.asin(start_to_mid / (2 * r))) + \
|
||||
(2 * math.asin(mid_to_end / (2 * r)))
|
||||
return r * angle
|
||||
|
||||
@property
|
||||
def measure(self) -> float:
|
||||
start_to_mid = self.startPoint.distance_to(self.midPoint)
|
||||
mid_to_end = self.midPoint.distance_to(self.endPoint)
|
||||
r = self.radius
|
||||
return (2 * math.asin(start_to_mid / (2 * r))) + \
|
||||
(2 * math.asin(mid_to_end / (2 * r)))
|
||||
@@ -0,0 +1,20 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import ICurve, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Line(
|
||||
Base,
|
||||
IHasUnits,
|
||||
ICurve,
|
||||
speckle_type="Objects.Geometry.Line"
|
||||
):
|
||||
start: Point
|
||||
end: Point
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.start.distance_to(self.end)
|
||||
@@ -0,0 +1,211 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Tuple
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import IHasArea, IHasUnits, IHasVolume
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Mesh(
|
||||
Base,
|
||||
IHasArea,
|
||||
IHasVolume,
|
||||
IHasUnits,
|
||||
speckle_type="Objects.Geometry.Mesh",
|
||||
detachable={"vertices", "faces", "colors", "textureCoordinates"},
|
||||
chunkable={
|
||||
"vertices": 31250,
|
||||
"faces": 62500,
|
||||
"colors": 62500,
|
||||
"textureCoordinates": 31250,
|
||||
},
|
||||
serialize_ignore={"vertices_count", "texture_coordinates_count"},
|
||||
):
|
||||
"""
|
||||
a 3D mesh consisting of vertices and faces with optional colors and texture coordinates
|
||||
"""
|
||||
vertices: List[float]
|
||||
faces: List[int]
|
||||
colors: List[int] = field(default_factory=list)
|
||||
textureCoordinates: List[float] = field(default_factory=list)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"vertices: {self.vertices_count}, "
|
||||
f"faces: {self.faces_count}, "
|
||||
f"units: {self.units}, "
|
||||
f"has_colors: {len(self.colors) > 0}, "
|
||||
f"has_texture_coords: {len(self.textureCoordinates) > 0})"
|
||||
)
|
||||
|
||||
@property
|
||||
def vertices_count(self) -> int:
|
||||
"""
|
||||
get the number of vertices in the mesh
|
||||
"""
|
||||
|
||||
if len(self.vertices) % 3 != 0:
|
||||
raise ValueError(
|
||||
f"Invalid vertices list: length ({len(
|
||||
self.vertices)}) must be a multiple of 3"
|
||||
)
|
||||
return len(self.vertices) // 3
|
||||
|
||||
@property
|
||||
def texture_coordinates_count(self) -> int:
|
||||
"""
|
||||
get the number of texture coordinates in the mesh
|
||||
"""
|
||||
return len(self.textureCoordinates) // 2
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get('_area', 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__['_area'] = value
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return self.__dict__.get('_volume', 0.0)
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value: float) -> None:
|
||||
self.__dict__['_volume'] = value
|
||||
|
||||
def calculate_area(self) -> float:
|
||||
"""
|
||||
calculate total surface area of the mesh
|
||||
"""
|
||||
total_area = 0.0
|
||||
face_index = 0
|
||||
i = 0
|
||||
|
||||
while i < len(self.faces):
|
||||
vertex_count = self.faces[i]
|
||||
if vertex_count >= 3:
|
||||
face_vertices = self.get_face_vertices(face_index)
|
||||
for j in range(1, vertex_count - 1):
|
||||
v0 = face_vertices[0]
|
||||
v1 = face_vertices[j]
|
||||
v2 = face_vertices[j + 1]
|
||||
a = [v1.x - v0.x, v1.y - v0.y, v1.z - v0.z]
|
||||
b = [v2.x - v0.x, v2.y - v0.y, v2.z - v0.z]
|
||||
cx = a[1] * b[2] - a[2] * b[1]
|
||||
cy = a[2] * b[0] - a[0] * b[2]
|
||||
cz = a[0] * b[1] - a[1] * b[0]
|
||||
area = 0.5 * (cx * cx + cy * cy + cz * cz) ** 0.5
|
||||
total_area += area
|
||||
i += vertex_count + 1
|
||||
face_index += 1
|
||||
|
||||
return total_area
|
||||
|
||||
def calculate_volume(self) -> float:
|
||||
"""
|
||||
calculate volume of the mesh if it is closed
|
||||
"""
|
||||
if not self.is_closed():
|
||||
return 0.0
|
||||
|
||||
total_volume = 0.0
|
||||
face_index = 0
|
||||
i = 0
|
||||
while i < len(self.faces):
|
||||
vertex_count = self.faces[i]
|
||||
if vertex_count >= 3:
|
||||
face_vertices = self.get_face_vertices(face_index)
|
||||
v0 = face_vertices[0]
|
||||
for j in range(1, vertex_count - 1):
|
||||
v1 = face_vertices[j]
|
||||
v2 = face_vertices[j + 1]
|
||||
a = [v0.x, v0.y, v0.z]
|
||||
b = [v1.x - v0.x, v1.y - v0.y, v1.z - v0.z]
|
||||
c = [v2.x - v0.x, v2.y - v0.y, v2.z - v0.z]
|
||||
cx = b[1] * c[2] - b[2] * c[1]
|
||||
cy = b[2] * c[0] - b[0] * c[2]
|
||||
cz = b[0] * c[1] - b[1] * c[0]
|
||||
v = (a[0] * cx + a[1] * cy + a[2] * cz) / 6.0
|
||||
total_volume += v
|
||||
i += vertex_count + 1
|
||||
face_index += 1
|
||||
|
||||
return abs(total_volume)
|
||||
|
||||
def get_point(self, index: int) -> Point:
|
||||
"""
|
||||
get vertex at index as a Point object
|
||||
"""
|
||||
if index < 0 or index >= self.vertices_count:
|
||||
raise IndexError(f"Vertex index {index} out of range")
|
||||
|
||||
index *= 3
|
||||
return Point(
|
||||
x=self.vertices[index],
|
||||
y=self.vertices[index + 1],
|
||||
z=self.vertices[index + 2],
|
||||
units=self.units,
|
||||
)
|
||||
|
||||
def get_points(self) -> List[Point]:
|
||||
"""
|
||||
get all vertices as Point objects
|
||||
"""
|
||||
return [self.get_point(i) for i in range(self.vertices_count)]
|
||||
|
||||
def get_texture_coordinate(self, index: int) -> Tuple[float, float]:
|
||||
"""
|
||||
get texture coordinate at index
|
||||
"""
|
||||
if index < 0 or index >= self.texture_coordinates_count:
|
||||
raise IndexError(f"Texture coordinate index {index} out of range")
|
||||
|
||||
index *= 2
|
||||
return (self.textureCoordinates[index], self.textureCoordinates[index + 1])
|
||||
|
||||
def get_face_vertices(self, face_index: int) -> List[Point]:
|
||||
"""
|
||||
get the vertices of a specific face
|
||||
"""
|
||||
i = 0
|
||||
current_face = 0
|
||||
|
||||
while i < len(self.faces):
|
||||
if current_face == face_index:
|
||||
vertex_count = self.faces[i]
|
||||
vertices = []
|
||||
for j in range(vertex_count):
|
||||
vertex_index = self.faces[i + j + 1]
|
||||
if vertex_index >= self.vertices_count:
|
||||
raise IndexError(
|
||||
f"Vertex index {vertex_index} out of range")
|
||||
vertices.append(self.get_point(vertex_index))
|
||||
return vertices
|
||||
|
||||
vertex_count = self.faces[i]
|
||||
i += vertex_count + 1
|
||||
current_face += 1
|
||||
|
||||
raise IndexError(f"Face index {face_index} out of range")
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
"""
|
||||
check if the mesh is closed (verifying each edge appears twice)
|
||||
"""
|
||||
edge_counts = {}
|
||||
|
||||
i = 0
|
||||
while i < len(self.faces):
|
||||
vertex_count = self.faces[i]
|
||||
for j in range(vertex_count):
|
||||
v1 = self.faces[i + 1 + j]
|
||||
v2 = self.faces[i + 1 + ((j + 1) % vertex_count)]
|
||||
edge = tuple(sorted([v1, v2]))
|
||||
edge_counts[edge] = edge_counts.get(edge, 0) + 1
|
||||
|
||||
i += vertex_count + 1
|
||||
|
||||
return all(count == 2 for count in edge_counts.values())
|
||||
@@ -0,0 +1,28 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.geometry.vector import Vector
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Plane(Base, IHasUnits, speckle_type="Objects.Geometry.Plane"):
|
||||
"""
|
||||
a plane consisting of an origin Point, and 3 Vectors as its X, Y and Z axis.
|
||||
"""
|
||||
|
||||
origin: Point
|
||||
normal: Vector
|
||||
xdir: Vector
|
||||
ydir: Vector
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"origin: {self.origin}, "
|
||||
f"normal: {self.normal}, "
|
||||
f"xdir: {self.xdir}, "
|
||||
f"ydir: {self.ydir}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Point(Base, IHasUnits, speckle_type="Objects.Geometry.Point"):
|
||||
"""
|
||||
a 3-dimensional point
|
||||
"""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})"
|
||||
|
||||
def distance_to(self, other: "Point") -> float:
|
||||
"""
|
||||
calculates the distance between this point and another given point.
|
||||
"""
|
||||
if not isinstance(other, Point):
|
||||
raise TypeError(f"Expected Point object, got {type(other)}")
|
||||
|
||||
# we assume that host application units are the same for both points
|
||||
# unit conversion could be expensive, so we avoid it here
|
||||
dx = other.x - self.x
|
||||
dy = other.y - self.y
|
||||
dz = other.z - self.z
|
||||
|
||||
return (dx * dx + dy * dy + dz * dz) ** 0.5
|
||||
@@ -0,0 +1,76 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import ICurve, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"):
|
||||
"""
|
||||
a polyline curve, defined by a set of vertices.
|
||||
"""
|
||||
value: List[float]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(value: {self.value}, units: {self.units})"
|
||||
|
||||
def is_closed(self, tolerance: float = 1e-6) -> bool:
|
||||
"""
|
||||
check if the polyline is closed (start point equals end point within tolerance)
|
||||
"""
|
||||
if len(self.value) < 6: # need at least 2 points to be closed
|
||||
return False
|
||||
|
||||
# compare first and last points
|
||||
start = Point(
|
||||
x=self.value[0],
|
||||
y=self.value[1],
|
||||
z=self.value[2],
|
||||
units=self.units
|
||||
)
|
||||
end = Point(
|
||||
x=self.value[-3],
|
||||
y=self.value[-2],
|
||||
z=self.value[-1],
|
||||
units=self.units
|
||||
)
|
||||
return start.distance_to(end) <= tolerance
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get('_length', 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__['_length'] = value
|
||||
|
||||
def calculate_length(self) -> float:
|
||||
points = self.get_points()
|
||||
total_length = 0.0
|
||||
for i in range(len(points) - 1):
|
||||
total_length += points[i].distance_to(points[i + 1])
|
||||
if self.is_closed() and points:
|
||||
total_length += points[-1].distance_to(points[0])
|
||||
return total_length
|
||||
|
||||
def get_points(self) -> List[Point]:
|
||||
"""
|
||||
converts the raw coordinate list into Point objects
|
||||
"""
|
||||
if len(self.value) % 3 != 0:
|
||||
raise ValueError(
|
||||
"Polyline value list is malformed: expected length to be multiple of 3"
|
||||
)
|
||||
|
||||
points = []
|
||||
for i in range(0, len(self.value), 3):
|
||||
point = Point(
|
||||
x=self.value[i],
|
||||
y=self.value[i + 1],
|
||||
z=self.value[i + 2],
|
||||
units=self.units
|
||||
)
|
||||
points.append(point)
|
||||
return points
|
||||
@@ -0,0 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Vector(Base, IHasUnits, speckle_type="Objects.Geometry.Vector", serialize_ignore = {"length"}):
|
||||
"""
|
||||
a 3-dimensional vector
|
||||
"""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})"
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5
|
||||
@@ -58,9 +58,7 @@ class CommitObjectBuilder(ABC, Generic[T]):
|
||||
if parent_id == ROOT:
|
||||
parent = root_commit_object
|
||||
else:
|
||||
parent = (
|
||||
self.converted[parent_id] if parent_id in self.converted else None
|
||||
)
|
||||
parent = self.converted.get(parent_id, None)
|
||||
|
||||
if not parent:
|
||||
continue
|
||||
@@ -74,13 +72,15 @@ class CommitObjectBuilder(ABC, Generic[T]):
|
||||
elements.append(current)
|
||||
return
|
||||
except Exception as ex:
|
||||
# A parent was found, but it was invalid (Likely because of a type mismatch on a `elements` property)
|
||||
# A parent was found, but it was invalid
|
||||
# (Likely because of a type mismatch on a `elements` property)
|
||||
print(
|
||||
f"Failed to add object {type(current)} to a converted parent; {ex}"
|
||||
)
|
||||
|
||||
raise Exception(
|
||||
f"Could not find a valid parent for object of type {type(current)}. Checked {len(parents)} potential parent, and non were converted!"
|
||||
f"Could not find a valid parent for object of type {type(current)}."
|
||||
f"Checked {len(parents)} potential parent, and non were converted!"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.graph_traversal.traversal import GraphTraversal, TraversalRule
|
||||
|
||||
DISPLAY_VALUE_PROPERTY_ALIASES = {"displayValue", "@displayValue"}
|
||||
ELEMENTS_PROPERTY_ALIASES = {"elements", "@elements"}
|
||||
|
||||
|
||||
def has_display_value(x: Base):
|
||||
return any(hasattr(x, alias) for alias in DISPLAY_VALUE_PROPERTY_ALIASES)
|
||||
|
||||
|
||||
def create_default_traversal_function() -> GraphTraversal:
|
||||
"""
|
||||
Traversal func for traversing the root object of a Speckle Model
|
||||
"""
|
||||
|
||||
convertible_rule = TraversalRule(
|
||||
[lambda b: b.speckle_type != "Base", has_display_value],
|
||||
lambda _: ELEMENTS_PROPERTY_ALIASES,
|
||||
)
|
||||
|
||||
default_rule = TraversalRule(
|
||||
[lambda _: True],
|
||||
# NOTE: Unlike the C# implementation, this does not ignore Obsolete members
|
||||
lambda o: o.get_member_names(),
|
||||
False,
|
||||
)
|
||||
|
||||
return GraphTraversal([convertible_rule, default_rule])
|
||||
@@ -7,6 +7,10 @@ from specklepy.objects.base import Base
|
||||
|
||||
|
||||
class ITraversalRule(Protocol):
|
||||
@property
|
||||
def should_return(self) -> bool:
|
||||
pass
|
||||
|
||||
def get_members_to_traverse(self, o: Base) -> Set[str]:
|
||||
"""Get the members to traverse."""
|
||||
pass
|
||||
@@ -50,20 +54,27 @@ class GraphTraversal:
|
||||
|
||||
while len(stack) > 0:
|
||||
head = stack.pop()
|
||||
yield head
|
||||
|
||||
current = head.current
|
||||
active_rule = self._get_active_rule_or_default_rule(current)
|
||||
|
||||
if active_rule.should_return:
|
||||
yield head
|
||||
|
||||
members_to_traverse = active_rule.get_members_to_traverse(current)
|
||||
for child_prop in members_to_traverse:
|
||||
try:
|
||||
if child_prop in {"speckle_type", "units", "applicationId"}:
|
||||
continue # debug: to avoid noisy exceptions, explicitly avoid checking ones we know will fail, this is not exhaustive
|
||||
# debug: to avoid noisy exceptions,
|
||||
# explicitly avoid checking ones we know will fail,
|
||||
# this is not exhaustive
|
||||
continue
|
||||
if getattr(current, child_prop, None):
|
||||
value = current[child_prop]
|
||||
self._traverse_member_to_stack(stack, value, child_prop, head)
|
||||
except KeyError:
|
||||
# Unset application ids, and class variables like SpeckleType will throw when __getitem__ is called
|
||||
# Unset application ids, and class variables like SpeckleType will
|
||||
# throw when __getitem__ is called
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@@ -114,12 +125,14 @@ class GraphTraversal:
|
||||
class TraversalRule:
|
||||
_conditions: Collection[Callable[[Base], bool]]
|
||||
_members_to_traverse: Callable[[Base], Iterable[str]]
|
||||
_should_return_to_output: bool = True
|
||||
|
||||
@property
|
||||
def should_return(self) -> bool:
|
||||
return self._should_return_to_output
|
||||
|
||||
def get_members_to_traverse(self, o: Base) -> Set[str]:
|
||||
return set(self._members_to_traverse(o))
|
||||
|
||||
def does_rule_hold(self, o: Base) -> bool:
|
||||
for condition in self._conditions:
|
||||
if condition(o):
|
||||
return True
|
||||
return False
|
||||
return any(condition(o) for condition in self._conditions)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Generic, List, TypeVar
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleInvalidUnitException
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
T = TypeVar("T") # define type variable for generic type
|
||||
|
||||
|
||||
# generic interfaces
|
||||
@dataclass(kw_only=True)
|
||||
class ICurve(metaclass=ABCMeta):
|
||||
_domain: Interval = field(default_factory=Interval.unit_interval, init=False)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def length(self) -> float:
|
||||
pass
|
||||
|
||||
@property
|
||||
def domain(self) -> Interval:
|
||||
return self._domain
|
||||
|
||||
@domain.setter
|
||||
def domain(self, value: Interval) -> None:
|
||||
if not isinstance(value, Interval):
|
||||
raise TypeError(f"Domain must be an Interval, got {type(value)}")
|
||||
self._domain = value
|
||||
|
||||
|
||||
class IDisplayValue(Generic[T], metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def displayValue(self) -> T:
|
||||
pass
|
||||
|
||||
|
||||
# field interfaces
|
||||
@dataclass(kw_only=True)
|
||||
class IHasUnits(metaclass=ABCMeta):
|
||||
|
||||
units: str | Units
|
||||
_units: str = field(repr=False, init=False)
|
||||
|
||||
@property
|
||||
def units(self) -> str:
|
||||
return self._units
|
||||
|
||||
@units.setter
|
||||
def units(self, value: str | Units):
|
||||
if isinstance(value, str):
|
||||
self._units = value
|
||||
elif isinstance(value, Units):
|
||||
self._units = value.value
|
||||
else:
|
||||
raise SpeckleInvalidUnitException(
|
||||
f"Unknown type {type(value)} received for units"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasArea(metaclass=ABCMeta):
|
||||
|
||||
_area: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self._area
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
raise ValueError(f"Area must be a number, got {type(value)}")
|
||||
self._area = float(value)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasVolume(metaclass=ABCMeta):
|
||||
|
||||
_volume: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return self._volume
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
raise ValueError(f"Volume must be a number, got {type(value)}")
|
||||
self._volume = float(value)
|
||||
|
||||
|
||||
# data object interfaces
|
||||
class IProperties(metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def properties(self) -> Dict[str, object]:
|
||||
pass
|
||||
|
||||
|
||||
class IDataObject(IProperties, IDisplayValue[List[Base]], metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class IBlenderObject(IDataObject, metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def type(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class IGisObject(IDataObject, metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def type(self) -> str:
|
||||
pass
|
||||
@@ -0,0 +1,37 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Collection(
|
||||
Base,
|
||||
# TODO: add deprecated speckle_types
|
||||
speckle_type="Speckle.Core.Models.Collections.Collection",
|
||||
detachable={"elements"},
|
||||
):
|
||||
"""
|
||||
A simple container for organising objects within a model
|
||||
and preserving object hierarchy.
|
||||
|
||||
A container is defined by a human-readable name a unique applicationId and
|
||||
its list of contained elements.
|
||||
The elements can include an unrestricted number of Base objects including
|
||||
additional nested Collections.
|
||||
|
||||
Note:
|
||||
A Collection can be for example a Layer in Rhino/AutoCad,
|
||||
a collection in Blender, or a Category in Revit.
|
||||
The location of each collection in the hierarchy of collections in a commit
|
||||
will be retrieved through commit traversal.
|
||||
|
||||
Attributes:
|
||||
name: The human-readable name of the Collection. This name is not necessarily
|
||||
unique within a commit. Set the applicationId for a unique identifier.
|
||||
elements: The elements contained in this Collection.
|
||||
This may include additional nested Collections
|
||||
"""
|
||||
|
||||
name: str
|
||||
elements: List[Base] = field(default_factory=list)
|
||||
@@ -0,0 +1,18 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class ISpeckleObject(speckle_type="ISpeckleObjects", metaclass=ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def id(self) -> str | None:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def application_id(self) -> str | None:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def speckle_type(self) -> str:
|
||||
pass
|
||||
@@ -1,311 +0,0 @@
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.objects.geometry import Plane, Point, Polyline, Vector
|
||||
|
||||
from .base import Base
|
||||
|
||||
OTHER = "Objects.Other."
|
||||
OTHER_REVIT = OTHER + "Revit."
|
||||
|
||||
IDENTITY_TRANSFORM = [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
]
|
||||
|
||||
|
||||
class Material(Base, speckle_type=OTHER + "Material"):
|
||||
"""Generic class for materials containing generic parameters."""
|
||||
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class RevitMaterial(Material, speckle_type="Objects.Other.Revit." + "RevitMaterial"):
|
||||
materialCategory: Optional[str] = None
|
||||
materialClass: Optional[str] = None
|
||||
shininess: Optional[int] = None
|
||||
smoothness: Optional[int] = None
|
||||
transparency: Optional[int] = None
|
||||
parameters: Optional[Base] = None
|
||||
|
||||
|
||||
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
||||
name: Optional[str] = None
|
||||
opacity: float = 1
|
||||
metalness: float = 0
|
||||
roughness: float = 1
|
||||
diffuse: int = -2894893 # light gray arbg
|
||||
emissive: int = -16777216 # black arbg
|
||||
|
||||
|
||||
class MaterialQuantity(Base, speckle_type=OTHER + "MaterialQuantity"):
|
||||
material: Optional[Material] = None
|
||||
volume: Optional[float] = None
|
||||
area: Optional[float] = None
|
||||
|
||||
|
||||
class DisplayStyle(Base, speckle_type=OTHER + "DisplayStyle"):
|
||||
"""
|
||||
Minimal display style class.
|
||||
Developed primarily for display styles in Rhino and AutoCAD.
|
||||
Rhino object attributes uses OpenNURBS definition for linetypes and lineweights.
|
||||
"""
|
||||
|
||||
name: Optional[str] = None
|
||||
color: int = -2894893 # light gray arbg
|
||||
linetype: Optional[str] = None
|
||||
lineweight: float = 0
|
||||
|
||||
|
||||
class Text(Base, speckle_type=OTHER + "Text"):
|
||||
"""
|
||||
Text object to render it on viewer.
|
||||
"""
|
||||
|
||||
plane: Plane
|
||||
value: str
|
||||
height: float
|
||||
rotation: float
|
||||
displayValue: Optional[List[Polyline]] = None
|
||||
richText: Optional[str] = None
|
||||
|
||||
|
||||
class Transform(
|
||||
Base,
|
||||
speckle_type=OTHER + "Transform",
|
||||
serialize_ignore={"translation", "scaling", "is_identity", "value"},
|
||||
):
|
||||
"""The 4x4 transformation matrix
|
||||
|
||||
The 3x3 sub-matrix determines scaling.
|
||||
The 4th column defines translation,
|
||||
where the last value is a divisor (usually equal to 1).
|
||||
"""
|
||||
|
||||
_value: Optional[List[float]] = None
|
||||
|
||||
@property
|
||||
@deprecated(version="2.12", reason="Use matrix")
|
||||
def value(self) -> List[float]:
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value: List[float]) -> None:
|
||||
self.matrix = value
|
||||
|
||||
@property
|
||||
def matrix(self) -> List[float]:
|
||||
"""The transform matrix represented as a flat list of 16 floats"""
|
||||
return self._value
|
||||
|
||||
@matrix.setter
|
||||
def matrix(self, value: List[float]) -> None:
|
||||
try:
|
||||
value = [float(x) for x in value]
|
||||
except (ValueError, TypeError) as error:
|
||||
raise ValueError(
|
||||
"Could not create a Transform object with the requested value. Input"
|
||||
f" must be a 16 element list of numbers. Value provided: {value}"
|
||||
) from error
|
||||
|
||||
if len(value) != 16:
|
||||
raise ValueError(
|
||||
"Could not create a Transform object: input list should be 16 floats"
|
||||
f" long, but was {len(value)} long"
|
||||
)
|
||||
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def translation(self) -> List[float]:
|
||||
"""The final column of the matrix which defines the translation"""
|
||||
return [self._value[i] for i in (3, 7, 11, 15)]
|
||||
|
||||
@property
|
||||
def scaling(self) -> List[float]:
|
||||
"""The 3x3 scaling sub-matrix"""
|
||||
return [self._value[i] for i in (0, 1, 2, 4, 5, 6, 8, 9, 10)]
|
||||
|
||||
@property
|
||||
def is_identity(self) -> bool:
|
||||
return self._value == IDENTITY_TRANSFORM
|
||||
|
||||
def apply_to_point(self, point: Point) -> Point:
|
||||
"""Transform a single speckle Point
|
||||
|
||||
Arguments:
|
||||
point {Point} -- the speckle Point to transform
|
||||
|
||||
Returns:
|
||||
Point -- a new transformed point
|
||||
"""
|
||||
coords = self.apply_to_point_value([point.x, point.y, point.z])
|
||||
return Point(x=coords[0], y=coords[1], z=coords[2], units=point.units)
|
||||
|
||||
def apply_to_point_value(self, point_value: List[float]) -> List[float]:
|
||||
"""Transform a list of three floats representing a point
|
||||
|
||||
Arguments:
|
||||
point_value {List[float]} -- a list of 3 floats
|
||||
|
||||
Returns:
|
||||
List[float] -- the list with the transform applied
|
||||
"""
|
||||
transformed = [
|
||||
point_value[0] * self._value[i]
|
||||
+ point_value[1] * self._value[i + 1]
|
||||
+ point_value[2] * self._value[i + 2]
|
||||
+ self._value[i + 3]
|
||||
for i in range(0, 15, 4)
|
||||
]
|
||||
|
||||
return [transformed[i] / transformed[3] for i in range(3)]
|
||||
|
||||
def apply_to_points(self, points: List[Point]) -> List[Point]:
|
||||
"""Transform a list of speckle Points
|
||||
|
||||
Arguments:
|
||||
points {List[Point]} -- the list of speckle Points to transform
|
||||
|
||||
Returns:
|
||||
List[Point] -- a new list of transformed points
|
||||
"""
|
||||
return [self.apply_to_point(point) for point in points]
|
||||
|
||||
def apply_to_points_values(self, points_value: List[float]) -> List[float]:
|
||||
"""Transform a list of speckle Points
|
||||
|
||||
Arguments:
|
||||
points {List[float]}
|
||||
-- a flat list of floats representing points to transform
|
||||
|
||||
Returns:
|
||||
List[float] -- a new transformed list
|
||||
"""
|
||||
if len(points_value) % 3 != 0:
|
||||
raise ValueError(
|
||||
"Cannot apply transform as the points list is malformed: expected"
|
||||
" length to be multiple of 3"
|
||||
)
|
||||
transformed = []
|
||||
for i in range(0, len(points_value), 3):
|
||||
transformed.extend(self.apply_to_point_value(points_value[i : i + 3]))
|
||||
|
||||
return transformed
|
||||
|
||||
def apply_to_vector(self, vector: Vector) -> Vector:
|
||||
"""Transform a single speckle Vector
|
||||
|
||||
Arguments:
|
||||
point {Vector} -- the speckle Vector to transform
|
||||
|
||||
Returns:
|
||||
Vector -- a new transformed point
|
||||
"""
|
||||
coords = self.apply_to_vector_value([vector.x, vector.y, vector.z])
|
||||
return Vector(x=coords[0], y=coords[1], z=coords[2], units=vector.units)
|
||||
|
||||
def apply_to_vector_value(self, vector_value: List[float]) -> List[float]:
|
||||
"""Transform a list of three floats representing a vector
|
||||
|
||||
Arguments:
|
||||
vector_value {List[float]} -- a list of 3 floats
|
||||
|
||||
Returns:
|
||||
List[float] -- the list with the transform applied
|
||||
"""
|
||||
return [
|
||||
vector_value[0] * self._value[i]
|
||||
+ vector_value[1] * self._value[i + 1]
|
||||
+ vector_value[2] * self._value[i + 2]
|
||||
for i in range(0, 15, 4)
|
||||
][:3]
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, value: Optional[List[float]] = None) -> "Transform":
|
||||
"""Returns a Transform object from a list of 16 numbers.
|
||||
If no value is provided, an identity transform will be returned.
|
||||
|
||||
Arguments:
|
||||
value {List[float]} -- the matrix as a flat list of 16 numbers
|
||||
(defaults to the identity transform)
|
||||
|
||||
Returns:
|
||||
Transform -- a complete transform object
|
||||
"""
|
||||
if not value:
|
||||
value = IDENTITY_TRANSFORM
|
||||
return cls(value=value)
|
||||
|
||||
|
||||
class BlockDefinition(
|
||||
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
|
||||
):
|
||||
name: Optional[str] = None
|
||||
basePoint: Optional[Point] = None
|
||||
geometry: Optional[List[Base]] = None
|
||||
|
||||
|
||||
class Instance(Base, speckle_type=OTHER + "Instance", detachable={"definition"}):
|
||||
transform: Optional[Transform] = None
|
||||
definition: Optional[Base] = None
|
||||
|
||||
|
||||
class BlockInstance(
|
||||
Instance, speckle_type=OTHER + "BlockInstance", serialize_ignore={"blockDefinition"}
|
||||
):
|
||||
@property
|
||||
@deprecated(version="2.13", reason="Use definition")
|
||||
def blockDefinition(self) -> Optional[BlockDefinition]:
|
||||
if isinstance(self.definition, BlockDefinition):
|
||||
return self.definition
|
||||
return None
|
||||
|
||||
@blockDefinition.setter
|
||||
def blockDefinition(self, value: Optional[BlockDefinition]) -> None:
|
||||
self.definition = value
|
||||
|
||||
|
||||
class RevitInstance(Instance, speckle_type=OTHER_REVIT + "RevitInstance"):
|
||||
level: Optional[Base] = None
|
||||
facingFlipped: bool
|
||||
handFlipped: bool
|
||||
parameters: Optional[Base] = None
|
||||
elementId: Optional[str]
|
||||
|
||||
|
||||
# TODO: prob move this into a built elements module, but just trialling this for now
|
||||
class RevitParameter(Base, speckle_type="Objects.BuiltElements.Revit.Parameter"):
|
||||
name: Optional[str] = None
|
||||
value: Any = None
|
||||
applicationUnitType: Optional[str] = None # eg UnitType UT_Length
|
||||
applicationUnit: Optional[str] = None # DisplayUnitType eg DUT_MILLIMITERS
|
||||
applicationInternalName: Optional[
|
||||
str
|
||||
] = None # BuiltInParameterName or GUID for shared parameter
|
||||
isShared: bool = False
|
||||
isReadOnly: bool = False
|
||||
isTypeParameter: bool = False
|
||||
|
||||
|
||||
class Collection(
|
||||
Base, speckle_type="Speckle.Core.Models.Collection", detachable={"elements"}
|
||||
):
|
||||
name: Optional[str] = None
|
||||
collectionType: Optional[str] = None
|
||||
elements: Optional[List[Base]] = None
|
||||
@@ -1,25 +1,21 @@
|
||||
from typing import Any, List
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
NAMESPACE = "Objects.Primitive"
|
||||
|
||||
|
||||
class Interval(Base, speckle_type=f"{NAMESPACE}.Interval"):
|
||||
@dataclass(kw_only=True)
|
||||
class Interval(Base, speckle_type="Objects.Primitive.Interval", serialize_ignore={"length"}):
|
||||
start: float = 0.0
|
||||
end: float = 0.0
|
||||
|
||||
def length(self):
|
||||
return abs(self.start - self.end)
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(start: {self.start}, end: {self.end})"
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
abs(self.end - self.start)
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, args: List[Any]) -> "Interval":
|
||||
return cls(start=args[0], end=args[1])
|
||||
|
||||
def to_list(self) -> List[Any]:
|
||||
return [self.start, self.end]
|
||||
|
||||
|
||||
class Interval2d(Base, speckle_type=f"{NAMESPACE}.Interval2d"):
|
||||
u: Interval
|
||||
v: Interval
|
||||
def unit_interval(cls) -> "Interval":
|
||||
interval = cls(start=0, end=1)
|
||||
return interval
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ColorProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.ColorProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
value: int
|
||||
name: Optional[str]
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class GroupProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.GroupProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
name: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class InstanceProxy(
|
||||
Base,
|
||||
IHasUnits,
|
||||
speckle_type="Models.Proxies.InstanceProxy",
|
||||
):
|
||||
definition_id: str
|
||||
transform: List[float]
|
||||
max_depth: int
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class InstanceDefinitionProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.InstanceDefinitionProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
max_depth: int
|
||||
name: str
|
||||
@@ -1,142 +0,0 @@
|
||||
"""Builtin Speckle object kit."""
|
||||
|
||||
from specklepy.objects.structural.analysis import (
|
||||
Model,
|
||||
ModelInfo,
|
||||
ModelSettings,
|
||||
ModelUnits,
|
||||
)
|
||||
from specklepy.objects.structural.axis import Axis, AxisType
|
||||
from specklepy.objects.structural.geometry import (
|
||||
Element1D,
|
||||
Element2D,
|
||||
Element3D,
|
||||
ElementType1D,
|
||||
ElementType2D,
|
||||
ElementType3D,
|
||||
Node,
|
||||
Restraint,
|
||||
)
|
||||
from specklepy.objects.structural.loading import (
|
||||
ActionType,
|
||||
BeamLoadType,
|
||||
CombinationType,
|
||||
FaceLoadType,
|
||||
Load,
|
||||
LoadAxisType,
|
||||
LoadBeam,
|
||||
LoadCase,
|
||||
LoadCombinations,
|
||||
LoadDirection,
|
||||
LoadDirection2D,
|
||||
LoadFace,
|
||||
LoadGravity,
|
||||
LoadNode,
|
||||
LoadType,
|
||||
)
|
||||
from specklepy.objects.structural.materials import (
|
||||
Concrete,
|
||||
MaterialType,
|
||||
Steel,
|
||||
StructuralMaterial,
|
||||
Timber,
|
||||
)
|
||||
from specklepy.objects.structural.properties import (
|
||||
BaseReferencePoint,
|
||||
MemberType,
|
||||
Property,
|
||||
Property1D,
|
||||
Property2D,
|
||||
Property3D,
|
||||
PropertyDamper,
|
||||
PropertyMass,
|
||||
PropertySpring,
|
||||
PropertyType2D,
|
||||
PropertyType3D,
|
||||
PropertyTypeDamper,
|
||||
PropertyTypeSpring,
|
||||
ReferenceSurface,
|
||||
ReferenceSurfaceEnum,
|
||||
SectionProfile,
|
||||
ShapeType,
|
||||
shapeType,
|
||||
)
|
||||
from specklepy.objects.structural.results import (
|
||||
Result,
|
||||
Result1D,
|
||||
Result2D,
|
||||
Result3D,
|
||||
ResultGlobal,
|
||||
ResultNode,
|
||||
ResultSet1D,
|
||||
ResultSet2D,
|
||||
ResultSet3D,
|
||||
ResultSetAll,
|
||||
ResultSetNode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Element1D",
|
||||
"Element2D",
|
||||
"Element3D",
|
||||
"ElementType1D",
|
||||
"ElementType2D",
|
||||
"ElementType3D",
|
||||
"AxisType",
|
||||
"Axis",
|
||||
"Node",
|
||||
"Restraint",
|
||||
"Load",
|
||||
"LoadType",
|
||||
"ActionType",
|
||||
"BeamLoadType",
|
||||
"FaceLoadType",
|
||||
"LoadDirection",
|
||||
"LoadDirection2D",
|
||||
"LoadAxisType",
|
||||
"CombinationType",
|
||||
"LoadBeam",
|
||||
"LoadCase",
|
||||
"LoadCombinations",
|
||||
"LoadFace",
|
||||
"LoadGravity",
|
||||
"LoadNode",
|
||||
"Model",
|
||||
"ModelInfo",
|
||||
"ModelSettings",
|
||||
"ModelUnits",
|
||||
"MaterialType",
|
||||
"Concrete",
|
||||
"StructuralMaterial",
|
||||
"Steel",
|
||||
"Timber",
|
||||
"Property",
|
||||
"Property1D",
|
||||
"Property2D",
|
||||
"Property3D",
|
||||
"PropertyDamper",
|
||||
"PropertyMass",
|
||||
"PropertySpring",
|
||||
"SectionProfile",
|
||||
"MemberType",
|
||||
"BaseReferencePoint",
|
||||
"ReferenceSurface",
|
||||
"PropertyType2D",
|
||||
"PropertyType3D",
|
||||
"ShapeType",
|
||||
"PropertyTypeSpring",
|
||||
"PropertyTypeDamper",
|
||||
"ReferenceSurfaceEnum",
|
||||
"shapeType",
|
||||
"Result",
|
||||
"Result1D",
|
||||
"ResultSet1D",
|
||||
"Result2D",
|
||||
"ResultSet2D",
|
||||
"Result3D",
|
||||
"ResultSet3D",
|
||||
"ResultGlobal",
|
||||
"ResultSetNode",
|
||||
"ResultNode",
|
||||
"ResultSetAll",
|
||||
]
|
||||
@@ -1,49 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis."
|
||||
|
||||
|
||||
class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"):
|
||||
length: Optional[str] = None
|
||||
sections: Optional[str] = None
|
||||
displacements: Optional[str] = None
|
||||
stress: Optional[str] = None
|
||||
force: Optional[str] = None
|
||||
mass: Optional[str] = None
|
||||
time: Optional[str] = None
|
||||
temperature: Optional[str] = None
|
||||
velocity: Optional[str] = None
|
||||
acceleration: Optional[str] = None
|
||||
energy: Optional[str] = None
|
||||
angle: Optional[str] = None
|
||||
strain: Optional[str] = None
|
||||
|
||||
|
||||
class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"):
|
||||
modelUnits: Optional[ModelUnits] = None
|
||||
steelCode: Optional[str] = None
|
||||
concreteCode: Optional[str] = None
|
||||
coincidenceTolerance: float = 0.0
|
||||
|
||||
|
||||
class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
projectNumber: Optional[str] = None
|
||||
projectName: Optional[str] = None
|
||||
settings: Optional[ModelSettings] = None
|
||||
initials: Optional[str] = None
|
||||
application: Optional[str] = None
|
||||
|
||||
|
||||
class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"):
|
||||
specs: Optional[ModelInfo] = None
|
||||
nodes: Optional[List] = None
|
||||
elements: Optional[List] = None
|
||||
loads: Optional[List] = None
|
||||
restraints: Optional[List] = None
|
||||
properties: Optional[List] = None
|
||||
materials: Optional[List] = None
|
||||
layerDescription: Optional[str] = None
|
||||
@@ -1,17 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry import Plane
|
||||
|
||||
|
||||
class AxisType(int, Enum):
|
||||
Cartesian = 0
|
||||
Cylindrical = 1
|
||||
Spherical = 2
|
||||
|
||||
|
||||
class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"):
|
||||
name: Optional[str] = None
|
||||
axisType: Optional[AxisType] = None
|
||||
plane: Optional[Plane] = None
|
||||
@@ -1,110 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry import Line, Mesh, Plane, Point, Vector
|
||||
from specklepy.objects.structural.axis import Axis
|
||||
from specklepy.objects.structural.properties import (
|
||||
Property1D,
|
||||
Property2D,
|
||||
Property3D,
|
||||
PropertyDamper,
|
||||
PropertyMass,
|
||||
PropertySpring,
|
||||
)
|
||||
|
||||
STRUCTURAL_GEOMETRY = "Objects.Structural.Geometry"
|
||||
|
||||
|
||||
class ElementType1D(int, Enum):
|
||||
Beam = 0
|
||||
Brace = 1
|
||||
Bar = 2
|
||||
Column = 3
|
||||
Rod = 4
|
||||
Spring = 5
|
||||
Tie = 6
|
||||
Strut = 7
|
||||
Link = 8
|
||||
Damper = 9
|
||||
Cable = 10
|
||||
Spacer = 11
|
||||
Other = 12
|
||||
Null = 13
|
||||
|
||||
|
||||
class ElementType2D(int, Enum):
|
||||
Quad4 = 0
|
||||
Quad8 = 1
|
||||
Triangle3 = 2
|
||||
Triangle6 = 3
|
||||
|
||||
|
||||
class ElementType3D(int, Enum):
|
||||
Brick8 = 0
|
||||
Wedge6 = 1
|
||||
Pyramid5 = 2
|
||||
Tetra4 = 3
|
||||
|
||||
|
||||
class Restraint(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Restraint"):
|
||||
code: Optional[str] = None
|
||||
stiffnessX: float = 0.0
|
||||
stiffnessY: float = 0.0
|
||||
stiffnessZ: float = 0.0
|
||||
stiffnessXX: float = 0.0
|
||||
stiffnessYY: float = 0.0
|
||||
stiffnessZZ: float = 0.0
|
||||
|
||||
|
||||
class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"):
|
||||
name: Optional[str] = None
|
||||
basePoint: Optional[Point] = None
|
||||
constraintAxis: Optional[Axis] = None
|
||||
restraint: Optional[Restraint] = None
|
||||
springProperty: Optional[PropertySpring] = None
|
||||
massProperty: Optional[PropertyMass] = None
|
||||
damperProperty: Optional[PropertyDamper] = None
|
||||
|
||||
|
||||
class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"):
|
||||
name: Optional[str] = None
|
||||
baseLine: Optional[Line] = None
|
||||
property: Optional[Property1D] = None
|
||||
type: Optional[ElementType1D] = None
|
||||
end1Releases: Optional[Restraint] = None
|
||||
end2Releases: Optional[Restraint] = None
|
||||
end1Offset: Optional[Vector] = None
|
||||
end2Offset: Optional[Vector] = None
|
||||
orientationNode: Optional[Node] = None
|
||||
orinetationAngle: float = 0.0
|
||||
localAxis: Optional[Plane] = None
|
||||
parent: Optional[Base] = None
|
||||
end1Node: Optional[Node] = None
|
||||
end2Node: Optional[Node] = None
|
||||
topology: Optional[List] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
|
||||
|
||||
class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"):
|
||||
name: Optional[str] = None
|
||||
property: Optional[Property2D] = None
|
||||
type: Optional[ElementType2D] = None
|
||||
offset: float = 0.0
|
||||
orientationAngle: float = 0.0
|
||||
parent: Optional[Base] = None
|
||||
topology: Optional[List] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
|
||||
|
||||
class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"):
|
||||
name: Optional[str] = None
|
||||
baseMesh: Optional[Mesh] = None
|
||||
property: Optional[Property3D] = None
|
||||
type: Optional[ElementType3D] = None
|
||||
orientationAngle: float = 0.0
|
||||
parent: Optional[Base] = None
|
||||
topology: List
|
||||
|
||||
|
||||
# class Storey needs ependency on built elements first
|
||||
@@ -1,137 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry import Vector
|
||||
from specklepy.objects.structural.axis import Axis
|
||||
|
||||
STRUCTURAL_LOADING = "Objects.Structural.Loading."
|
||||
|
||||
|
||||
class LoadType(int, Enum):
|
||||
none = 0
|
||||
Dead = 1
|
||||
SuperDead = 2
|
||||
Soil = 3
|
||||
Live = 4
|
||||
LiveRoof = 5
|
||||
ReducibleLive = 6
|
||||
Wind = 7
|
||||
Snow = 8
|
||||
Rain = 9
|
||||
Thermal = 10
|
||||
Notional = 11
|
||||
Prestress = 12
|
||||
Equivalent = 13
|
||||
Accidental = 14
|
||||
SeismicRSA = 15
|
||||
SeismicAccTorsion = 16
|
||||
SeismicStatic = 17
|
||||
Other = 18
|
||||
|
||||
|
||||
class ActionType(int, Enum):
|
||||
none = 0
|
||||
Permanent = 1
|
||||
Variable = 2
|
||||
Accidental = 3
|
||||
|
||||
|
||||
class BeamLoadType(int, Enum):
|
||||
Point = 0
|
||||
Uniform = 1
|
||||
Linear = 2
|
||||
Patch = 3
|
||||
TriLinear = 4
|
||||
|
||||
|
||||
class FaceLoadType(int, Enum):
|
||||
Constant = 0
|
||||
Variable = 1
|
||||
Point = 2
|
||||
|
||||
|
||||
class LoadDirection2D(int, Enum):
|
||||
X = 0
|
||||
Y = 1
|
||||
Z = 2
|
||||
|
||||
|
||||
class LoadDirection(int, Enum):
|
||||
X = 0
|
||||
Y = 1
|
||||
Z = 2
|
||||
XX = 3
|
||||
YY = 4
|
||||
ZZ = 5
|
||||
|
||||
|
||||
class LoadAxisType(int, Enum):
|
||||
Global = 0
|
||||
Local = 1 # local element axes
|
||||
DeformedLocal = (
|
||||
2 # element local axis that is embedded in the element as it deforms
|
||||
)
|
||||
|
||||
|
||||
class CombinationType(int, Enum):
|
||||
LinearAdd = 0
|
||||
Envelope = 1
|
||||
AbsoluteAdd = 2
|
||||
SRSS = 3
|
||||
RangeAdd = 4
|
||||
|
||||
|
||||
class LoadCase(Base, speckle_type=STRUCTURAL_LOADING + "LoadCase"):
|
||||
name: Optional[str] = None
|
||||
loadType: Optional[LoadType] = None
|
||||
group: Optional[str] = None
|
||||
actionType: Optional[ActionType] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"):
|
||||
name: Optional[str] = None
|
||||
loadCase: Optional[LoadCase] = None
|
||||
|
||||
|
||||
class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"):
|
||||
elements: Optional[List] = None
|
||||
loadType: Optional[BeamLoadType] = None
|
||||
direction: Optional[LoadDirection] = None
|
||||
loadAxis: Optional[Axis] = None
|
||||
loadAxisType: Optional[LoadAxisType] = None
|
||||
isProjected: Optional[bool] = None
|
||||
values: Optional[List] = None
|
||||
positions: Optional[List] = None
|
||||
|
||||
|
||||
class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"):
|
||||
name: Optional[str] = None
|
||||
loadCases: List
|
||||
loadFactors: List
|
||||
combinationType: CombinationType
|
||||
|
||||
|
||||
class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"):
|
||||
elements: Optional[List] = None
|
||||
loadType: Optional[FaceLoadType] = None
|
||||
direction: Optional[LoadDirection2D] = None
|
||||
loadAxis: Optional[Axis] = None
|
||||
loadAxisType: Optional[LoadAxisType] = None
|
||||
isProjected: Optional[bool] = None
|
||||
values: Optional[List] = None
|
||||
positions: Optional[List] = None
|
||||
|
||||
|
||||
class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"):
|
||||
elements: Optional[List] = None
|
||||
nodes: Optional[List] = None
|
||||
gravityFactors: Optional[Vector] = None
|
||||
|
||||
|
||||
class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"):
|
||||
nodes: Optional[List] = None
|
||||
loadAxis: Optional[Axis] = None
|
||||
direction: Optional[LoadDirection] = None
|
||||
value: float = 0.0
|
||||
@@ -1,61 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
STRUCTURAL_MATERIALS = "Objects.Structural.Materials"
|
||||
|
||||
|
||||
class MaterialType(int, Enum):
|
||||
Concrete = 0
|
||||
Steel = 1
|
||||
Timber = 2
|
||||
Aluminium = 3
|
||||
Masonry = 4
|
||||
FRP = 5
|
||||
Glass = 6
|
||||
Fabric = 7
|
||||
Rebar = 8
|
||||
Tendon = 9
|
||||
ColdFormed = 10
|
||||
Other = 11
|
||||
|
||||
|
||||
class StructuralMaterial(
|
||||
Base, speckle_type=STRUCTURAL_MATERIALS + ".StructuralMaterial"
|
||||
):
|
||||
name: Optional[str] = None
|
||||
grade: Optional[str] = None
|
||||
materialType: Optional[MaterialType] = None
|
||||
designCode: Optional[str] = None
|
||||
codeYear: Optional[str] = None
|
||||
strength: float = 0.0
|
||||
elasticModulus: float = 0.0
|
||||
poissonsRatio: float = 0.0
|
||||
shearModulus: float = 0.0
|
||||
density: float = 0.0
|
||||
thermalExpansivity: float = 0.0
|
||||
dampingRatio: float = 0.0
|
||||
cost: float = 0.0
|
||||
materialSafetyFactor: float = 0.0
|
||||
|
||||
|
||||
class Concrete(StructuralMaterial):
|
||||
compressiveStrength: float = 0.0
|
||||
tensileStrength: float = 0.0
|
||||
flexuralStrength: float = 0.0
|
||||
maxCompressiveStrain: float = 0.0
|
||||
maxTensileStrain: float = 0.0
|
||||
maxAggregateSize: float = 0.0
|
||||
lightweight: Optional[bool] = None
|
||||
|
||||
|
||||
class Steel(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Steel"):
|
||||
yieldStrength: float = 0.0
|
||||
ultimateStrength: float = 0.0
|
||||
maxStrain: float = 0.0
|
||||
strainHardeningModulus: float = 0.0
|
||||
|
||||
|
||||
class Timber(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Timber"):
|
||||
species: Optional[str] = None
|
||||
@@ -1,212 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.structural.axis import Axis
|
||||
from specklepy.objects.structural.materials import StructuralMaterial
|
||||
|
||||
STRUCTURAL_PROPERTY = "Objects.Structural.Properties"
|
||||
|
||||
|
||||
class MemberType(int, Enum):
|
||||
Beam = 0
|
||||
Column = 1
|
||||
Generic1D = 2
|
||||
Slab = 3
|
||||
Wall = 4
|
||||
Generic2D = 5
|
||||
VoidCutter1D = 6
|
||||
VoidCutter2D = 7
|
||||
|
||||
|
||||
class BaseReferencePoint(int, Enum):
|
||||
Centroid = 0
|
||||
TopLeft = 1
|
||||
TopCentre = 2
|
||||
TopRight = 3
|
||||
MidLeft = 4
|
||||
MidRight = 5
|
||||
BotLeft = 6
|
||||
BotCentre = 7
|
||||
BotRight = 8
|
||||
|
||||
|
||||
class ReferenceSurface(int, Enum):
|
||||
Top = 0
|
||||
Middle = 1
|
||||
Bottom = 2
|
||||
|
||||
|
||||
class PropertyType2D(int, Enum):
|
||||
Stress = 0
|
||||
Fabric = 1
|
||||
Plate = 2
|
||||
Shell = 3
|
||||
Curved = 4
|
||||
Wall = 5
|
||||
Strain = 6
|
||||
Axi = 7
|
||||
Load = 8
|
||||
|
||||
|
||||
class PropertyType3D(int, Enum):
|
||||
Solid = 0
|
||||
Infinite = 1
|
||||
|
||||
|
||||
class ShapeType(int, Enum):
|
||||
Rectangular = 0
|
||||
Circular = 1
|
||||
I = 2 # noqa: E741
|
||||
Tee = 3
|
||||
Angle = 4
|
||||
Channel = 5
|
||||
Perimeter = 6
|
||||
Box = 7
|
||||
Catalogue = 8
|
||||
Explicit = 9
|
||||
Undefined = 10
|
||||
|
||||
|
||||
class PropertyTypeSpring(int, Enum):
|
||||
Axial = 0
|
||||
Torsional = 1
|
||||
General = 2
|
||||
Matrix = 3
|
||||
TensionOnly = 4
|
||||
CompressionOnly = 5
|
||||
Connector = 6
|
||||
LockUp = 7
|
||||
Gap = 8
|
||||
Friction = 9
|
||||
|
||||
|
||||
class PropertyTypeDamper(int, Enum):
|
||||
Axial = 0
|
||||
Torsional = 1
|
||||
General = 2
|
||||
|
||||
|
||||
class Property(Base, speckle_type=STRUCTURAL_PROPERTY):
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class SectionProfile(
|
||||
Base, speckle_type=STRUCTURAL_PROPERTY + ".Profiles.SectionProfile"
|
||||
):
|
||||
name: Optional[str] = None
|
||||
shapeType: Optional[ShapeType] = None
|
||||
area: float = 0.0
|
||||
Iyy: float = 0.0
|
||||
Izz: float = 0.0
|
||||
J: float = 0.0
|
||||
Ky: float = 0.0
|
||||
weight: float = 0.0
|
||||
|
||||
|
||||
class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"):
|
||||
memberType: Optional[MemberType] = None
|
||||
material: Optional[StructuralMaterial] = None
|
||||
profile: Optional[SectionProfile] = None
|
||||
referencePoint: Optional[BaseReferencePoint] = None
|
||||
offsetY: float = 0.0
|
||||
offsetZ: float = 0.0
|
||||
|
||||
|
||||
class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
|
||||
type: Optional[PropertyType2D] = None
|
||||
thickness: float = 0.0
|
||||
material: Optional[StructuralMaterial] = None
|
||||
orientationAxis: Optional[Axis] = None
|
||||
refSurface: Optional[ReferenceSurface] = None
|
||||
zOffset: float = 0.0
|
||||
modifierInPlane: float = 0.0
|
||||
modifierBending: float = 0.0
|
||||
modifierShear: float = 0.0
|
||||
modifierVolume: float = 0.0
|
||||
|
||||
|
||||
class Property3D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property3D"):
|
||||
type: Optional[PropertyType3D] = None
|
||||
material: Optional[StructuralMaterial] = None
|
||||
orientationAxis: Optional[Axis] = None
|
||||
|
||||
|
||||
class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"):
|
||||
damperType: Optional[PropertyTypeDamper] = None
|
||||
dampingX: float = 0.0
|
||||
dampingY: float = 0.0
|
||||
dampingZ: float = 0.0
|
||||
dampingXX: float = 0.0
|
||||
dampingYY: float = 0.0
|
||||
dampingZZ: float = 0.0
|
||||
|
||||
|
||||
class PropertyMass(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyMass"):
|
||||
mass: float = 0.0
|
||||
inertiaXX: float = 0.0
|
||||
inertiaYY: float = 0.0
|
||||
inertiaZZ: float = 0.0
|
||||
inertiaXY: float = 0.0
|
||||
inertiaYZ: float = 0.0
|
||||
inertiaZX: float = 0.0
|
||||
massModified: Optional[bool] = None
|
||||
massModifierX: float = 0.0
|
||||
massModifierY: float = 0.0
|
||||
massModifierZ: float = 0.0
|
||||
|
||||
|
||||
class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"):
|
||||
springType: Optional[PropertyTypeSpring] = None
|
||||
springCurveX: float = 0.0
|
||||
stiffnessX: float = 0.0
|
||||
springCurveY: float = 0.0
|
||||
stiffnessY: float = 0.0
|
||||
springCurveZ: float = 0.0
|
||||
stiffnessZ: float = 0.0
|
||||
springCurveXX: float = 0.0
|
||||
stiffnessXX: float = 0.0
|
||||
springCurveYY: float = 0.0
|
||||
stiffnessYY: float = 0.0
|
||||
springCurveZZ: float = 0.0
|
||||
stiffnessZZ: float = 0.0
|
||||
dampingRatio: float = 0.0
|
||||
dampingX: float = 0.0
|
||||
dampingY: float = 0.0
|
||||
dampingZ: float = 0.0
|
||||
dampingXX: float = 0.0
|
||||
dampingYY: float = 0.0
|
||||
dampingZZ: float = 0.0
|
||||
matrix: float = 0.0
|
||||
postiveLockup: float = 0.0
|
||||
frictionCoefficient: float = 0.0
|
||||
|
||||
|
||||
class ReferenceSurfaceEnum(int, Enum):
|
||||
Concrete = 0
|
||||
Steel = 1
|
||||
Timber = 2
|
||||
Aluminium = 3
|
||||
Masonry = 4
|
||||
FRP = 5
|
||||
Glass = 6
|
||||
Fabric = 7
|
||||
Rebar = 8
|
||||
Tendon = 9
|
||||
ColdFormed = 10
|
||||
Other = 11
|
||||
|
||||
|
||||
class shapeType(int, Enum):
|
||||
Concrete = 0
|
||||
Steel = 1
|
||||
Timber = 2
|
||||
Aluminium = 3
|
||||
Masonry = 4
|
||||
FRP = 5
|
||||
Glass = 6
|
||||
Fabric = 7
|
||||
Rebar = 8
|
||||
Tendon = 9
|
||||
ColdFormed = 10
|
||||
Other = 11
|
||||
@@ -1,172 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.structural.analysis import Model
|
||||
from specklepy.objects.structural.geometry import Element1D, Element2D, Element3D, Node
|
||||
|
||||
STRUCTURAL_RESULTS = "Objects.Structural.Results."
|
||||
|
||||
|
||||
class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"):
|
||||
resultCase: Optional[Base] = None
|
||||
permutation: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"):
|
||||
results1D: List
|
||||
|
||||
|
||||
class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"):
|
||||
element: Optional[Element1D] = None
|
||||
position: Optional[float] = None
|
||||
dispX: Optional[float] = None
|
||||
dispY: Optional[float] = None
|
||||
dispZ: Optional[float] = None
|
||||
rotXX: Optional[float] = None
|
||||
rotYY: Optional[float] = None
|
||||
rotZZ: Optional[float] = None
|
||||
forceX: Optional[float] = None
|
||||
forceY: Optional[float] = None
|
||||
forceZ: Optional[float] = None
|
||||
momentXX: Optional[float] = None
|
||||
momentYY: Optional[float] = None
|
||||
momentZZ: Optional[float] = None
|
||||
axialStress: Optional[float] = None
|
||||
shearStressY: Optional[float] = None
|
||||
shearStressZ: Optional[float] = None
|
||||
bendingStressYPos: Optional[float] = None
|
||||
bendingStressYNeg: Optional[float] = None
|
||||
bendingStressZPos: Optional[float] = None
|
||||
bendingStressZNeg: Optional[float] = None
|
||||
combinedStressMax: Optional[float] = None
|
||||
combinedStressMin: Optional[float] = None
|
||||
|
||||
|
||||
class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"):
|
||||
results2D: List
|
||||
|
||||
|
||||
class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"):
|
||||
element: Optional[Element2D] = None
|
||||
position: List
|
||||
dispX: Optional[float] = None
|
||||
dispY: Optional[float] = None
|
||||
dispZ: Optional[float] = None
|
||||
forceXX: Optional[float] = None
|
||||
forceYY: Optional[float] = None
|
||||
forceXY: Optional[float] = None
|
||||
momentXX: Optional[float] = None
|
||||
momentYY: Optional[float] = None
|
||||
momentXY: Optional[float] = None
|
||||
shearX: Optional[float] = None
|
||||
shearY: Optional[float] = None
|
||||
stressTopXX: Optional[float] = None
|
||||
stressTopYY: Optional[float] = None
|
||||
stressTopZZ: Optional[float] = None
|
||||
stressTopXY: Optional[float] = None
|
||||
stressTopYZ: Optional[float] = None
|
||||
stressTopZX: Optional[float] = None
|
||||
stressMidXX: Optional[float] = None
|
||||
stressMidYY: Optional[float] = None
|
||||
stressMidZZ: Optional[float] = None
|
||||
stressMidXY: Optional[float] = None
|
||||
stressMidYZ: Optional[float] = None
|
||||
stressMidZX: Optional[float] = None
|
||||
stressBotXX: Optional[float] = None
|
||||
stressBotYY: Optional[float] = None
|
||||
stressBotZZ: Optional[float] = None
|
||||
stressBotXY: Optional[float] = None
|
||||
stressBotYZ: Optional[float] = None
|
||||
stressBotZX: Optional[float] = None
|
||||
|
||||
|
||||
class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"):
|
||||
results3D: List
|
||||
|
||||
|
||||
class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"):
|
||||
element: Optional[Element3D] = None
|
||||
position: List
|
||||
dispX: Optional[float] = None
|
||||
dispY: Optional[float] = None
|
||||
dispZ: Optional[float] = None
|
||||
stressXX: Optional[float] = None
|
||||
stressYY: Optional[float] = None
|
||||
stressZZ: Optional[float] = None
|
||||
stressXY: Optional[float] = None
|
||||
stressYZ: Optional[float] = None
|
||||
stressZX: Optional[float] = None
|
||||
|
||||
|
||||
class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"):
|
||||
model: Optional[Model] = None
|
||||
loadX: Optional[float] = None
|
||||
loadY: Optional[float] = None
|
||||
loadZ: Optional[float] = None
|
||||
loadXX: Optional[float] = None
|
||||
loadYY: Optional[float] = None
|
||||
loadZZ: Optional[float] = None
|
||||
reactionX: Optional[float] = None
|
||||
reactionY: Optional[float] = None
|
||||
reactionZ: Optional[float] = None
|
||||
reactionXX: Optional[float] = None
|
||||
reactionYY: Optional[float] = None
|
||||
reactionZZ: Optional[float] = None
|
||||
mode: Optional[float] = None
|
||||
frequency: Optional[float] = None
|
||||
loadFactor: Optional[float] = None
|
||||
modalStiffness: Optional[float] = None
|
||||
modalGeoStiffness: Optional[float] = None
|
||||
effMassX: Optional[float] = None
|
||||
effMassY: Optional[float] = None
|
||||
effMassZ: Optional[float] = None
|
||||
effMassXX: Optional[float] = None
|
||||
effMassYY: Optional[float] = None
|
||||
effMassZZ: Optional[float] = None
|
||||
|
||||
|
||||
class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"):
|
||||
resultsNode: List
|
||||
|
||||
|
||||
class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"):
|
||||
node: Optional[Node] = None
|
||||
dispX: Optional[float] = None
|
||||
dispY: Optional[float] = None
|
||||
dispZ: Optional[float] = None
|
||||
rotXX: Optional[float] = None
|
||||
rotYY: Optional[float] = None
|
||||
rotZZ: Optional[float] = None
|
||||
reactionX: Optional[float] = None
|
||||
reactionY: Optional[float] = None
|
||||
reactionZ: Optional[float] = None
|
||||
reactionXX: Optional[float] = None
|
||||
reactionYY: Optional[float] = None
|
||||
reactionZZ: Optional[float] = None
|
||||
constraintX: Optional[float] = None
|
||||
constraintY: Optional[float] = None
|
||||
constraintZ: Optional[float] = None
|
||||
constraintXX: Optional[float] = None
|
||||
constraintYY: Optional[float] = None
|
||||
constraintZZ: Optional[float] = None
|
||||
velX: Optional[float] = None
|
||||
velY: Optional[float] = None
|
||||
velZ: Optional[float] = None
|
||||
velXX: Optional[float] = None
|
||||
velYY: Optional[float] = None
|
||||
velZZ: Optional[float] = None
|
||||
accX: Optional[float] = None
|
||||
accY: Optional[float] = None
|
||||
accZ: Optional[float] = None
|
||||
accXX: Optional[float] = None
|
||||
accYY: Optional[float] = None
|
||||
accZZ: Optional[float] = None
|
||||
|
||||
|
||||
class ResultSetAll(Base, speckle_type=None):
|
||||
resultSet1D: Optional[ResultSet1D] = None
|
||||
resultSet2D: Optional[ResultSet2D] = None
|
||||
resultSet3D: Optional[ResultSet3D] = None
|
||||
resultsGlobal: Optional[ResultGlobal] = None
|
||||
resultsNode: Optional[ResultSetNode] = None
|
||||
@@ -0,0 +1,9 @@
|
||||
from specklepy.objects.geometry import Point, Line
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
p_1 = Point(x=0, y=0, z=0, units=Units.m)
|
||||
p_2 = Point(x=3, y=0, z=0, units=Units.m)
|
||||
|
||||
line = Line(start=p_1, end=p_2, units=Units.m)
|
||||
line.length = line.calculate_length()
|
||||
print(line.length)
|
||||
@@ -0,0 +1,159 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Arc, Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points():
|
||||
start = Point(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
mid = Point(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
end = Point(x=-1.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
return start, mid, end
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane():
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
plane = Plane(origin=origin, normal=normal,
|
||||
xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
return plane
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_arc(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
return arc
|
||||
|
||||
|
||||
def test_arc_creation(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
assert arc.startPoint == start
|
||||
assert arc.midPoint == mid
|
||||
assert arc.endPoint == end
|
||||
assert arc.plane == sample_plane
|
||||
assert arc.units == Units.m.value
|
||||
|
||||
|
||||
def test_arc_domain(sample_arc):
|
||||
assert isinstance(sample_arc.domain, Interval)
|
||||
assert sample_arc.domain.start == 0.0
|
||||
assert sample_arc.domain.end == 1.0
|
||||
|
||||
|
||||
def test_arc_radius(sample_arc):
|
||||
|
||||
assert sample_arc.radius == pytest.approx(1.0)
|
||||
|
||||
|
||||
def test_arc_length(sample_arc):
|
||||
|
||||
assert sample_arc.length == pytest.approx(math.pi)
|
||||
|
||||
|
||||
def test_arc_units(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
assert arc.units == Units.m.value
|
||||
|
||||
arc.units = "mm"
|
||||
assert arc.units == "mm"
|
||||
|
||||
|
||||
def test_arc_invalid_construction(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane="not a plane",
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint="not a point",
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint="not a point",
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint="not a point",
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
|
||||
def test_arc_serialization(sample_arc):
|
||||
serialized = serialize(sample_arc)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.startPoint.x == sample_arc.startPoint.x
|
||||
assert deserialized.startPoint.y == sample_arc.startPoint.y
|
||||
assert deserialized.startPoint.z == sample_arc.startPoint.z
|
||||
|
||||
assert deserialized.midPoint.x == sample_arc.midPoint.x
|
||||
assert deserialized.midPoint.y == sample_arc.midPoint.y
|
||||
assert deserialized.midPoint.z == sample_arc.midPoint.z
|
||||
|
||||
assert deserialized.endPoint.x == sample_arc.endPoint.x
|
||||
assert deserialized.endPoint.y == sample_arc.endPoint.y
|
||||
assert deserialized.endPoint.z == sample_arc.endPoint.z
|
||||
|
||||
assert deserialized.plane.origin.x == sample_arc.plane.origin.x
|
||||
assert deserialized.plane.origin.y == sample_arc.plane.origin.y
|
||||
assert deserialized.plane.origin.z == sample_arc.plane.origin.z
|
||||
|
||||
assert deserialized.units == sample_arc.units
|
||||
@@ -0,0 +1,86 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Line, Point
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points():
|
||||
|
||||
p1 = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
p2 = Point(x=3.0, y=4.0, z=0.0, units=Units.m)
|
||||
return p1, p2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_line(sample_points):
|
||||
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
return line
|
||||
|
||||
|
||||
def test_line_creation(sample_points):
|
||||
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
|
||||
assert line.start == start
|
||||
assert line.end == end
|
||||
assert line.units == Units.m.value
|
||||
|
||||
|
||||
def test_line_domain(sample_line):
|
||||
|
||||
# Domain should be automatically initialized to unit interval by ICurve
|
||||
assert isinstance(sample_line.domain, Interval)
|
||||
assert sample_line.domain.start == 0.0
|
||||
assert sample_line.domain.end == 1.0
|
||||
|
||||
|
||||
def test_line_length(sample_line):
|
||||
|
||||
assert sample_line.length == 5.0
|
||||
|
||||
|
||||
def test_line_units(sample_points):
|
||||
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
|
||||
assert line.units == Units.m.value
|
||||
|
||||
# Test setting units with string
|
||||
line.units = "mm"
|
||||
assert line.units == "mm"
|
||||
|
||||
|
||||
def test_line_serialization(sample_line):
|
||||
|
||||
serialized = serialize(sample_line)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.start.x == sample_line.start.x
|
||||
assert deserialized.start.y == sample_line.start.y
|
||||
assert deserialized.start.z == sample_line.start.z
|
||||
assert deserialized.end.x == sample_line.end.x
|
||||
assert deserialized.end.y == sample_line.end.y
|
||||
assert deserialized.end.z == sample_line.end.z
|
||||
assert deserialized.units == sample_line.units
|
||||
assert deserialized.domain.start == sample_line.domain.start
|
||||
assert deserialized.domain.end == sample_line.domain.end
|
||||
|
||||
|
||||
def test_line_invalid_construction():
|
||||
"""Test error cases"""
|
||||
p1 = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
# Test with invalid start point
|
||||
with pytest.raises(Exception):
|
||||
Line(start="not a point", end=p1)
|
||||
|
||||
# Test with invalid end point
|
||||
with pytest.raises(Exception):
|
||||
Line(start=p1, end="not a point")
|
||||
@@ -0,0 +1,185 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry.mesh import Mesh
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_vertices():
|
||||
|
||||
return [
|
||||
-0.5, -0.5, -0.5,
|
||||
0.5, -0.5, -0.5,
|
||||
0.5, 0.5, -0.5,
|
||||
-0.5, 0.5, -0.5,
|
||||
-0.5, -0.5, 0.5,
|
||||
0.5, -0.5, 0.5,
|
||||
0.5, 0.5, 0.5,
|
||||
-0.5, 0.5, 0.5
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_faces():
|
||||
|
||||
return [
|
||||
4, 0, 3, 2, 1, # bottom (-z)
|
||||
4, 4, 5, 6, 7, # top (+z)
|
||||
4, 0, 1, 5, 4, # front (-y)
|
||||
4, 3, 7, 6, 2, # back (+y)
|
||||
4, 0, 4, 7, 3, # left (-x)
|
||||
4, 1, 2, 6, 5 # right (+x)
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_colors():
|
||||
|
||||
return [
|
||||
255, 0, 0, 255, # red
|
||||
0, 255, 0, 255, # green
|
||||
0, 0, 255, 255, # blue
|
||||
255, 255, 0, 255, # yellow
|
||||
255, 0, 255, 255, # magenta
|
||||
0, 255, 255, 255, # cyan
|
||||
255, 255, 255, 255, # white
|
||||
0, 0, 0, 255 # black
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_texture_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_mesh(cube_vertices, cube_faces):
|
||||
|
||||
return Mesh(vertices=cube_vertices, faces=cube_faces, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def full_mesh(cube_vertices, cube_faces, cube_colors, cube_texture_coords):
|
||||
|
||||
return Mesh(
|
||||
vertices=cube_vertices,
|
||||
faces=cube_faces,
|
||||
colors=cube_colors,
|
||||
textureCoordinates=cube_texture_coords,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
|
||||
def test_mesh_creation(cube_vertices, cube_faces):
|
||||
mesh = Mesh(vertices=cube_vertices, faces=cube_faces, units=Units.m)
|
||||
|
||||
assert mesh.vertices == cube_vertices
|
||||
assert mesh.faces == cube_faces
|
||||
assert mesh.colors == []
|
||||
assert mesh.textureCoordinates == []
|
||||
assert mesh.units == Units.m.value
|
||||
|
||||
|
||||
def test_mesh_vertex_count(sample_mesh):
|
||||
assert sample_mesh.vertices_count == 8 # cube has 8 vertices
|
||||
|
||||
|
||||
def test_mesh_texture_coordinates_count(full_mesh):
|
||||
assert full_mesh.texture_coordinates_count == 8 # one UV per vertex
|
||||
|
||||
|
||||
def test_mesh_get_point(sample_mesh):
|
||||
|
||||
point = sample_mesh.get_point(0)
|
||||
assert isinstance(point, Point)
|
||||
assert point.x == -0.5
|
||||
assert point.y == -0.5
|
||||
assert point.z == -0.5
|
||||
assert point.units == sample_mesh.units
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
sample_mesh.get_point(8) # Beyond vertex count
|
||||
|
||||
|
||||
def test_mesh_get_points(sample_mesh):
|
||||
points = sample_mesh.get_points()
|
||||
assert len(points) == 8
|
||||
assert all(isinstance(p, Point) for p in points)
|
||||
assert all(p.units == sample_mesh.units for p in points)
|
||||
|
||||
|
||||
def test_mesh_get_texture_coordinate(full_mesh):
|
||||
uv = full_mesh.get_texture_coordinate(0)
|
||||
assert uv == (0.0, 0.0)
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
full_mesh.get_texture_coordinate(8) # beyond UV count
|
||||
|
||||
|
||||
def test_mesh_get_face_vertices(sample_mesh):
|
||||
face_vertices = sample_mesh.get_face_vertices(0)
|
||||
assert len(face_vertices) == 4
|
||||
assert all(isinstance(v, Point) for v in face_vertices)
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
sample_mesh.get_face_vertices(6) # beyond face count
|
||||
|
||||
|
||||
def test_mesh_is_closed(sample_mesh):
|
||||
assert sample_mesh.is_closed() # cube is a closed mesh
|
||||
|
||||
|
||||
def test_mesh_area(sample_mesh):
|
||||
|
||||
calculated_area = sample_mesh.calculate_area()
|
||||
sample_mesh.area = calculated_area
|
||||
assert sample_mesh.area == pytest.approx(6.0)
|
||||
|
||||
|
||||
def test_mesh_volume(sample_mesh):
|
||||
|
||||
calculated_volume = sample_mesh.calculate_volume()
|
||||
sample_mesh.volume = calculated_volume
|
||||
|
||||
# Verify volume is set correctly
|
||||
assert sample_mesh.volume == pytest.approx(1.0)
|
||||
|
||||
|
||||
def test_mesh_invalid_vertices():
|
||||
|
||||
mesh = Mesh(vertices=[0.0, 0.0], faces=[3, 0, 1, 2], units=Units.m)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
mesh.vertices_count
|
||||
|
||||
|
||||
def test_mesh_invalid_faces():
|
||||
|
||||
vertices = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
|
||||
with pytest.raises(IndexError):
|
||||
# Face references vertex index out of range
|
||||
mesh = Mesh(vertices=vertices, faces=[3, 0, 1, 5], units=Units.m)
|
||||
mesh.get_face_vertices(0)
|
||||
|
||||
|
||||
def test_mesh_serialization(full_mesh):
|
||||
serialized = serialize(full_mesh)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.vertices == full_mesh.vertices
|
||||
assert deserialized.faces == full_mesh.faces
|
||||
assert deserialized.colors == full_mesh.colors
|
||||
assert deserialized.textureCoordinates == full_mesh.textureCoordinates
|
||||
assert deserialized.units == full_mesh.units
|
||||
@@ -0,0 +1,117 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_point():
|
||||
|
||||
point = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
return point
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_vectors():
|
||||
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
return normal, xdir, ydir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
)
|
||||
return plane
|
||||
|
||||
|
||||
def test_plane_creation(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
assert plane.origin == sample_point
|
||||
assert plane.normal == normal
|
||||
assert plane.xdir == xdir
|
||||
assert plane.ydir == ydir
|
||||
assert plane.units == Units.m.value
|
||||
|
||||
|
||||
def test_plane_units(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
assert plane.units == Units.m.value
|
||||
|
||||
plane.units = "mm"
|
||||
assert plane.units == "mm"
|
||||
|
||||
|
||||
def test_plane_invalid_construction():
|
||||
|
||||
point = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin="not a point", normal=normal, xdir=xdir, ydir=ydir)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal="not a vector", xdir=xdir, ydir=ydir)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal=normal, xdir="not a vector", ydir=ydir)
|
||||
|
||||
# Test with invalid ydir vector
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal=normal, xdir=xdir, ydir="not a vector")
|
||||
|
||||
|
||||
def test_plane_serialization(sample_plane):
|
||||
|
||||
serialized = serialize(sample_plane)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
# Check all properties are preserved
|
||||
assert deserialized.origin.x == sample_plane.origin.x
|
||||
assert deserialized.origin.y == sample_plane.origin.y
|
||||
assert deserialized.origin.z == sample_plane.origin.z
|
||||
|
||||
assert deserialized.normal.x == sample_plane.normal.x
|
||||
assert deserialized.normal.y == sample_plane.normal.y
|
||||
assert deserialized.normal.z == sample_plane.normal.z
|
||||
|
||||
assert deserialized.xdir.x == sample_plane.xdir.x
|
||||
assert deserialized.xdir.y == sample_plane.xdir.y
|
||||
assert deserialized.xdir.z == sample_plane.xdir.z
|
||||
|
||||
assert deserialized.ydir.x == sample_plane.ydir.x
|
||||
assert deserialized.ydir.y == sample_plane.ydir.y
|
||||
assert deserialized.ydir.z == sample_plane.ydir.z
|
||||
|
||||
assert deserialized.units == sample_plane.units
|
||||
@@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Point
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
def test_point_creation():
|
||||
p1 = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
assert p1.x == 1.0
|
||||
assert p1.y == 2.0
|
||||
assert p1.z == 3.0
|
||||
assert p1.units == Units.m.value
|
||||
|
||||
|
||||
def test_point_distance_calculation():
|
||||
p1 = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
p2 = Point(x=4.0, y=6.0, z=8.0, units=Units.m)
|
||||
|
||||
distance = p1.distance_to(p2)
|
||||
expected = ((3.0**2 + 4.0**2 + 5.0**2) ** 0.5)
|
||||
assert distance == pytest.approx(expected)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
p1.distance_to("not a point")
|
||||
|
||||
|
||||
def test_point_serialization():
|
||||
p1 = Point(x=1.0, y=2.0, z=3.0, units=Units.mm)
|
||||
serialized = serialize(p1)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.x == p1.x
|
||||
assert deserialized.y == p1.y
|
||||
assert deserialized.z == p1.z
|
||||
assert deserialized.units == p1.units
|
||||
@@ -0,0 +1,123 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Point, Polyline
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def open_square_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0, 0.0, # point 1
|
||||
1.0, 0.0, 0.0, # point 2
|
||||
1.0, 1.0, 0.0, # point 3
|
||||
0.0, 1.0, 0.0 # point 4
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def closed_square_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0, 0.0, # point 1
|
||||
1.0, 0.0, 0.0, # point 2
|
||||
1.0, 1.0, 0.0, # point 3
|
||||
0.0, 1.0, 0.0, # point 4
|
||||
0.0, 0.0, 0.0 # point 5 (same as point 1)
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_polyline(open_square_coords):
|
||||
return Polyline(value=open_square_coords, units=Units.m)
|
||||
|
||||
|
||||
def test_polyline_creation(open_square_coords):
|
||||
polyline = Polyline(value=open_square_coords, units=Units.m)
|
||||
assert polyline.value == open_square_coords
|
||||
assert polyline.units == Units.m.value
|
||||
|
||||
|
||||
def test_polyline_domain(sample_polyline):
|
||||
assert isinstance(sample_polyline.domain, Interval)
|
||||
assert sample_polyline.domain.start == 0.0
|
||||
assert sample_polyline.domain.end == 1.0
|
||||
|
||||
|
||||
def test_polyline_is_closed(open_square_coords, closed_square_coords):
|
||||
open_poly = Polyline(value=open_square_coords, units=Units.m)
|
||||
closed_poly = Polyline(value=closed_square_coords, units=Units.m)
|
||||
|
||||
assert not open_poly.is_closed()
|
||||
assert closed_poly.is_closed()
|
||||
|
||||
|
||||
def test_polyline_is_closed_with_tolerance(open_square_coords):
|
||||
|
||||
almost_closed = open_square_coords + \
|
||||
[0.0, 0.0, 0.001] # last point slightly above start
|
||||
poly = Polyline(value=almost_closed, units=Units.m)
|
||||
|
||||
assert not poly.is_closed(tolerance=1e-6)
|
||||
assert poly.is_closed(tolerance=0.01)
|
||||
|
||||
|
||||
def test_polyline_length_open(sample_polyline):
|
||||
sample_polyline.length = sample_polyline.calculate_length()
|
||||
assert sample_polyline.length == 3.0
|
||||
|
||||
|
||||
def test_polyline_length_closed(closed_square_coords):
|
||||
polyline = Polyline(value=closed_square_coords, units=Units.m)
|
||||
polyline.length = polyline.calculate_length()
|
||||
assert polyline.length == 4.0
|
||||
|
||||
|
||||
def test_polyline_get_points(sample_polyline):
|
||||
points = sample_polyline.get_points()
|
||||
|
||||
assert len(points) == 4
|
||||
assert all(isinstance(p, Point) for p in points)
|
||||
assert all(p.units == Units.m.value for p in points)
|
||||
|
||||
# Create expected points
|
||||
expected_points = [
|
||||
Point(x=0.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=1.0, z=0.0, units=Units.m),
|
||||
Point(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
]
|
||||
|
||||
# Check coordinates match
|
||||
for actual, expected in zip(points, expected_points, strict=False):
|
||||
assert actual.x == expected.x
|
||||
assert actual.y == expected.y
|
||||
assert actual.z == expected.z
|
||||
|
||||
|
||||
def test_polyline_invalid_coordinates():
|
||||
|
||||
invalid_coords = [0.0, 0.0, 0.0, 1.0, 1.0] # missing one coordinate
|
||||
with pytest.raises(ValueError):
|
||||
polyline = Polyline(value=invalid_coords, units=Units.m)
|
||||
polyline.get_points()
|
||||
|
||||
|
||||
def test_polyline_units(open_square_coords):
|
||||
polyline = Polyline(value=open_square_coords, units=Units.m)
|
||||
assert polyline.units == Units.m.value
|
||||
|
||||
polyline.units = "mm"
|
||||
assert polyline.units == "mm"
|
||||
|
||||
|
||||
def test_polyline_serialization(sample_polyline):
|
||||
serialized = serialize(sample_polyline)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.value == sample_polyline.value
|
||||
assert deserialized.units == sample_polyline.units
|
||||
assert deserialized.domain.start == sample_polyline.domain.start
|
||||
assert deserialized.domain.end == sample_polyline.domain.end
|
||||
@@ -0,0 +1,42 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
def test_vector_creation():
|
||||
v = Vector(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
assert v.x == 1.0
|
||||
assert v.y == 2.0
|
||||
assert v.z == 3.0
|
||||
assert v.units == Units.m.value
|
||||
|
||||
|
||||
def test_vector_length():
|
||||
v = Vector(x=3.0, y=4.0, z=0.0, units=Units.m)
|
||||
assert v.length == pytest.approx(5.0) # 3-4-5 triangle
|
||||
|
||||
|
||||
def test_vector_units():
|
||||
v = Vector(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
assert v.units == Units.m.value
|
||||
|
||||
v.units = "mm"
|
||||
assert v.units == "mm"
|
||||
|
||||
|
||||
def test_vector_invalid_construction():
|
||||
with pytest.raises(TypeError):
|
||||
Vector(x=1.0, y=2.0) # missing z and units
|
||||
|
||||
|
||||
def test_vector_serialization():
|
||||
v = Vector(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
serialized = serialize(v)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.x == v.x
|
||||
assert deserialized.y == v.y
|
||||
assert deserialized.z == v.z
|
||||
assert deserialized.units == v.units
|
||||
@@ -30,6 +30,7 @@ def safe_json_loads(obj: str, obj_id=None) -> Any:
|
||||
f"Failed to deserialise object (id: {obj_id}). This is likely a ujson big"
|
||||
f" int error - falling back to json. \nError: {err}",
|
||||
SpeckleWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return json.loads(obj)
|
||||
|
||||
@@ -140,7 +141,8 @@ class BaseObjectSerializer:
|
||||
object_builder[prop] = value
|
||||
continue
|
||||
|
||||
# NOTE: for dynamic props, this won't be re-serialised as an enum but as an int
|
||||
# NOTE: for dynamic props, this won't be re-serialised
|
||||
# as an enum but as an int
|
||||
if isinstance(value, Enum):
|
||||
object_builder[prop] = value.value
|
||||
continue
|
||||
@@ -222,7 +224,7 @@ class BaseObjectSerializer:
|
||||
if isinstance(obj, Enum):
|
||||
return obj.value
|
||||
|
||||
elif isinstance(obj, (list, tuple, set)):
|
||||
elif isinstance(obj, list | tuple | set):
|
||||
if not detach:
|
||||
return [self.traverse_value(o) for o in obj]
|
||||
|
||||
@@ -257,6 +259,7 @@ class BaseObjectSerializer:
|
||||
f"Failed to handle {type(obj)} in"
|
||||
" `BaseObjectSerializer.traverse_value`",
|
||||
SpeckleWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
return str(obj)
|
||||
@@ -342,7 +345,11 @@ class BaseObjectSerializer:
|
||||
object_type = Base.get_registered_type(speckle_type)
|
||||
|
||||
# initialise the base object using `speckle_type` fall back to base if needed
|
||||
base = object_type() if object_type else Base.of_type(speckle_type=speckle_type)
|
||||
base = (
|
||||
object_type.__new__(object_type)
|
||||
if object_type
|
||||
else Base.of_type(speckle_type=speckle_type)
|
||||
)
|
||||
# get total children count
|
||||
if "__closure" in obj:
|
||||
if not self.read_transport:
|
||||
@@ -370,6 +377,7 @@ class BaseObjectSerializer:
|
||||
f"Could not find the referenced child object of id `{ref_id}`"
|
||||
f" in the given read transport: {self.read_transport.name}",
|
||||
SpeckleWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
base.__setattr__(prop, self.handle_value(value))
|
||||
|
||||
@@ -433,6 +441,7 @@ class BaseObjectSerializer:
|
||||
f"Could not find the referenced child object of id `{ref_id}` in the"
|
||||
f" given read transport: {self.read_transport.name}",
|
||||
SpeckleWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return obj
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ class MemoryTransport(AbstractTransport):
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_object(self, id: str) -> str or None:
|
||||
return self.objects[id] if id in self.objects else None
|
||||
def get_object(self, id: str) -> str | None:
|
||||
return self.objects.get(id, None)
|
||||
|
||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||
return {id: (id in self.objects) for id in id_list}
|
||||
|
||||
@@ -11,7 +11,7 @@ from specklepy.logging.exceptions import SpeckleException
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BatchSender(object):
|
||||
class BatchSender:
|
||||
def __init__(
|
||||
self,
|
||||
server_url,
|
||||
@@ -123,8 +123,14 @@ class BatchSender(object):
|
||||
upload_data = "[" + ",".join(new_objects) + "]"
|
||||
upload_data_gzip = gzip.compress(upload_data.encode())
|
||||
LOG.info(
|
||||
"Uploading batch of %s objects (%s new): (size: %s, compressed size: %s)"
|
||||
% (len(batch), len(new_objects), len(upload_data), len(upload_data_gzip))
|
||||
"Uploading batch of {batch_size} objects {new_object_count}: ",
|
||||
"(size: {upload_size}, compressed size: {upload_data_size})",
|
||||
{
|
||||
"batch_size": len(batch),
|
||||
"new_object_count": len(new_objects),
|
||||
"upload_size": len(upload_data),
|
||||
"upload_data_size": len(upload_data_gzip),
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -74,7 +74,8 @@ class ServerTransport(AbstractTransport):
|
||||
SpeckleWarning(
|
||||
"Unauthenticated Speckle Client provided to Server Transport"
|
||||
f" for {url}. Receiving from private streams will fail."
|
||||
)
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
self.account = client.account
|
||||
|
||||
@@ -39,8 +39,7 @@ class SQLiteTransport(AbstractTransport):
|
||||
f"SQLiteTransport could not initialise {self.scope}.db at"
|
||||
f" {self._base_path}. Either provide a different `base_path` or use an"
|
||||
" alternative transport.",
|
||||
ex,
|
||||
)
|
||||
) from ex
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"SQLiteTransport(app: '{self.app_name}', scope: '{self.scope}')"
|
||||
@@ -105,10 +104,9 @@ class SQLiteTransport(AbstractTransport):
|
||||
raise SpeckleException(
|
||||
"Could not save the batch of objects to the local db. Inner exception:"
|
||||
f" {ex}",
|
||||
ex,
|
||||
)
|
||||
) from ex
|
||||
|
||||
def get_object(self, id: str) -> str or None:
|
||||
def get_object(self, id: str) -> str | None:
|
||||
self.__check_connection()
|
||||
with closing(self.__connection.cursor()) as c:
|
||||
row = c.execute(
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.models import ResourceCollection, User
|
||||
|
||||
|
||||
@@ -42,3 +42,22 @@ class TestActiveUserResource:
|
||||
assert len(res.items) == len(existing.items) + 2
|
||||
assert any(project.id == p1.id for project in res.items)
|
||||
assert any(project.id == p2.id for project in res.items)
|
||||
|
||||
def test_active_user_get_projects_with_filter(self, client: SpeckleClient):
|
||||
# Since the client may be reused for other tests,
|
||||
# this test does rely on no other test creating a project
|
||||
# with "Search for me" in its name
|
||||
p1 = client.project.create(
|
||||
ProjectCreateInput(name="Search for me!", description=None, visibility=None)
|
||||
)
|
||||
_ = client.project.create(
|
||||
ProjectCreateInput(name="But not me!", description=None, visibility=None)
|
||||
)
|
||||
filter = UserProjectsFilter(search="Search for me")
|
||||
|
||||
res = client.active_user.get_projects(filter=filter)
|
||||
|
||||
assert isinstance(res, ResourceCollection)
|
||||
assert len(res.items) == 1
|
||||
assert res.totalCount == 1
|
||||
assert res.items[0].id == p1.id
|
||||
|
||||
@@ -6,7 +6,10 @@ from specklepy.core.api.inputs.model_inputs import (
|
||||
DeleteModelInput,
|
||||
UpdateModelInput,
|
||||
)
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.core.api.inputs.project_inputs import (
|
||||
ProjectCreateInput,
|
||||
ProjectModelsFilter,
|
||||
)
|
||||
from specklepy.core.api.models.current import (
|
||||
Model,
|
||||
Project,
|
||||
@@ -65,6 +68,18 @@ class TestModelResource:
|
||||
assert result.createdAt == test_model.createdAt
|
||||
assert result.updatedAt == test_model.updatedAt
|
||||
|
||||
def test_models_get_with_filter(
|
||||
self, client: SpeckleClient, test_model: Model, test_project: Project
|
||||
):
|
||||
filter = ProjectModelsFilter(search=test_model.name)
|
||||
|
||||
result = client.model.get_models(test_project.id, models_filter=filter)
|
||||
|
||||
assert isinstance(result, ResourceCollection)
|
||||
assert len(result.items) == 1
|
||||
assert result.totalCount == 1
|
||||
assert result.items[0].id == test_model.id
|
||||
|
||||
def test_get_models(
|
||||
self, client: SpeckleClient, test_project: Project, test_model: Model
|
||||
):
|
||||
@@ -82,6 +97,20 @@ class TestModelResource:
|
||||
|
||||
assert isinstance(result, ProjectWithModels)
|
||||
assert result.id == test_project.id
|
||||
assert isinstance(result.models, ResourceCollection)
|
||||
assert len(result.models.items) == 1
|
||||
assert result.models.totalCount == 1
|
||||
assert result.models.items[0].id == test_model.id
|
||||
|
||||
def test_project_get_models_with_filter(
|
||||
self, client: SpeckleClient, test_project: Project, test_model: Model
|
||||
):
|
||||
filter = ProjectModelsFilter(search=test_model.name)
|
||||
result = client.project.get_with_models(test_project.id, models_filter=filter)
|
||||
|
||||
assert isinstance(result, ProjectWithModels)
|
||||
assert result.id == test_project.id
|
||||
assert isinstance(result.models, ResourceCollection)
|
||||
assert len(result.models.items) == 1
|
||||
assert result.models.totalCount == 1
|
||||
assert result.models.items[0].id == test_model.id
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.model_inputs import CreateModelInput
|
||||
from specklepy.core.api.inputs.model_inputs import CreateModelInput, ModelVersionsFilter
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.core.api.inputs.version_inputs import (
|
||||
DeleteVersionsInput,
|
||||
@@ -76,6 +76,26 @@ class TestVersionResource:
|
||||
assert result.totalCount == 1
|
||||
assert result.items[0].id == test_version.id
|
||||
|
||||
def test_versions_get_with_filter(
|
||||
self,
|
||||
client: SpeckleClient,
|
||||
test_model_1: Model,
|
||||
test_project: Project,
|
||||
test_version: Version,
|
||||
):
|
||||
filter = ModelVersionsFilter(
|
||||
priorityIds=[test_version.id], priorityIdsOnly=True
|
||||
)
|
||||
|
||||
result = client.version.get_versions(
|
||||
test_model_1.id, test_project.id, filter=filter
|
||||
)
|
||||
|
||||
assert isinstance(result, ResourceCollection)
|
||||
assert len(result.items) == 1
|
||||
assert result.totalCount == 1
|
||||
assert result.items[0].id == test_version.id
|
||||
|
||||
def test_version_received(
|
||||
self, client: SpeckleClient, test_version: Version, test_project: Project
|
||||
):
|
||||
@@ -103,6 +123,27 @@ class TestVersionResource:
|
||||
assert result.versions.totalCount == 1
|
||||
assert result.versions.items[0].id == test_version.id
|
||||
|
||||
def test_model_get_with_versions_with_filter(
|
||||
self,
|
||||
client: SpeckleClient,
|
||||
test_model_1: Model,
|
||||
test_project: Project,
|
||||
test_version: Version,
|
||||
):
|
||||
filter = ModelVersionsFilter(
|
||||
priorityIds=[test_version.id], priorityIdsOnly=True
|
||||
)
|
||||
|
||||
result = client.model.get_with_versions(
|
||||
test_model_1.id, test_project.id, versions_filter=filter
|
||||
)
|
||||
|
||||
assert isinstance(result, ModelWithVersions)
|
||||
assert len(result.versions.items) == 1
|
||||
assert result.versions.totalCount == 1
|
||||
assert isinstance(result.versions, ResourceCollection)
|
||||
assert result.versions.items[0].id == test_version.id
|
||||
|
||||
def test_version_update(
|
||||
self, client: SpeckleClient, test_version: Version, test_project: Project
|
||||
):
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.models import Stream
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.encoding import ObjectArray
|
||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
|
||||
|
||||
class TestObject:
|
||||
@pytest.fixture(scope="module")
|
||||
def stream(self, client):
|
||||
stream = Stream(
|
||||
name="a sample stream for testing",
|
||||
description="a stream created for testing",
|
||||
isPublic=True,
|
||||
)
|
||||
stream.id = client.stream.create(
|
||||
stream.name, stream.description, stream.isPublic
|
||||
)
|
||||
return stream
|
||||
|
||||
def test_object_create(self, client, stream, base):
|
||||
transport = SQLiteTransport()
|
||||
s = BaseObjectSerializer(write_transports=[transport], read_transport=transport)
|
||||
_, base_dict = s.traverse_base(base)
|
||||
obj_id = client.object.create(stream_id=stream.id, objects=[base_dict])[0]
|
||||
|
||||
assert isinstance(obj_id, str)
|
||||
assert base_dict["@detach"]["speckle_type"] == "reference"
|
||||
assert obj_id == base.get_id(True)
|
||||
|
||||
def test_object_get(self, client, stream, base):
|
||||
fetched_base = client.object.get(
|
||||
stream_id=stream.id, object_id=base.get_id(True)
|
||||
)
|
||||
|
||||
assert isinstance(fetched_base, Base)
|
||||
assert fetched_base.name == base.name
|
||||
assert isinstance(fetched_base.vertices, list)
|
||||
# assert fetched_base["@detach"]["speckle_type"] == "reference"
|
||||
|
||||
def test_object_array_decoder(self):
|
||||
array = ObjectArray()
|
||||
array.data = [5, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1]
|
||||
|
||||
assert array.decode(decoder=sum) == [5, 4, 3, 2, 1]
|
||||
@@ -33,8 +33,8 @@ class TestOtherUser:
|
||||
assert isinstance(fetched_user, LimitedUser)
|
||||
assert fetched_user.name == second_user_dict["name"]
|
||||
# changed in the server, now you cannot get emails of other users
|
||||
# not checking this, since the first user could or could not be an admin on the server
|
||||
# admins can get emails of others, regular users can't
|
||||
# not checking this, since the first user could or could not be an admin
|
||||
# on the server, admins can get emails of others, regular users can't
|
||||
# assert fetched_user.email == None
|
||||
|
||||
second_user_dict["id"] = fetched_user.id
|
||||
|
||||
@@ -36,8 +36,8 @@ class TestUser:
|
||||
assert isinstance(fetched_user, User)
|
||||
assert fetched_user.name == second_user_dict["name"]
|
||||
# changed in the server, now you cannot get emails of other users
|
||||
# not checking this, since the first user could or could not be an admin on the server
|
||||
# admins can get emails of others, regular users can't
|
||||
# not checking this, since the first user could or could not be an admin
|
||||
# on the server, admins can get emails of others, regular users can't
|
||||
# assert fetched_user.email == None
|
||||
|
||||
second_user_dict["id"] = fetched_user.id
|
||||
|
||||
@@ -12,7 +12,7 @@ from specklepy.core.api.inputs.version_inputs import CreateVersionInput
|
||||
from specklepy.core.api.models import Stream, Version
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.fakemesh import FakeDirection, FakeMesh
|
||||
from .fakemesh import FakeMesh, FakeDirection
|
||||
from specklepy.objects.geometry import Point
|
||||
from specklepy.transports.server.server import ServerTransport
|
||||
|
||||
@@ -44,7 +44,8 @@ def seed_user(host: str) -> Dict[str, str]:
|
||||
if not r.ok:
|
||||
raise Exception(f"Cannot seed user: {r.reason}")
|
||||
redirect_url = urlparse(r.headers.get("location"))
|
||||
access_code = parse_qs(redirect_url.query)["access_code"][0] # type: ignore
|
||||
access_code = parse_qs(redirect_url.query)[
|
||||
"access_code"][0] # type: ignore
|
||||
|
||||
r_tokens = requests.post(
|
||||
url=f"http://{host}/auth/token",
|
||||
@@ -113,7 +114,8 @@ def sample_stream(client: SpeckleClient) -> Stream:
|
||||
description="a stream created for testing",
|
||||
isPublic=True,
|
||||
)
|
||||
stream.id = client.stream.create(stream.name, stream.description, stream.isPublic)
|
||||
stream.id = client.stream.create(
|
||||
stream.name, stream.description, stream.isPublic)
|
||||
return stream
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import List, Optional
|
||||
|
||||
from specklepy.objects.geometry import Point
|
||||
|
||||
from .base import Base
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
CHUNKABLE_PROPS = {
|
||||
"vertices": 100,
|
||||
@@ -16,7 +16,9 @@ CHUNKABLE_PROPS = {
|
||||
DETACHABLE = {"detach_this", "origin", "detached_list"}
|
||||
|
||||
|
||||
class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}):
|
||||
class FakeGeo(
|
||||
Base, speckle_type="FakeGeo", chunkable={"dots": 50}, detachable={"pointslist"}
|
||||
):
|
||||
pointslist: Optional[List[Base]] = None
|
||||
dots: Optional[List[int]] = None
|
||||
|
||||
@@ -28,7 +30,9 @@ class FakeDirection(Enum):
|
||||
WEST = 4
|
||||
|
||||
|
||||
class FakeMesh(FakeGeo, chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE):
|
||||
class FakeMesh(
|
||||
FakeGeo, speckle_type="FakeMesh", chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE
|
||||
):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Run integration tests with a speckle server."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
@@ -3,8 +3,8 @@ import json
|
||||
import pytest
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.fakemesh import FakeMesh
|
||||
from specklepy.objects.base import Base
|
||||
from .fakemesh import FakeMesh
|
||||
from specklepy.objects.geometry import Point
|
||||
from specklepy.transports.memory import MemoryTransport
|
||||
from specklepy.transports.server import ServerTransport
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import contextlib
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -16,14 +17,11 @@ def user_path() -> Iterable[Path]:
|
||||
speckle_path_provider.override_application_data_path(tempfile.gettempdir())
|
||||
path = speckle_path_provider.accounts_folder_path().joinpath("test_acc.json")
|
||||
# hey, py37 doesn't support the missing_ok argument
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
path.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
path.parent.absolute().mkdir(exist_ok=True)
|
||||
yield path
|
||||
if path.exists():
|
||||
@@ -34,7 +32,7 @@ def user_path() -> Iterable[Path]:
|
||||
def test_parse_empty():
|
||||
try:
|
||||
StreamWrapper("https://testing.speckle.dev/streams")
|
||||
assert False
|
||||
raise AssertionError()
|
||||
except SpeckleException:
|
||||
assert True
|
||||
|
||||
@@ -42,7 +40,7 @@ def test_parse_empty():
|
||||
def test_parse_empty_fe2():
|
||||
try:
|
||||
StreamWrapper("https://latest.speckle.systems/projects")
|
||||
assert False
|
||||
raise AssertionError()
|
||||
except SpeckleException:
|
||||
assert True
|
||||
|
||||
@@ -169,7 +167,7 @@ def test_parse_model():
|
||||
def test_parse_federated_model():
|
||||
try:
|
||||
StreamWrapper("https://latest.speckle.systems/projects/843d07eb10/models/$main")
|
||||
assert False
|
||||
raise AssertionError()
|
||||
except SpeckleException:
|
||||
assert True
|
||||
|
||||
@@ -179,7 +177,7 @@ def test_parse_multi_model():
|
||||
StreamWrapper(
|
||||
"https://latest.speckle.systems/projects/2099ac4b5f/models/1870f279e3,a9cfdddc79"
|
||||
)
|
||||
assert False
|
||||
raise AssertionError()
|
||||
except SpeckleException:
|
||||
assert True
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user