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 1 commit
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
246 changes: 246 additions & 0 deletions flow360/component/geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
"""
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

supportedGeometryFilePatterns = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use UPPER_CASE here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

".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,
geometry_files: Union[List[str], str],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file_names would be better name here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

name: str = None,
tags: List[str] = None,
):
"""
Create geometry from geometry files
:param geometry_files:
:param name:
:param tags:
:param solver_version:
:return:
"""
if isinstance(geometry_files, str):
geometry_files = [geometry_files]
return GeometryDraft(
geometry_files=geometry_files,
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,
geometry_files: List[str],
name: str = None,
tags: List[str] = None,
solver_version=None,
):
self._geometry_files = geometry_files
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.geometry_files, list):
raise Flow360FileError("geometry_files field has to be a list.")
for geometry_file in self.geometry_files:
_, ext = os.path.splitext(geometry_file)
if not _match_file_pattern(supportedGeometryFilePatterns, geometry_file):
raise Flow360FileError(
"Unsupported geometry file extensions: {}. Supported: [{}].".format(
ext.lower(), ", ".join(supportedGeometryFilePatterns)
)
)

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

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

@property
def geometry_files(self) -> List[str]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file_names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. all geometry_files are replaced by file_names

"""geometry file"""
return self._geometry_files

# 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.geometry_files[0]))[0]
self.name = name

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

data = {
"name": self.name,
"tags": self.tags,
}
print("debug=============")
print(data)

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

resp = RestApi(GeometryInterface.endpoint).post(data)
info = GeometryMeta(**resp)
self._id = info.id
submitted_mesh = Geometry(self.id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

submitted_geometry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


remote_file_names = []
for index, geometry_file in enumerate(self.geometry_files):
_, ext = os.path.splitext(geometry_file)
remote_file_name = f"geometry_{index}{ext}"
file_name_to_upload = geometry_file
submitted_mesh._upload_file(
remote_file_name, file_name_to_upload, progress_callback=progress_callback
)
remote_file_names.append(remote_file_name)
submitted_mesh._complete_upload(remote_file_names)
log.info(f"Geometry successfully submitted: {submitted_mesh.short_description()}")
return submitted_mesh
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