Skip to content

API for geometry #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/geometry_from_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import flow360 as fl
from flow360.component.geometry import Geometry
from flow360.examples import Octahedron

geometry = Geometry.from_file(Octahedron.geometry, name="octahedron")
geometry = geometry.submit()

print(geometry)
3 changes: 3 additions & 0 deletions flow360/cloud/s3_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class S3TransferType(Enum):
Enum for s3 transfer type
"""

GEOMETRY = "Geometry"
VOLUME_MESH = "VolumeMesh"
SURFACE_MESH = "SurfaceMesh"
CASE = "Case"
Expand All @@ -174,6 +175,8 @@ def _get_grant_url(self, resource_id, file_name: str) -> str:
return f"surfacemeshes/{resource_id}/file?filename={file_name}"
if self is S3TransferType.CASE:
return f"cases/{resource_id}/file?filename={file_name}"
if self is S3TransferType.GEOMETRY:
return f"geometries/{resource_id}/file?filename={file_name}"

return None

Expand Down
244 changes: 244 additions & 0 deletions flow360/component/geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
"""
Geometry component
"""

from __future__ import annotations

import os
import re
from typing import List, Union

from ..cloud.rest_api import RestApi
from ..exceptions import Flow360FileError, Flow360ValueError
from ..log import log
from .interfaces import GeometryInterface
from .resource_base import Flow360Resource, Flow360ResourceBaseModel, ResourceDraft
from .utils import shared_account_confirm_proceed, validate_type

SUPPORTED_GEOMETRY_FILE_PATTERNS = [
".sat",
".sab",
".asat",
".asab",
".iam",
".catpart",
".catproduct",
".igs",
".iges",
".gt",
".prt",
".prt.*",
".asm.*",
".par",
".asm",
".psm",
".sldprt",
".sldasm",
".stp",
".step",
".x_t",
".xmt_txt",
".x_b",
".xmt_bin",
".3dm",
".ipt",
]


def _match_file_pattern(patterns, filename):
for pattern in patterns:
if re.search(pattern + "$", filename.lower()) is not None:
return True
return False


class Geometry(Flow360Resource):
"""
Geometry component
"""

# pylint: disable=redefined-builtin
def __init__(self, id: str):
super().__init__(
interface=GeometryInterface,
info_type_class=GeometryMeta,
id=id,
)
self._params = None

@classmethod
def _from_meta(cls, meta: GeometryMeta):
validate_type(meta, "meta", GeometryMeta)
geometry = cls(id=meta.id)
geometry._set_meta(meta)
return geometry

@property
def info(self) -> GeometryMeta:
return super().info

@classmethod
def _interface(cls):
return GeometryInterface

@classmethod
def _meta_class(cls):
"""
returns geometry meta info class: GeometryMeta
"""
return GeometryMeta

def _complete_upload(self, remote_file_names: List[str]):
"""
Complete geometry files upload
:return:
"""
for remote_file_name in remote_file_names:
resp = self.post({}, method=f"completeUpload?fileName={remote_file_name}")
self._info = GeometryMeta(**resp)

@classmethod
def from_cloud(cls, geometry_id: str):
"""
Get geometry from cloud
:param geometry_id:
:return:
"""
return cls(geometry_id)

@classmethod
def from_file(
cls,
file_names: Union[List[str], str],
name: str = None,
tags: List[str] = None,
):
"""
Create geometry from geometry files
:param file_names:
:param name:
:param tags:
:param solver_version:
:return:
"""
if isinstance(file_names, str):
file_names = [file_names]
return GeometryDraft(
file_names=file_names,
name=name,
tags=tags,
)


class GeometryMeta(Flow360ResourceBaseModel):
"""
GeometryMeta component
"""

def to_geometry(self) -> Geometry:
"""
returns Geometry object from geometry meta info
"""
return Geometry(self.id)


class GeometryDraft(ResourceDraft):
"""
Geometry Draft component
"""

# pylint: disable=too-many-arguments
def __init__(
self,
file_names: List[str],
name: str = None,
tags: List[str] = None,
solver_version=None,
):
self._file_names = file_names
self.name = name
self.tags = tags
self.solver_version = solver_version
self._id = None
self._validate()
ResourceDraft.__init__(self)

def _validate(self):
self._validate_geometry()

# pylint: disable=consider-using-f-string
def _validate_geometry(self):
if not isinstance(self.file_names, list):
raise Flow360FileError("file_names field has to be a list.")
for geometry_file in self.file_names:
_, ext = os.path.splitext(geometry_file)
if not _match_file_pattern(SUPPORTED_GEOMETRY_FILE_PATTERNS, geometry_file):
raise Flow360FileError(
"Unsupported geometry file extensions: {}. Supported: [{}].".format(
ext.lower(), ", ".join(SUPPORTED_GEOMETRY_FILE_PATTERNS)
)
)

if not os.path.exists(geometry_file):
raise Flow360FileError(f"{geometry_file} not found.")

if self.name is None and len(self.file_names) > 1:
raise Flow360ValueError(
"name field is required if more than one geometry files are provided."
)

@property
def file_names(self) -> List[str]:
"""geometry file"""
return self._file_names

# pylint: disable=protected-access
# pylint: disable=duplicate-code
def submit(self, progress_callback=None) -> Geometry:
"""submit geometry to cloud

Parameters
----------
progress_callback : callback, optional
Use for custom progress bar, by default None

Returns
-------
Geometry
Geometry object with id
"""

self._validate()
name = self.name
if name is None:
name = os.path.splitext(os.path.basename(self.file_names[0]))[0]
self.name = name

if not shared_account_confirm_proceed():
raise Flow360ValueError("User aborted resource submit.")

data = {
"name": self.name,
"tags": self.tags,
}

if self.solver_version:
data["solverVersion"] = self.solver_version

resp = RestApi(GeometryInterface.endpoint).post(data)
info = GeometryMeta(**resp)
self._id = info.id
submitted_geometry = Geometry(self.id)

remote_file_names = []
for index, geometry_file in enumerate(self.file_names):
_, ext = os.path.splitext(geometry_file)
remote_file_name = f"geometry_{index}{ext}"
file_name_to_upload = geometry_file
submitted_geometry._upload_file(
remote_file_name, file_name_to_upload, progress_callback=progress_callback
)
remote_file_names.append(remote_file_name)
submitted_geometry._complete_upload(remote_file_names)
log.info(f"Geometry successfully submitted: {submitted_geometry.short_description()}")
return submitted_geometry
5 changes: 5 additions & 0 deletions flow360/component/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,10 @@ class BaseInterface(BaseModel):
resource_type="Case", s3_transfer_method=S3TransferType.CASE, endpoint="cases"
)

GeometryInterface = BaseInterface(
resource_type="Geometry",
s3_transfer_method=S3TransferType.GEOMETRY,
endpoint="geometries",
)

FolderInterface = BaseInterface(resource_type="Folder", s3_transfer_method=None, endpoint="folders")
5 changes: 4 additions & 1 deletion flow360/component/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class Validator(Enum):
""" ":class: Validator"""

GEOMETRY = "Geometry"
VOLUME_MESH = "VolumeMesh"
SURFACE_MESH = "SurfaceMesh"
CASE = "Case"
Expand All @@ -26,6 +27,8 @@ def _get_url(self):
return "validator/surfacemesh/validate"
if self is Validator.CASE:
return "validator/case/validate"
if self is Validator.GEOMETRY:
return "validator/geometry/validate"

return None

Expand All @@ -40,7 +43,7 @@ def validate(

Parameters
----------
params : Union[Flow360Params, SurfaceMeshingParams]
params : Union[Flow360Params, SurfaceMeshingParams, VolumeMeshingParams]
flow360 parameters to validate
solver_version : str, optional
solver version, by default None
Expand Down
2 changes: 2 additions & 0 deletions flow360/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .convergence import Convergence
from .cylinder import Cylinder
from .monitors import MonitorsAndSlices
from .octahedron import Octahedron
from .om6wing import OM6wing
from .om6wing_user_defined_dynamics import OM6wingUserDefinedDynamics
from .rotating_spheres import RotatingSpheres
Expand All @@ -20,4 +21,5 @@
"OM6wing",
"OM6wingUserDefinedDynamics",
"RotatingSpheres",
"Octahedron",
]
12 changes: 12 additions & 0 deletions flow360/examples/octahedron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
octahdron geometry example
"""

from .base_test_case import BaseTestCase


class Octahedron(BaseTestCase):
name = "octahedron"

class url:
geometry = "local://Trunc.SLDASM"
Binary file added flow360/examples/octahedron/Trunc.SLDASM
Binary file not shown.
Binary file added tests/data/geometry/Trunc.SLDASM
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/test_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest

import pytest

from flow360 import exceptions as ex
from flow360.component.geometry import Geometry

assertions = unittest.TestCase("__init__")


@pytest.fixture(autouse=True)
def change_test_dir(request, monkeypatch):
monkeypatch.chdir(request.fspath.dirname)


def test_draft_geometry_from_file():
with pytest.raises(ex.Flow360FileError, match="Unsupported geometry file extensions"):
sm = Geometry.from_file("file.unsupported")

with pytest.raises(ex.Flow360FileError, match="not found"):
sm = Geometry.from_file("data/geometry/no_exist.step")

sm = Geometry.from_file("data/geometry/Trunc.SLDASM")
sm = Geometry.from_file(["data/geometry/Trunc.SLDASM"])
assert sm
Loading