Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6fc5c6bd4 | |||
| 2f84214786 | |||
| 0fe1af8e75 | |||
| 6297943fe1 | |||
| 428bbe2c3d | |||
| 0ca22891bc |
@@ -97,10 +97,7 @@ services:
|
||||
|
||||
STRATEGY_LOCAL: "true"
|
||||
|
||||
POSTGRES_URL: "postgres"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle"
|
||||
POSTGRES_URL: 'postgres://speckle:speckle@postgres:5432/speckle'
|
||||
ENABLE_MP: "false"
|
||||
|
||||
LOG_PRETTY: "true"
|
||||
|
||||
@@ -12,12 +12,23 @@ def data_object_to_speckle(
|
||||
step_element: entity_instance,
|
||||
children: list[Base],
|
||||
current_storey: str | None = None,
|
||||
parent_element: entity_instance | None = None,
|
||||
) -> DataObject:
|
||||
guid = cast(str, step_element.GlobalId)
|
||||
name = cast(str, step_element.Name or guid)
|
||||
|
||||
properties = extract_properties(step_element)
|
||||
|
||||
# Add parent ID only if element's parent is also a DataObject (not a Collection)
|
||||
# Collections are: IfcProject and IfcSpatialStructureElement types
|
||||
if (
|
||||
parent_element
|
||||
and hasattr(parent_element, "GlobalId")
|
||||
and not parent_element.is_a("IfcProject")
|
||||
and not parent_element.is_a("IfcSpatialStructureElement")
|
||||
):
|
||||
properties["parentApplicationId"] = parent_element.GlobalId
|
||||
|
||||
# Add building storey information if available and not a building storey itself
|
||||
if current_storey and not step_element.is_a("IfcBuildingStorey"):
|
||||
properties["Building Storey"] = current_storey
|
||||
|
||||
@@ -51,4 +51,10 @@ def open_ifc(file_path: str) -> file:
|
||||
|
||||
|
||||
def create_geometry_iterator(ifc_file: file | sqlite) -> iterator:
|
||||
return iterator(_create_iterator_settings(), ifc_file, multiprocessing.cpu_count())
|
||||
GEOMETRY_LIBRARY = "hybrid-opencascade-cgal" # First OCC then fallback to CGAL
|
||||
return iterator(
|
||||
_create_iterator_settings(),
|
||||
ifc_file,
|
||||
multiprocessing.cpu_count(),
|
||||
geometry_library=GEOMETRY_LIBRARY, # type: ignore
|
||||
)
|
||||
|
||||
@@ -44,9 +44,13 @@ class ImportJob:
|
||||
_display_value_cache: dict[int, list[Base]] = field(default_factory=dict)
|
||||
"""Maps an instance step ID to a list of instances"""
|
||||
|
||||
def convert_element(self, step_element: entity_instance) -> Base:
|
||||
def convert_element(
|
||||
self,
|
||||
step_element: entity_instance,
|
||||
parent_element: entity_instance | None = None,
|
||||
) -> Base:
|
||||
try:
|
||||
return self._convert_element(step_element)
|
||||
return self._convert_element(step_element, parent_element)
|
||||
except SpeckleException:
|
||||
raise
|
||||
except Exception as ex:
|
||||
@@ -54,14 +58,18 @@ class ImportJob:
|
||||
f"Failed to convert {step_element.is_a()} #{step_element.id()}"
|
||||
) from ex
|
||||
|
||||
def _convert_element(self, step_element: entity_instance) -> Base:
|
||||
def _convert_element(
|
||||
self,
|
||||
step_element: entity_instance,
|
||||
parent_element: entity_instance | None = None,
|
||||
) -> Base:
|
||||
# Track current storey context and store for level proxies
|
||||
previous_storey_data_object = self._current_storey_data_object
|
||||
if step_element.is_a("IfcBuildingStorey"):
|
||||
# Convert the building storey to a DataObject for the level proxy
|
||||
storey_display_value = self._display_value_cache.get(step_element.id(), [])
|
||||
self._current_storey_data_object = data_object_to_speckle(
|
||||
storey_display_value, step_element, []
|
||||
storey_display_value, step_element, [], parent_element=None
|
||||
)
|
||||
|
||||
children = self._convert_children(step_element)
|
||||
@@ -86,7 +94,11 @@ class ImportJob:
|
||||
)
|
||||
else:
|
||||
result = data_object_to_speckle(
|
||||
display_value, step_element, children, current_storey_name
|
||||
display_value,
|
||||
step_element,
|
||||
children,
|
||||
current_storey_name,
|
||||
parent_element,
|
||||
)
|
||||
# Associate non-spatial elements with current storey for level proxies
|
||||
if self._current_storey_data_object is not None and result.applicationId:
|
||||
@@ -100,7 +112,7 @@ class ImportJob:
|
||||
|
||||
def _convert_children(self, step_element: entity_instance) -> list[Base]:
|
||||
return [
|
||||
self.convert_element(i)
|
||||
self.convert_element(i, parent_element=step_element)
|
||||
for i in get_children(step_element)
|
||||
if self._should_convert(i)
|
||||
]
|
||||
|
||||
@@ -23,6 +23,7 @@ from specklepy.transports.server import ServerTransport
|
||||
def open_and_convert_file(
|
||||
file_path: str,
|
||||
project: Project,
|
||||
version_message: str,
|
||||
model_ingestion_id: str,
|
||||
client: SpeckleClient,
|
||||
) -> Version:
|
||||
@@ -86,7 +87,7 @@ def open_and_convert_file(
|
||||
project_id=project.id,
|
||||
ingestion_id=model_ingestion_id,
|
||||
root_object_id=root_id,
|
||||
# version_message=version_message,
|
||||
version_message=version_message,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
from deprecated import deprecated
|
||||
from typing_extensions import override
|
||||
|
||||
from specklepy.core.api.inputs import (
|
||||
@@ -10,7 +11,10 @@ from specklepy.core.api.inputs import (
|
||||
from specklepy.core.api.models import FileImport, FileUploadUrl
|
||||
from specklepy.core.api.models.current import ResourceCollection
|
||||
from specklepy.core.api.resources import FileImportResource as CoreResource
|
||||
from specklepy.core.api.resources.current.file_import_resource import UploadFileResponse
|
||||
from specklepy.core.api.resources.current.file_import_resource import (
|
||||
FILE_UPLOAD_DEPRECATION_WARNING,
|
||||
UploadFileResponse,
|
||||
)
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
@@ -25,11 +29,6 @@ class FileImportResource(CoreResource):
|
||||
server_version=server_version,
|
||||
)
|
||||
|
||||
@override
|
||||
def start_file_import(self, input: StartFileImportInput) -> FileImport:
|
||||
metrics.track(metrics.SDK, self.account, {"name": "File Import Start"})
|
||||
return super().start_file_import(input)
|
||||
|
||||
@override
|
||||
def generate_upload_url(self, input: GenerateFileUploadUrlInput) -> FileUploadUrl:
|
||||
"""
|
||||
@@ -61,6 +60,13 @@ class FileImportResource(CoreResource):
|
||||
metrics.track(metrics.SDK, self.account, {"name": "File Import Download File"})
|
||||
return super().download_file(project_id, file_id, target_file)
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
@override
|
||||
def start_file_import(self, input: StartFileImportInput) -> FileImport:
|
||||
metrics.track(metrics.SDK, self.account, {"name": "File Import Start"})
|
||||
return super().start_file_import(input)
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
@override
|
||||
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
|
||||
"""
|
||||
@@ -72,6 +78,7 @@ class FileImportResource(CoreResource):
|
||||
metrics.track(metrics.SDK, self.account, {"name": "File Import Finish Job"})
|
||||
return super().finish_file_import_job(input)
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
@override
|
||||
def get_model_file_import_jobs(
|
||||
self,
|
||||
|
||||
@@ -26,6 +26,10 @@ class ModelIngestionResource(CoreResource):
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Ingestion Create"})
|
||||
return super().create(input)
|
||||
|
||||
def get_ingestion(self, project_id: str, model_ingestion_id: str) -> ModelIngestion:
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Ingestion Get"})
|
||||
return super().get_ingestion(project_id, model_ingestion_id)
|
||||
|
||||
def update_progress(self, input: ModelIngestionUpdateInput) -> ModelIngestion:
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Ingestion Update"})
|
||||
return super().update_progress(input)
|
||||
|
||||
@@ -40,6 +40,7 @@ class ModelIngestionSuccessInput(GraphQLBaseModel):
|
||||
ingestion_id: str
|
||||
project_id: str
|
||||
root_object_id: str
|
||||
version_message: str | None
|
||||
|
||||
|
||||
class ModelIngestionFailedInput(GraphQLBaseModel):
|
||||
|
||||
@@ -2,6 +2,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from deprecated import deprecated
|
||||
from gql import Client, gql
|
||||
|
||||
from specklepy.core.api.credentials import Account
|
||||
@@ -23,6 +24,16 @@ class UploadFileResponse(GraphQLBaseModel):
|
||||
etag: str
|
||||
|
||||
|
||||
FILE_UPLOAD_DEPRECATION_WARNING: dict[str, Any] = {
|
||||
"version": "3.2.4",
|
||||
"reason": (
|
||||
"Part of the old API surface"
|
||||
"and will be removed in the future. Use the new ingestion API instead."
|
||||
"Field will be deleted on June 1st, 2026"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class FileImportResource(ResourceBase):
|
||||
"""API Access class for file imports"""
|
||||
|
||||
@@ -41,59 +52,6 @@ class FileImportResource(ResourceBase):
|
||||
name=NAME,
|
||||
)
|
||||
|
||||
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
|
||||
"""
|
||||
This is mostly an internal api, that marks a file import job finished.
|
||||
|
||||
Only use this if you are writing a file importer, that is responsible for
|
||||
processing file import jobs.
|
||||
"""
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation FinishFileImport($input: FinishFileImportInput!) {
|
||||
data:fileUploadMutations {
|
||||
data:finishFileImport(input: $input)
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[DataResponse[bool]], QUERY, variables
|
||||
).data.data
|
||||
|
||||
def start_file_import(self, input: StartFileImportInput) -> FileImport:
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation StartFileImport($input: StartFileImportInput!) {
|
||||
data:fileUploadMutations {
|
||||
data:startFileImport(input: $input) {
|
||||
id
|
||||
projectId
|
||||
convertedVersionId
|
||||
userId
|
||||
convertedStatus
|
||||
convertedMessage
|
||||
modelId
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[DataResponse[FileImport]], QUERY, variables
|
||||
).data.data
|
||||
|
||||
def generate_upload_url(self, input: GenerateFileUploadUrlInput) -> FileUploadUrl:
|
||||
"""
|
||||
Get a file upload url from the Speckle server.
|
||||
@@ -161,6 +119,62 @@ class FileImportResource(ResourceBase):
|
||||
_ = f.write(chunk)
|
||||
return target_file
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
def start_file_import(self, input: StartFileImportInput) -> FileImport:
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation StartFileImport($input: StartFileImportInput!) {
|
||||
data:fileUploadMutations {
|
||||
data:startFileImport(input: $input) {
|
||||
id
|
||||
projectId
|
||||
convertedVersionId
|
||||
userId
|
||||
convertedStatus
|
||||
convertedMessage
|
||||
modelId
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[DataResponse[FileImport]], QUERY, variables
|
||||
).data.data
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
def finish_file_import_job(self, input: FinishFileImportInput) -> bool:
|
||||
"""
|
||||
This is mostly an internal api, that marks a file import job finished.
|
||||
|
||||
Only use this if you are writing a file importer, that is responsible for
|
||||
processing file import jobs.
|
||||
"""
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation FinishFileImport($input: FinishFileImportInput!) {
|
||||
data:fileUploadMutations {
|
||||
data:finishFileImport(input: $input)
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[DataResponse[bool]], QUERY, variables
|
||||
).data.data
|
||||
|
||||
@deprecated(**FILE_UPLOAD_DEPRECATION_WARNING)
|
||||
def get_model_file_import_jobs(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Optional, Tuple
|
||||
from typing import Any, Tuple
|
||||
|
||||
from gql import Client, gql
|
||||
|
||||
@@ -30,7 +30,7 @@ class ModelIngestionResource(ResourceBase):
|
||||
account: Account,
|
||||
basepath: str,
|
||||
client: Client,
|
||||
server_version: Optional[Tuple[Any, ...]],
|
||||
server_version: Tuple[Any, ...] | None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
@@ -40,24 +40,23 @@ class ModelIngestionResource(ResourceBase):
|
||||
server_version=server_version,
|
||||
)
|
||||
|
||||
def get_ingestion(self, project_id: str, model_id: str) -> ModelIngestion:
|
||||
def get_ingestion(self, project_id: str, model_ingestion_id: str) -> ModelIngestion:
|
||||
QUERY = gql(
|
||||
"""
|
||||
query Query($projectId: String!, $modelId: String!) {
|
||||
query Query($projectId: String!, $modelIngestionId: ID!) {
|
||||
data:project(id: $projectId) {
|
||||
data:model(id: $modelId) {
|
||||
data:ingestion {
|
||||
id
|
||||
createdAt
|
||||
modelId
|
||||
cancellationRequested
|
||||
statusData {
|
||||
... on HasModelIngestionStatus {
|
||||
status
|
||||
}
|
||||
... on HasProgressMessage {
|
||||
progressMessage
|
||||
}
|
||||
data:ingestion(id: $modelIngestionId) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
modelId
|
||||
cancellationRequested
|
||||
statusData {
|
||||
... on HasModelIngestionStatus {
|
||||
status
|
||||
}
|
||||
... on HasProgressMessage {
|
||||
progressMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,14 +67,14 @@ class ModelIngestionResource(ResourceBase):
|
||||
|
||||
variables = {
|
||||
"projectId": project_id,
|
||||
"modelId": model_id,
|
||||
"modelIngestionId": model_ingestion_id,
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[DataResponse[DataResponse[ModelIngestion]]],
|
||||
DataResponse[DataResponse[ModelIngestion]],
|
||||
QUERY,
|
||||
variables,
|
||||
).data.data.data
|
||||
).data.data
|
||||
|
||||
def create(self, input: ModelIngestionCreateInput) -> ModelIngestion:
|
||||
QUERY = gql(
|
||||
|
||||
@@ -79,6 +79,15 @@ class TestIngestionResource:
|
||||
|
||||
return ingestion
|
||||
|
||||
def test_get_ingestion(
|
||||
self, client: SpeckleClient, project: Project, ingestion: ModelIngestion
|
||||
):
|
||||
queried_ingestion = client.model_ingestion.get_ingestion(
|
||||
project.id, ingestion.id
|
||||
)
|
||||
assert queried_ingestion.id == ingestion.id
|
||||
assert queried_ingestion.status_data.status == ingestion.status_data.status
|
||||
|
||||
def test_update_progress(
|
||||
self, client: SpeckleClient, ingestion: ModelIngestion, project: Project
|
||||
):
|
||||
@@ -198,7 +207,7 @@ class TestIngestionResource:
|
||||
ingestion_id=ingestion.id,
|
||||
root_object_id=object_id,
|
||||
project_id=project.id,
|
||||
# version_message=None,
|
||||
version_message=None,
|
||||
)
|
||||
|
||||
res = client.model_ingestion.complete(input)
|
||||
@@ -262,7 +271,7 @@ class TestIngestionResource:
|
||||
ingestion_id=ingestion.id,
|
||||
root_object_id="asdfasdfasdfasfd",
|
||||
project_id=project.id,
|
||||
# version_message=None,
|
||||
version_message=None,
|
||||
)
|
||||
with pytest.raises(GraphQLException):
|
||||
_ = client.model_ingestion.complete(input)
|
||||
|
||||
Reference in New Issue
Block a user