@@ -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+
273319def _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
0 commit comments