Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c6f3a4f
feat(HorizonStack): Add Horizon Stack to the horizon step
SpliiT Jul 8, 2026
8b9dd98
fix utils functions
SpliiT Jul 13, 2026
7a98960
Apply prepare changes
SpliiT Jul 13, 2026
8b7ade0
add missing test.og_hst3d
SpliiT Jul 13, 2026
72b1089
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 13, 2026
49fa924
Merge branch 'next' into feat/HorizonStack
SpliiT Jul 13, 2026
28cd18e
Apply prepare changes
SpliiT Jul 13, 2026
21df80a
resolve mypy errors on GeodeHorizonStack3D and utils
SpliiT Jul 13, 2026
f656590
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 13, 2026
c75f16c
Apply prepare changes
SpliiT Jul 13, 2026
c9f3d0b
rm comments
SpliiT Jul 15, 2026
9583a1e
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 15, 2026
7f9b1d2
Apply prepare changes
SpliiT Jul 15, 2026
2a0ecbd
fix comments
SpliiT Jul 15, 2026
05eec1a
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 15, 2026
6785a86
reput asserts data
SpliiT Jul 17, 2026
bb604f2
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
SpliiT Jul 23, 2026
7d75d71
clear validate
SpliiT Jul 24, 2026
92b1133
Apply prepare changes
SpliiT Jul 24, 2026
0a81e56
rm useless hasattr
SpliiT Jul 24, 2026
8eef857
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 24, 2026
24f4de5
Apply prepare changes
SpliiT Jul 24, 2026
69195d2
try to go around horizonstack saveable
SpliiT Jul 24, 2026
0450aef
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 24, 2026
628d5be
rc geosciences
SpliiT Jul 24, 2026
b704741
Apply prepare changes
SpliiT Jul 24, 2026
9fa68f4
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
SpliiT Jul 24, 2026
cf62a7c
Merge branch 'feat/HorizonStack' of https://github.com/Geode-solution…
SpliiT Jul 24, 2026
059e15b
fix comments
SpliiT Jul 27, 2026
4ad2188
mypy
SpliiT Jul 27, 2026
c10bfea
latest v9.6.1
SpliiT Jul 27, 2026
edb12d3
clean code and fix comments
SpliiT Jul 27, 2026
355eef5
Apply prepare changes
SpliiT Jul 27, 2026
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
4 changes: 2 additions & 2 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
opengeode-core==17.3.7
opengeode-core==17.4.4
opengeode-io==7.7.0
opengeode-inspector==6.11.6
opengeode-inspector-geosciences==1.1.0
opengeode-geosciences==9.6.0
opengeode-geosciences==9.6.1
opengeode-geosciencesio==5.9.0
geode-common==33.24.2
geode-viewables==3.4.0
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ markupsafe>=3
# flask
# jinja2
# werkzeug
opengeode-core==17.3.7
opengeode-core==17.4.4
# via
# -r requirements.in
# geode-common
Expand All @@ -41,7 +41,7 @@ opengeode-core==17.3.7
# opengeode-inspector
# opengeode-inspector-geosciences
# opengeode-io
opengeode-geosciences==9.6.0
opengeode-geosciences==9.6.1
# via
# -r requirements.in
# geode-viewables
Expand All @@ -66,4 +66,3 @@ werkzeug==3.1.8
# flask
# flask-cors

opengeodeweb-microservice==1.*,>=1.1.4
2 changes: 2 additions & 0 deletions src/opengeodeweb_back/geode_objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .geode_cross_section import GeodeCrossSection
from .geode_implicit_structural_model import GeodeImplicitStructuralModel
from .geode_implicit_cross_section import GeodeImplicitCrossSection
from .geode_horizon_stack3d import GeodeHorizonStack3D

geode_objects: dict[GeodeObjectType, type[GeodeObject]] = {
"VertexSet": GeodeVertexSet,
Expand Down Expand Up @@ -58,4 +59,5 @@
"CrossSection": GeodeCrossSection,
"ImplicitStructuralModel": GeodeImplicitStructuralModel,
"ImplicitCrossSection": GeodeImplicitCrossSection,
"HorizonStack3D": GeodeHorizonStack3D,
}
103 changes: 103 additions & 0 deletions src/opengeodeweb_back/geode_objects/geode_horizon_stack3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Standard library imports
from __future__ import annotations
from typing import Any

# Third party imports
import opengeode as og
import opengeode_geosciences as og_geosciences
from opengeodeweb_microservice.database.data_types import GeodeModelType

# Local application imports
from .geode_model import GeodeModel, ComponentRegistry


class GeodeHorizonStack3D(GeodeModel):
horizon_stack: og_geosciences.HorizonsStack3D

def __init__(
self, horizon_stack: og_geosciences.HorizonsStack3D | None = None
) -> None:
self.horizon_stack = (
horizon_stack
if horizon_stack is not None
else og_geosciences.HorizonsStack3D()
)
super().__init__(self.horizon_stack)

@classmethod
def geode_object_type(cls) -> GeodeModelType:
return "HorizonStack3D"

def native_extension(self) -> str:
return self.horizon_stack.native_extension()

@classmethod
def is_3D(cls) -> bool:
return True

@classmethod
def is_viewable(cls) -> bool:
return False

def builder(self) -> og_geosciences.HorizonsStackBuilder3D:
return og_geosciences.HorizonsStackBuilder3D(self.horizon_stack)
Comment thread
SpliiT marked this conversation as resolved.

@classmethod
def load(cls, filename: str) -> GeodeHorizonStack3D:
return GeodeHorizonStack3D(og_geosciences.load_horizons_stack3D(filename))

@classmethod
def additional_files(cls, filename: str) -> og.AdditionalFiles:
return og_geosciences.horizons_stack_additional_files3D(filename)

@classmethod
def is_loadable(cls, filename: str) -> og.Percentage:
return og_geosciences.is_horizons_stack_loadable3D(filename)

@classmethod
def input_extensions(cls) -> list[str]:
return og_geosciences.HorizonsStackInputFactory3D.list_creators()

@classmethod
def output_extensions(cls) -> list[str]:
return og_geosciences.HorizonsStackOutputFactory3D.list_creators()

@classmethod
def object_priority(cls, filename: str) -> int:
return og_geosciences.horizons_stack_object_priority3D(filename)

def is_saveable(self, filename: str) -> bool:
return og_geosciences.is_horizons_stack_saveable3D(self.horizon_stack, filename)

def save(self, filename: str) -> list[str]:
return og_geosciences.save_horizons_stack3D(self.horizon_stack, filename)

def save_viewable(self, filename_without_extension: str) -> str:
return ""

def save_light_viewable(self, filename_without_extension: str) -> str:
return ""

def mesh_components(self) -> ComponentRegistry:
return {}

def collection_components(self) -> ComponentRegistry:
return {}

def boundaries(self, id: og.uuid) -> list[og.ComponentID]:
Comment thread
SpliiT marked this conversation as resolved.
return []

def internals(self, id: og.uuid) -> list[og.ComponentID]:
return []

def items(self, id: og.uuid) -> list[og.ComponentID]:
return []

def component(self, id: og.uuid) -> og.Component3D:
raise NotImplementedError("HorizonStack3D has no mesh components")

def inspect(self) -> Any:
return None

def validate(self) -> Any:
return None
2 changes: 1 addition & 1 deletion src/opengeodeweb_back/routes/blueprint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def save_viewable_file() -> flask.Response:
)
params = schemas.SaveViewableFile.from_dict(json_data)
return flask.make_response(
utils_functions.generate_native_viewable_and_light_viewable_from_file(
utils_functions.generate_files_from_file(
geode_object_type=geode_object_type(params.geode_object_type),
input_file=params.filename,
),
Expand Down
12 changes: 3 additions & 9 deletions src/opengeodeweb_back/routes/create/blueprint_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ def point_set() -> flask.Response:
for point in params.points:
builder.create_point(opengeode.Point3D([point.x, point.y, point.z]))

result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
pointset
)
result = utils_functions.generate_files_from_object(pointset)
return flask.make_response(result, 200)


Expand All @@ -61,9 +59,7 @@ def edged_curve() -> flask.Response:
for edge in params.edges:
builder.create_edge_with_vertices(edge[0], edge[1])

result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
edged_curve_obj
)
result = utils_functions.generate_files_from_object(edged_curve_obj)
return flask.make_response(result, 200)


Expand All @@ -89,7 +85,5 @@ def polygonal_surface() -> flask.Response:

builder.compute_polygon_adjacencies()

result = utils_functions.generate_native_viewable_and_light_viewable_from_object(
polygonal_surface_obj
)
result = utils_functions.generate_files_from_object(polygonal_surface_obj)
return flask.make_response(result, 200)
93 changes: 55 additions & 38 deletions src/opengeodeweb_back/utils_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,20 +198,22 @@ def create_data_folder_from_id(data_id: str) -> str:


def model_components(
data_id: str, model: GeodeModel, viewable_file: str
data_id: str, model: GeodeModel, viewable_file: str | None
) -> dict[str, Any]:
vtm_file_path = geode_functions.data_file_path(data_id, viewable_file)
tree = ET.parse(vtm_file_path)
root = tree.find("vtkMultiBlockDataSet")
if root is None:
flask.abort(500, "Failed to read viewable file")
uuid_to_flat_index = {}
current_index = 0
assert root is not None
for elem in root.iter():
if "uuid" in elem.attrib and elem.tag == "DataSet":
uuid_to_flat_index[elem.attrib["uuid"]] = current_index
current_index += 1
uuid_to_flat_index: dict[str, int] = {}
if viewable_file:
vtm_file_path = geode_functions.data_file_path(data_id, viewable_file)
tree = ET.parse(vtm_file_path)
root = tree.find("vtkMultiBlockDataSet")
if root is None:
flask.abort(500, "Failed to read viewable file")
current_index = 0
assert root is not None
for elem in root.iter():
if "uuid" in elem.attrib and elem.tag == "DataSet":
uuid_to_flat_index[elem.attrib["uuid"]] = current_index
current_index += 1

model_mesh_components = model.mesh_components()
mesh_components = []
for mesh_component, ids in model_mesh_components.items():
Expand Down Expand Up @@ -270,52 +272,67 @@ def save_all_viewables_and_return_info(
data_path: str,
) -> dict[str, Any]:
with ThreadPoolExecutor() as executor:
native_files, viewable_path, light_path = executor.map(
lambda args: args[0](args[1]),
[
(
geode_object.save,
os.path.join(
data_path, "native." + geode_object.native_extension()
tasks: list[tuple[Callable[[str], Any], str]] = [
(
geode_object.save,
os.path.join(data_path, "native." + geode_object.native_extension()),
)
]
if geode_object.is_viewable():
tasks.extend(
[
(geode_object.save_viewable, os.path.join(data_path, "viewable")),
Comment thread
SpliiT marked this conversation as resolved.
(
geode_object.save_light_viewable,
os.path.join(data_path, "light_viewable"),
),
),
(geode_object.save_viewable, os.path.join(data_path, "viewable")),
(
geode_object.save_light_viewable,
os.path.join(data_path, "light_viewable"),
),
],
)
with open(light_path, "rb") as f:
binary_light_viewable = f.read()
]
)

results = list(executor.map(lambda args: args[0](args[1]), tasks))
native_files = results[0]
if geode_object.is_viewable():
viewable_path = results[1]
light_path = results[2]
with open(light_path, "rb") as f:
binary_light_viewable = f.read()
binary_light_viewable_str = binary_light_viewable.decode("utf-8")
data.viewable_file = os.path.basename(viewable_path)
data.light_viewable_file = os.path.basename(light_path)
else:
binary_light_viewable_str = None
data.viewable_file = None
data.light_viewable_file = None

data.native_file = os.path.basename(native_files[0])
data.viewable_file = os.path.basename(viewable_path)
data.light_viewable_file = os.path.basename(light_path)

assert data.native_file is not None
assert data.viewable_file is not None
Comment thread
SpliiT marked this conversation as resolved.
assert data.light_viewable_file is not None
if geode_object.is_viewable():
assert data.viewable_file is not None
assert data.light_viewable_file is not None
name = geode_object.identifier.name()
if not name:
flask.abort(400, "Geode object has no name defined.")

response: dict[str, Any] = {
"native_file": data.native_file,
"viewable_file": data.viewable_file,
"id": data.id,
"name": name,
"viewer_type": data.viewer_object,
"binary_light_viewable": binary_light_viewable.decode("utf-8"),
"is_viewable": geode_object.is_viewable(),
"geode_object_type": data.geode_object,
}
if geode_object.is_viewable():
response["viewable_file"] = data.viewable_file
response["binary_light_viewable"] = binary_light_viewable_str
if isinstance(geode_object, GeodeVertexSet):
response["nb_vertices"] = geode_object.vertex_set.nb_vertices()
if isinstance(geode_object, GeodeModel):
response |= model_components(data.id, geode_object, data.viewable_file)
return response


def generate_native_viewable_and_light_viewable_from_object(
def generate_files_from_object(
geode_object: GeodeObject,
) -> dict[str, Any]:
data = Data.create(
Expand All @@ -327,7 +344,7 @@ def generate_native_viewable_and_light_viewable_from_object(
return save_all_viewables_and_return_info(geode_object, data, data_path)


def generate_native_viewable_and_light_viewable_from_file(
def generate_files_from_file(
geode_object_type: GeodeObjectType, input_file: str
) -> dict[str, Any]:
generic_geode_object = geode_objects[geode_object_type]
Expand Down
Binary file added tests/data/test.og_hst3d
Binary file not shown.
16 changes: 6 additions & 10 deletions tests/test_utils_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,18 +162,14 @@ def test_save_all_viewables_commits_to_db(client: FlaskClient) -> None:
assert db_entry_before.native_file == result["native_file"]


def test_generate_native_viewable_and_light_viewable_from_object(
def test_generate_files_from_object(
client: FlaskClient,
) -> None:
app = client.application
with app.app_context():
geode_object = GeodeBRep.load(os.path.join(data_dir, "test.og_brep"))

result = (
utils_functions.generate_native_viewable_and_light_viewable_from_object(
geode_object
)
)
result = utils_functions.generate_files_from_object(geode_object)

assert isinstance(result, dict)
assert isinstance(result["native_file"], str)
Expand All @@ -197,12 +193,12 @@ def test_generate_native_viewable_and_light_viewable_from_object(
assert os.path.exists(os.path.join(data_path, data.light_viewable_file))


def test_generate_native_viewable_and_light_viewable_from_file(
def test_generate_files_from_file(
client: FlaskClient,
) -> None:
app = client.application
with app.app_context():
result = utils_functions.generate_native_viewable_and_light_viewable_from_file(
result = utils_functions.generate_files_from_file(
GeodeBRep.geode_object_type(), "test.og_brep"
)

Expand All @@ -218,12 +214,12 @@ def test_generate_native_viewable_and_light_viewable_from_file(
assert isinstance(result["binary_light_viewable"], str)


def test_generate_native_viewable_and_light_viewable_from_file_with_multi_dots(
def test_generate_files_from_file_with_multi_dots(
client: FlaskClient,
) -> None:
app = client.application
with app.app_context():
result = utils_functions.generate_native_viewable_and_light_viewable_from_file(
result = utils_functions.generate_files_from_file(
GeodeBRep.geode_object_type(), "cube.test.og_brep"
)
assert result["name"] == "cube.test"
Expand Down
Loading