@ -4,10 +4,12 @@ from pathlib import Path
import FreeCAD as App
import FreeCADGui as Gui
import ImportGui
import DevicePreview
ROOT_GROUP_NAME = " QETExchangeDevices "
ROOT_GROUP_LABEL = " QET Exchange Devices "
CABINET_MODEL_GROUP_NAME = " QETCabinetModel "
DEVICE_GROUP_PREFIX = " QETDevice_ "
@ -61,29 +63,130 @@ def _new_objects_since(doc, before_names):
return [ obj for obj in doc . Objects if obj . Name not in before_names ]
def _top_level_imported_objects ( imported_objects ) :
imported_by_name = { obj . Name : obj for obj in imported_objects }
child_names = set ( )
for obj in imported_objects :
for parent in list ( getattr ( obj , " InList " , [ ] ) or [ ] ) :
if getattr ( parent , " Name " , None ) in imported_by_name :
child_names . add ( obj . Name )
for child in list ( getattr ( obj , " Group " , [ ] ) or [ ] ) :
if getattr ( child , " Name " , None ) in imported_by_name :
child_names . add ( child . Name )
return [ obj for obj in imported_objects if obj . Name not in child_names ]
def _ensure_string_property ( obj , prop_name , group_name , description , value ) :
if prop_name not in getattr ( obj , " PropertiesList " , [ ] ) :
obj . addProperty ( " App::PropertyString " , prop_name , group_name , description )
setattr ( obj , prop_name , value or " " )
def _ensure_bool_property ( obj , prop_name , group_name , description , value ) :
if prop_name not in getattr ( obj , " PropertiesList " , [ ] ) :
obj . addProperty ( " App::PropertyBool " , prop_name , group_name , description )
setattr ( obj , prop_name , bool ( value ) )
def _ensure_document ( scene_path ) :
if App . ActiveDocument :
return App . ActiveDocument
preferred_name = _safe_token ( Path ( scene_path ) . stem if scene_path else " QETScene " ) [ : 48 ] or " QETScene "
existing_doc = DevicePreview . find_main_exchange_document ( preferred_name )
if existing_doc is not None :
return existing_doc
return App . newDocument ( preferred_name )
doc_name = Path ( scene_path ) . stem if scene_path else " QETScene "
doc_name = _safe_token ( doc_name ) [ : 48 ] or " QETScene "
return App . newDocument ( doc_name )
def _cabinet_label_text ( cabinet ) :
if not isinstance ( cabinet , dict ) :
return " QET Cabinet "
label = ( cabinet . get ( " display_text " ) or " " ) . strip ( )
if label :
return label
def _ensure_root_group ( doc ) :
label = ( cabinet . get ( " label " ) or " " ) . strip ( )
if label :
return label
label = ( cabinet . get ( " name " ) or " " ) . strip ( )
if label :
return label
return " QET Cabinet "
def _ensure_root_group ( doc , cabinet = None ) :
root = doc . getObject ( ROOT_GROUP_NAME )
if root is None :
root = doc . addObject ( " App::DocumentObjectGroup " , ROOT_GROUP_NAME )
root . Label = ROOT_GROUP_LABEL
if isinstance ( cabinet , dict ) :
root . Label = _cabinet_label_text ( cabinet )
else :
root . Label = ROOT_GROUP_LABEL
_ensure_string_property (
root ,
" QetCabinetLabel " ,
" QET Exchange " ,
" Cabinet label from QET exchange " ,
cabinet . get ( " label " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetName " ,
" QET Exchange " ,
" Cabinet name from QET exchange " ,
cabinet . get ( " name " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetDisplayText " ,
" QET Exchange " ,
" Cabinet display text from QET exchange " ,
cabinet . get ( " display_text " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetFileSet " ,
" QET Exchange " ,
" Associated fileset from QET exchange " ,
cabinet . get ( " associated_fileset " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetRelativePath " ,
" QET Exchange " ,
" Relative 3D cabinet path from QET exchange " ,
cabinet . get ( " three_d_relative_path " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetResolvedScenePath " ,
" QET Exchange " ,
" Resolved local cabinet scene path from QET exchange " ,
cabinet . get ( " resolved_scene_path " , " " ) if isinstance ( cabinet , dict ) else " " ,
)
_ensure_string_property (
root ,
" QetCabinetLocationId " ,
" QET Exchange " ,
" Cabinet location id from QET exchange " ,
str ( cabinet . get ( " location_id " ) or " " ) if isinstance ( cabinet , dict ) else " " ,
)
return root
def _ensure_cabinet_model_group ( doc , root_group ) :
group = doc . getObject ( CABINET_MODEL_GROUP_NAME )
if group is None :
group = doc . addObject ( " App::DocumentObjectGroup " , CABINET_MODEL_GROUP_NAME )
group . Label = " 3D机柜 "
if group not in getattr ( root_group , " Group " , [ ] ) :
root_group . addObject ( group )
return group
def _find_device_group ( doc , element_uuid ) :
target_uuid = ( element_uuid or " " ) . strip ( )
if not target_uuid :
@ -111,13 +214,19 @@ def _device_label_text(display_tag, instance_id, element_uuid):
return " QET Device "
def _ensure_device_group ( doc , root_group , element_uuid , instance_id , model_path , display_tag ) :
def _ensure_device_group ( doc , root_group , element_uuid , instance_id , model_path , display_tag , layout_index ) :
created_now = False
device_group = _find_device_group ( doc , element_uuid )
if device_group is not None and getattr ( device_group , " TypeId " , " " ) != " App::Part " :
_remove_object_tree ( doc , device_group )
device_group = None
if device_group is None :
device_group = doc . addObject (
" App::DocumentObjectGroup " ,
" App:: Part " ,
DEVICE_GROUP_PREFIX + _safe_token ( element_uuid ) ,
)
created_now = True
if device_group not in getattr ( root_group , " Group " , [ ] ) :
root_group . addObject ( device_group )
@ -151,7 +260,16 @@ def _ensure_device_group(doc, root_group, element_uuid, instance_id, model_path,
" 2D display tag from QET exchange " ,
display_tag ,
)
return device_group
_ensure_bool_property (
device_group ,
" QetAutoPlaced " ,
" QET Exchange " ,
" Whether the device has been placed by the QET auto layout. " ,
created_now ,
)
if created_now :
device_group . Placement = App . Placement ( )
return device_group , created_now
def _remove_object_tree ( doc , obj ) :
@ -184,20 +302,26 @@ def _supported_for_import(model_path):
}
def _import_model_into_group ( doc , device_group , model_path ):
def _import_model_into_group ( doc , device_group , model_path , merge = False , use_link_group = True ):
before_names = _existing_object_names ( doc )
try :
ImportGui . insert ( name = model_path , docName = doc . Name , merge = False , useLinkGroup = True )
ImportGui . insert (
name = model_path ,
docName = doc . Name ,
merge = bool ( merge ) ,
useLinkGroup = bool ( use_link_group ) ,
)
except Exception :
for obj in _new_objects_since ( doc , before_names ) :
_remove_object_tree ( doc , obj )
raise
imported_objects = _new_objects_since ( doc , before_names )
for obj in imported_objects :
top_level_objects = _top_level_imported_objects ( imported_objects )
for obj in top_level_objects :
if obj not in getattr ( device_group , " Group " , [ ] ) :
device_group . addObject ( obj )
return imported _objects
return top_level _objects
def _model_index ( payload ) :
@ -209,10 +333,71 @@ def _model_index(payload):
return index
def _import_cabinet_model ( doc , root_group , cabinet , report ) :
if not isinstance ( cabinet , dict ) :
return
resolved_scene_path = _native_path ( cabinet . get ( " resolved_scene_path " , " " ) )
_append_debug_log (
" DeviceImport cabinet resolved_scene_path= {0} " . format ( resolved_scene_path )
)
if not resolved_scene_path :
report [ " cabinet_skipped_missing_model " ] + = 1
return
if not os . path . isfile ( resolved_scene_path ) :
report [ " cabinet_skipped_missing_file " ] + = 1
report [ " warnings " ] . append (
" 机柜 3D 文件不存在: {0} " . format ( resolved_scene_path )
)
return
if not _supported_for_import ( resolved_scene_path ) :
report [ " cabinet_skipped_unsupported_format " ] + = 1
report [ " warnings " ] . append (
" 机柜 3D 文件格式暂不支持: {0} " . format ( resolved_scene_path )
)
return
cabinet_group = _ensure_cabinet_model_group ( doc , root_group )
_clear_group_contents ( doc , cabinet_group )
_ensure_string_property (
cabinet_group ,
" QetCabinetResolvedScenePath " ,
" QET Exchange " ,
" Resolved local cabinet scene path from QET exchange " ,
resolved_scene_path ,
)
try :
_append_debug_log (
" DeviceImport importing cabinet model: {0} " . format (
resolved_scene_path
)
)
_import_model_into_group (
doc ,
cabinet_group ,
resolved_scene_path ,
merge = False ,
use_link_group = True ,
)
report [ " cabinet_imported " ] + = 1
_append_debug_log ( " DeviceImport cabinet import succeeded " )
except Exception as exc :
report [ " cabinet_skipped_import_error " ] + = 1
report [ " warnings " ] . append (
" 机柜 3D 导入失败: {0} " . format ( exc )
)
_append_debug_log (
" DeviceImport cabinet import failed: {0} " . format ( exc )
)
def import_devices_from_payload ( payload , scene_path = " " ) :
_append_debug_log ( " DeviceImport.import_devices_from_payload entered " )
doc = _ensure_document ( scene_path )
root_group = _ensure_root_group ( doc )
cabinet = payload . get ( " cabinet " )
root_group = _ensure_root_group ( doc , cabinet )
models_by_element = _model_index ( payload )
report = {
@ -226,10 +411,17 @@ def import_devices_from_payload(payload, scene_path=""):
" skipped_missing_file " : 0 ,
" skipped_unsupported_format " : 0 ,
" skipped_import_error " : 0 ,
" cabinet_imported " : 0 ,
" cabinet_skipped_missing_model " : 0 ,
" cabinet_skipped_missing_file " : 0 ,
" cabinet_skipped_unsupported_format " : 0 ,
" cabinet_skipped_import_error " : 0 ,
" warnings " : [ ] ,
}
for device in payload . get ( " devices " , [ ] ) :
_import_cabinet_model ( doc , root_group , cabinet , report )
for index , device in enumerate ( payload . get ( " devices " , [ ] ) ) :
report [ " total_devices " ] + = 1
element_uuid = device . get ( " element_uuid " , " " ) . strip ( )
@ -265,8 +457,14 @@ def import_devices_from_payload(payload, scene_path=""):
continue
existing_group = _find_device_group ( doc , element_uuid )
device_group = _ensure_device_group (
doc , root_group , element_uuid , instance_id , resolved_model_path , display_tag
device_group , created_now = _ensure_device_group (
doc ,
root_group ,
element_uuid ,
instance_id ,
resolved_model_path ,
display_tag ,
index ,
)
_clear_group_contents ( doc , device_group )
@ -292,7 +490,7 @@ def import_devices_from_payload(payload, scene_path=""):
)
continue
if existing_group is None :
if created_now or existing_group is None :
report [ " imported_devices " ] + = 1
else :
report [ " updated_devices " ] + = 1
@ -307,7 +505,8 @@ def import_devices_from_payload(payload, scene_path=""):
pass
_append_debug_log (
" DeviceImport finished: imported= {0} , updated= {1} , skipped_missing_model= {2} , skipped_missing_file= {3} , skipped_import_error= {4} " . format (
" DeviceImport finished: cabinet_imported= {0} , imported= {1} , updated= {2} , skipped_missing_model= {3} , skipped_missing_file= {4} , skipped_import_error= {5} " . format (
report [ " cabinet_imported " ] ,
report [ " imported_devices " ] ,
report [ " updated_devices " ] ,
report [ " skipped_missing_model " ] ,