Skip to content

Commit 2cb155b

Browse files
Stage 4: Clean entity merging on upload with DraftContext support
Add draft_entity_info parameter to set_up_params_for_uploading() to use DraftContext's entity_info as source of truth during upload. Implement _merge_draft_entities_from_params() to collect draft entities from params and merge into entity_info without duplicates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 0891a02 commit 2cb155b

File tree

4 files changed

+372
-83
lines changed

4 files changed

+372
-83
lines changed

flow360/component/project.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
validate_params_with_context,
3333
)
3434
from flow360.component.resource_base import Flow360Resource
35-
from flow360.component.simulation.draft_context.context import DraftContext
35+
from flow360.component.simulation.draft_context.context import (
36+
DraftContext,
37+
get_active_draft,
38+
)
3639
from flow360.component.simulation.entity_info import GeometryEntityInfo
3740
from flow360.component.simulation.folder import Folder
3841
from flow360.component.simulation.simulation_params import SimulationParams
@@ -1480,12 +1483,17 @@ def _run(
14801483
if use_geometry_AI is True and use_beta_mesher is False:
14811484
raise Flow360ValueError("Enabling Geometry AI requires also enabling beta mesher.")
14821485

1486+
# Check if there's an active DraftContext and get its entity_info
1487+
active_draft = get_active_draft()
1488+
draft_entity_info = active_draft._entity_info if active_draft is not None else None
1489+
14831490
params = set_up_params_for_uploading(
14841491
params=params,
14851492
root_asset=self._root_asset,
14861493
length_unit=self.length_unit,
14871494
use_beta_mesher=use_beta_mesher,
14881495
use_geometry_AI=use_geometry_AI,
1496+
draft_entity_info=draft_entity_info,
14891497
)
14901498

14911499
params, errors = validate_params_with_context(

flow360/component/project_utils.py

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ def _set_up_params_non_persistent_entity_info(entity_info, params: SimulationPar
255255
"""
256256
Setting up non-persistent entities (AKA draft entities) in params.
257257
Add the ones used to the entity info.
258+
259+
LEGACY: This function is used for the legacy workflow (without DraftContext).
260+
For DraftContext workflow, use _merge_draft_entities_from_params() instead.
258261
"""
259262

260263
entity_registry = params.used_entity_registry
@@ -270,6 +273,49 @@ def _set_up_params_non_persistent_entity_info(entity_info, params: SimulationPar
270273
return entity_info
271274

272275

276+
def _merge_draft_entities_from_params(
277+
entity_info: EntityInfoModel,
278+
params: SimulationParams,
279+
) -> EntityInfoModel:
280+
"""
281+
Collect draft entities from params.used_entity_registry and merge into entity_info.
282+
283+
This function implements the merging logic for the DraftContext workflow:
284+
- If a draft entity already exists in entity_info (by ID), use entity_info version (source of truth)
285+
- If a draft entity is new (not in entity_info), add it from params
286+
287+
This ensures that:
288+
1. Entities managed by DraftContext retain their modifications
289+
2. New entities created by the user during simulation setup are captured
290+
291+
Parameters:
292+
entity_info: The entity_info to merge into (typically from DraftContext)
293+
params: The SimulationParams containing used_entity_registry
294+
295+
Returns:
296+
EntityInfoModel: The updated entity_info with merged draft entities
297+
"""
298+
used_registry = params.used_entity_registry
299+
300+
# Get all draft entity types from the DraftEntityTypes annotation
301+
draft_type_union = get_args(DraftEntityTypes)[0]
302+
draft_type_list = get_args(draft_type_union)
303+
304+
# Build a set of IDs already in entity_info for quick lookup (Draft entities have unique UUIDs)
305+
existing_ids = {e.private_attribute_id for e in entity_info.draft_entities}
306+
307+
for draft_type in draft_type_list:
308+
draft_entities_used = list(used_registry.view(draft_type))
309+
for draft_entity in draft_entities_used:
310+
# Only add if not already in entity_info (by ID)
311+
# If already present, entity_info version is source of truth - keep it as is
312+
if draft_entity.private_attribute_id not in existing_ids:
313+
entity_info.draft_entities.append(draft_entity)
314+
existing_ids.add(draft_entity.private_attribute_id)
315+
316+
return entity_info
317+
318+
273319
def _update_entity_grouping_tags(entity_info, params: SimulationParams) -> EntityInfoModel:
274320
"""
275321
Update the entity grouping tags in params to resolve possible conflicts
@@ -411,9 +457,20 @@ def set_up_params_for_uploading(
411457
params: SimulationParams,
412458
use_beta_mesher: bool,
413459
use_geometry_AI: bool, # pylint: disable=invalid-name
460+
draft_entity_info: Optional[EntityInfoModel] = None,
414461
) -> SimulationParams:
415462
"""
416463
Set up params before submitting the draft.
464+
465+
Parameters:
466+
root_asset: The root asset (Geometry, SurfaceMesh, or VolumeMesh).
467+
length_unit: The project length unit.
468+
params: The SimulationParams to set up.
469+
use_beta_mesher: Whether to use the beta mesher.
470+
use_geometry_AI: Whether to use Geometry AI.
471+
draft_entity_info: Optional entity_info from DraftContext. When provided,
472+
this is used as the source of truth for entities instead of root_asset.entity_info (legacy behavior).
473+
This enables proper entity isolation for the DraftContext workflow.
417474
"""
418475

419476
with model_attribute_unlock(params.private_attribute_asset_cache, "project_length_unit"):
@@ -429,18 +486,28 @@ def set_up_params_for_uploading(
429486
use_geometry_AI if use_geometry_AI else False
430487
)
431488

432-
# User may have made modifications to the entities which is recorded in asset's entity registry
433-
# We need to reflect these changes.
434-
root_asset.entity_info.update_persistent_entities(
435-
asset_entity_registry=root_asset.internal_registry
436-
)
489+
if draft_entity_info is not None:
490+
# New DraftContext workflow: use draft's entity_info as source of truth
491+
# Merge draft entities from params.used_entity_registry into draft_entity_info
492+
entity_info = _merge_draft_entities_from_params(draft_entity_info, params)
493+
494+
# Update entity grouping tags if needed
495+
# (back compatibility, since the grouping should already have been captured in the draft_entity_info)
496+
entity_info = _update_entity_grouping_tags(entity_info, params)
497+
else:
498+
# Legacy workflow (without DraftContext): use root_asset.entity_info
499+
# User may have made modifications to the entities which is recorded in asset's entity registry
500+
# We need to reflect these changes.
501+
root_asset.entity_info.update_persistent_entities(
502+
asset_entity_registry=root_asset.internal_registry
503+
)
437504

438-
# Check if there are any new draft entities that have been added in the params by the user
439-
entity_info = _set_up_params_non_persistent_entity_info(root_asset.entity_info, params)
505+
# Check if there are any new draft entities that have been added in the params by the user
506+
entity_info = _set_up_params_non_persistent_entity_info(root_asset.entity_info, params)
440507

441-
# If the customer just load the param without re-specify the same set of entity grouping tags,
442-
# we need to update the entity grouping tags to the ones in the SimulationParams.
443-
entity_info = _update_entity_grouping_tags(entity_info, params)
508+
# If the customer just load the param without re-specify the same set of entity grouping tags,
509+
# we need to update the entity grouping tags to the ones in the SimulationParams.
510+
entity_info = _update_entity_grouping_tags(entity_info, params)
444511

445512
with model_attribute_unlock(params.private_attribute_asset_cache, "project_entity_info"):
446513
params.private_attribute_asset_cache.project_entity_info = entity_info

flow360/component/simulation/draft_context/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def __exit__(self, exc_type, exc, exc_tb) -> None:
102102
# endregion ------------------------------------------------------------------------------------
103103

104104
# region -----------------------------Public properties Below-------------------------------------
105+
105106
# Persistent entities
106107
@property
107108
def body_groups(self) -> EntityRegistryView:

0 commit comments

Comments
 (0)