blenderpython/suw_core/selection_manager.py

660 lines
23 KiB
Python
Raw Normal View History

2025-08-01 17:13:30 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SUW Core - Selection Manager Module
拆分自: suw_impl.py (Line 3937-4300, 5914-5926)
用途: Blender选择管理对象高亮状态维护
版本: 1.0.0
作者: SUWood Team
"""
from .geometry_utils import MAT_TYPE_OBVERSE, MAT_TYPE_NORMAL, MAT_TYPE_NATURE
from .memory_manager import memory_manager
from .data_manager import data_manager, get_data_manager
import logging
from typing import Dict, Any, Optional, List
# 设置日志
logger = logging.getLogger(__name__)
# 检查Blender可用性
try:
import bpy
BLENDER_AVAILABLE = True
except ImportError:
BLENDER_AVAILABLE = False
# ==================== 选择管理器类 ====================
class SelectionManager:
"""选择管理器 - 负责所有选择相关操作"""
def __init__(self):
"""
初始化选择管理器 - 完全独立不依赖suw_impl
"""
# 使用全局数据管理器 - 【修复】使用get_data_manager()函数
self.data_manager = get_data_manager() # 使用函数获取实例,确保初始化
# 选择状态
self.selected_faces = []
self.selected_parts = []
self.selected_hws = []
# 状态缓存
self._face_color_cache = {}
# 类级别选择状态 - 本地维护不再依赖suw_impl
self._selected_uid = None
self._selected_obj = None
self._selected_zone = None
self._selected_part = None
logger.info("✅ 选择管理器初始化完成")
# ==================== 选择清除方法 ====================
def sel_clear(self):
"""清除选择 - 优化版本,避免阻塞界面"""
try:
if BLENDER_AVAILABLE:
# 【修复】使用非阻塞的直接属性操作,而不是阻塞性操作符
try:
for obj in bpy.data.objects:
if hasattr(obj, 'select_set'):
obj.select_set(False) # 直接设置选择状态,不刷新视口
except:
# 如果直接操作失败,跳过而不是使用阻塞性操作符
pass
# 清除类级别选择状态 - 使用本地属性
self._selected_uid = None
self._selected_obj = None
self._selected_zone = None
self._selected_part = None
# 清除选择的面、零件和硬件
for face in self.selected_faces:
if face:
self._textured_face(face, False)
self.selected_faces.clear()
for part in self.selected_parts:
if part:
self.textured_part(part, False)
self.selected_parts.clear()
for hw in self.selected_hws:
if hw:
self._textured_hw(hw, False)
self.selected_hws.clear()
logger.debug("选择状态已清除")
except Exception as e:
logger.error(f"清除选择失败: {e}")
# ==================== 选择逻辑方法 ====================
def sel_local(self, obj):
"""本地选择对象"""
try:
if not obj:
logger.warning("选择对象为空")
return
uid = obj.get("sw_uid")
zid = obj.get("sw_zid")
typ = obj.get("sw_typ")
pid = obj.get("sw_pid", -1)
cp = obj.get("sw_cp", -1)
# 检查是否已选择
if typ == "zid":
if (self._selected_uid == uid and
self._selected_obj == zid):
return
elif typ == "cp":
if (self._selected_uid == uid and
(self._selected_obj == pid or
self._selected_obj == cp)):
return
else:
self.sel_clear()
return
# 准备选择参数
params = {}
params["uid"] = uid
params["zid"] = zid
# 根据模式选择
if typ == "cp" and self.data_manager.get_part_mode():
params["pid"] = pid
params["cp"] = cp
self._sel_part_local(params)
else:
params["pid"] = -1
params["cp"] = -1
self._sel_zone_local(params)
# 发送选择命令到客户端(如果需要)
# self._set_cmd("r01", params) # select_client
except Exception as e:
logger.error(f"本地选择失败: {e}")
def _sel_zone_local(self, data):
"""本地区域选择"""
try:
self.sel_clear()
uid = data.get("uid")
zid = data.get("zid")
zones = self.data_manager.get_zones({"uid": uid})
parts = self.data_manager.get_parts({"uid": uid})
hardwares = self.data_manager.get_hardwares({"uid": uid})
children = self._get_child_zones(zones, zid, True)
for child in children:
child_id = child.get("zid")
child_zone = zones.get(child_id)
leaf = child.get("leaf")
# 为区域的部件设置纹理
for v_root, part in parts.items():
if part and part.get("sw_zid") == child_id:
self.textured_part(part, True)
# 为区域的硬件设置纹理
for v_root, hw in hardwares.items():
if hw and hw.get("sw_zid") == child_id:
self._textured_hw(hw, True)
# 处理区域可见性
hide_none = self.data_manager.hide_none
if not leaf or hide_none:
if child_zone and hasattr(child_zone, 'hide_viewport'):
child_zone.hide_viewport = True
else:
if child_zone and hasattr(child_zone, 'hide_viewport'):
child_zone.hide_viewport = False
# 为区域面设置纹理
self._texture_zone_faces(child_zone, True)
if child_id == zid:
self._selected_uid = uid
self._selected_obj = zid
self._selected_zone = child_zone
except Exception as e:
logger.error(f"区域选择失败: {e}")
def _sel_part_local(self, data):
"""本地部件选择"""
try:
self.sel_clear()
parts = self.data_manager.get_parts(data)
hardwares = self.data_manager.get_hardwares(data)
uid = data.get("uid")
cp = data.get("cp")
if cp in parts:
part = parts[cp]
if part:
self.textured_part(part, True)
self._selected_part = part
elif cp in hardwares:
hw = hardwares[cp]
if hw:
self._textured_hw(hw, True)
self._selected_uid = uid
self._selected_obj = cp
except Exception as e:
logger.error(f"部件选择失败: {e}")
def _sel_part_parent(self, data):
"""选择部件父级"""
try:
# 这是一个从服务器来的命令,目前简化实现
uid = data.get("uid")
pid = data.get("pid")
parts = self.data_manager.get_parts({"uid": uid})
for v_root, part in parts.items():
if part and part.get("sw_pid") == pid:
self.textured_part(part, True)
self._selected_uid = uid
self._selected_obj = pid
except Exception as e:
logger.error(f"选择部件父级失败: {e}")
def _get_child_zones(self, zones, zip_id, myself=False):
"""获取子区域"""
try:
children = []
for zid, entity in zones.items():
if entity and entity.get("sw_zip") == zip_id:
grandchildren = self._get_child_zones(zones, zid, False)
child = {
"zid": zid,
"leaf": len(grandchildren) == 0
}
children.append(child)
children.extend(grandchildren)
if myself:
child = {
"zid": zip_id,
"leaf": len(children) == 0
}
children.append(child)
return children
except Exception as e:
logger.error(f"获取子区域失败: {e}")
return []
def _is_selected_part(self, part):
"""检查部件是否被选中"""
return part in self.selected_parts
# ==================== 纹理方法 ====================
def _textured_face(self, face, selected):
"""为面设置纹理"""
try:
if selected:
self.selected_faces.append(face)
# 获取材质管理器
from .material_manager import material_manager
if not material_manager:
return
color = "mat_select" if selected else "mat_normal"
texture = material_manager.get_texture(color)
if texture and hasattr(face, 'material'):
face.material = texture
# 设置背面材质
back_material = self.data_manager.get_back_material()
if back_material or (texture and texture.get("alpha", 1.0) < 1.0):
if hasattr(face, 'back_material'):
face.back_material = texture
except Exception as e:
logger.error(f"设置面纹理失败: {e}")
def textured_part(self, part, selected):
"""为部件设置纹理"""
try:
if not part:
return
if selected:
self.selected_parts.append(part)
# 获取材质管理器
from .material_manager import material_manager
if not material_manager:
return
# 根据材质类型确定颜色
mat_type = self.data_manager.get_mat_type()
if selected:
color = "mat_select"
elif mat_type == MAT_TYPE_NATURE:
# 根据部件类型确定自然材质
mn = part.get("sw_mn", 1)
if mn == 1:
color = "mat_obverse" # 门板
elif mn == 2:
color = "mat_reverse" # 柜体
elif mn == 3:
color = "mat_thin" # 背板
else:
color = "mat_normal"
else:
color = self._face_color(part, part) or "mat_normal"
# 应用材质
texture = material_manager.get_texture(color)
if texture:
self._apply_part_material(part, texture, selected)
# 处理子对象
if hasattr(part, 'children'):
for child in part.children:
if hasattr(child, 'type') and child.type == 'MESH':
self._apply_part_material(child, texture, selected)
except Exception as e:
logger.error(f"设置部件纹理失败: {e}")
def _textured_hw(self, hw, selected):
"""为硬件设置纹理"""
try:
if not hw:
return
if selected:
self.selected_hws.append(hw)
# 获取材质管理器
from .material_manager import material_manager
if not material_manager:
return
color = "mat_select" if selected else hw.get(
"sw_ckey", "mat_hardware")
texture = material_manager.get_texture(color)
if texture:
self._apply_hw_material(hw, texture)
except Exception as e:
logger.error(f"设置硬件纹理失败: {e}")
def _face_color(self, face, leaf):
"""获取面颜色"""
try:
# 检查是否有差异标记
if face and face.get("sw_differ", False):
return "mat_default"
# 根据材质类型确定颜色
mat_type = self.data_manager.get_mat_type()
if mat_type == MAT_TYPE_OBVERSE:
typ = face.get("sw_typ") if face else None
if typ == "o" or typ == "e1":
return "mat_obverse"
elif typ == "e2":
return "mat_thin"
elif typ == "r" or typ == "e0":
return "mat_reverse"
# 从属性获取颜色
color = face.get("sw_ckey") if face else None
if not color and leaf:
color = leaf.get("sw_ckey")
return color
except Exception as e:
logger.error(f"获取面颜色失败: {e}")
return "mat_default"
def _apply_part_material(self, obj, material, selected):
"""应用部件材质"""
try:
if not obj or not material:
return
if hasattr(obj, 'data') and obj.data and hasattr(obj.data, 'materials'):
if not obj.data.materials:
obj.data.materials.append(material)
else:
obj.data.materials[0] = material
# 设置可见性
edge_visible = selected or self.data_manager.get_mat_type() == MAT_TYPE_NATURE
if hasattr(obj, 'hide_viewport'):
obj.hide_viewport = not edge_visible
except Exception as e:
logger.error(f"应用部件材质失败: {e}")
def _apply_hw_material(self, obj, material):
"""应用硬件材质"""
try:
if not obj or not material:
return
if hasattr(obj, 'data') and obj.data and hasattr(obj.data, 'materials'):
if not obj.data.materials:
obj.data.materials.append(material)
else:
obj.data.materials[0] = material
except Exception as e:
logger.error(f"应用硬件材质失败: {e}")
def _texture_zone_faces(self, zone, selected):
"""为区域面设置纹理"""
try:
if not zone or not hasattr(zone, 'data') or not zone.data:
return
# 遍历区域的所有面
if hasattr(zone.data, 'polygons'):
for face in zone.data.polygons:
self._textured_face(face, selected)
except Exception as e:
logger.error(f"设置区域面纹理失败: {e}")
def view_front_and_zoom_extents(self):
"""切换到前视图并缩放到全部刷新视图适配无UI/后台环境)"""
try:
if not BLENDER_AVAILABLE:
logger.warning("Blender 不可用,无法切换视图")
return True # 不报错
found_view3d = False
# for window in getattr(bpy.context, "window_manager", []).windows if hasattr(bpy.context, "window_manager") else []:
# for area in window.screen.areas:
# if area.type == 'VIEW_3D':
# found_view3d = True
# region = next(
# (reg for reg in area.regions if reg.type == 'WINDOW'), None)
# space = next(
# (sp for sp in area.spaces if sp.type == 'VIEW_3D'), None)
# if region and space:
# with bpy.context.temp_override(window=window, area=area, region=region, space_data=space):
# bpy.ops.view3d.view_axis(type='FRONT')
# bpy.ops.view3d.view_all(center=False)
# area.tag_redraw()
# logger.info("✅ 已切换到前视图并缩放到全部")
# return True
if not found_view3d:
logger.info("无3D视图环境跳过视图操作后台/无UI模式")
return True # 不报错直接返回True
logger.warning("未找到3D视图区域无法切换视图")
return True
except Exception as e:
logger.info("无3D视图环境跳过视图操作后台/无UI模式")
return True # 不报错直接返回True
def _is_leaf_zone(self, zip_id_to_check, all_zones_for_uid):
"""检查一个区域是否是叶子节点 (没有子区域)"""
try:
for zid, zone_obj in all_zones_for_uid.items():
if zone_obj and hasattr(zone_obj, 'get') and zone_obj.get("sw_zip") == zip_id_to_check:
return False # Found a child, so it's not a leaf
return True
except Exception:
return True # 发生错误时默认为叶子节点
# ==================== 命令处理方法 ====================
def c15(self, data: Dict[str, Any]):
"""sel_unit - 清除选择并根据层级设置区域可见性"""
try:
if not BLENDER_AVAILABLE:
return False
self.sel_clear()
zones = self.data_manager.get_zones(data)
hide_none = self.data_manager.hide_none
for zid, zone in zones.items():
if zone and hasattr(zone, 'hide_viewport'):
is_leaf = self._is_leaf_zone(zid, zones)
if is_leaf:
zone.hide_viewport = hide_none
else:
zone.hide_viewport = True
logger.info("c15 (sel_unit) 执行完成")
return True
except Exception as e:
logger.error(f"c15 (sel_unit) 执行失败: {e}")
return False
def c16(self, data: Dict[str, Any]):
"""sel_zone - 选择区域命令"""
try:
return self._sel_zone_local(data)
except Exception as e:
logger.error(f"c16命令执行失败: {e}")
return None
def c17(self, data: Dict[str, Any]):
"""sel_elem - 选择元素命令"""
try:
# 根据模式选择不同的处理方式
if self.data_manager.get_part_mode():
return self._sel_part_parent(data)
else:
return self._sel_zone_local(data)
except Exception as e:
logger.error(f"c17命令执行失败: {e}")
return None
def set_config(self, data: dict):
"""设置全局/单元/显示等配置兼容Ruby set_config"""
try:
# 1. 服务器路径等全局参数
if "server_path" in data:
setattr(self.data_manager, "server_path", data["server_path"])
if "order_id" in data:
setattr(self.data_manager, "order_id", data["order_id"])
if "order_code" in data:
setattr(self.data_manager, "order_code", data["order_code"])
if "back_material" in data:
self.data_manager.back_material = data["back_material"]
if "part_mode" in data:
self.data_manager.part_mode = data["part_mode"]
if "hide_none" in data:
self.data_manager.hide_none = data["hide_none"]
# 2. 单元/图纸相关
if "unit_drawing" in data:
setattr(self.data_manager, "unit_drawing",
data["unit_drawing"])
if "drawing_name" in data:
setattr(self.data_manager, "drawing_name",
data["drawing_name"])
# 3. 区域角点
if "zone_corner" in data:
uid = data.get("uid")
zid = data.get("zid")
if uid and zid:
zones = self.data_manager.get_zones({"uid": uid})
zone = zones.get(zid)
if zone:
zone["sw_cor"] = data["zone_corner"]
logger.info("✅ set_config 配置完成")
return True
except Exception as e:
logger.error(f"set_config 配置失败: {e}")
return False
# ==================== 类方法(保持兼容性)====================
@classmethod
def selected_uid(cls):
"""获取选中的UID - 兼容性方法"""
# 从全局实例获取
global selection_manager
if selection_manager:
return selection_manager._selected_uid
return None
@classmethod
def selected_zone(cls):
"""获取选中的区域 - 兼容性方法"""
global selection_manager
if selection_manager:
return selection_manager._selected_zone
return None
@classmethod
def selected_part(cls):
"""获取选中的部件 - 兼容性方法"""
global selection_manager
if selection_manager:
return selection_manager._selected_part
return None
@classmethod
def selected_obj(cls):
"""获取选中的对象 - 兼容性方法"""
global selection_manager
if selection_manager:
return selection_manager._selected_obj
return None
# ==================== 管理方法 ====================
def cleanup(self):
"""清理选择管理器"""
try:
self.sel_clear()
self._face_color_cache.clear()
logger.info("✅ 选择管理器清理完成")
except Exception as e:
logger.error(f"清理选择管理器失败: {e}")
def get_selection_stats(self) -> Dict[str, Any]:
"""获取选择统计信息"""
try:
return {
"selected_faces_count": len(self.selected_faces),
"selected_parts_count": len(self.selected_parts),
"selected_hws_count": len(self.selected_hws),
"face_color_cache_size": len(self._face_color_cache),
"selected_uid": self._selected_uid,
"selected_obj": self._selected_obj,
}
except Exception as e:
logger.error(f"获取选择统计失败: {e}")
return {"error": str(e)}
# ==================== 全局选择管理器实例 ====================
# 全局实例
selection_manager = None
def init_selection_manager():
"""初始化全局选择管理器实例 - 不再需要suw_impl参数"""
global selection_manager
selection_manager = SelectionManager()
return selection_manager
def get_selection_manager():
"""获取全局选择管理器实例"""
global selection_manager
if selection_manager is None:
selection_manager = init_selection_manager()
return selection_manager