blenderpython/suw_core/selection_manager.py

660 lines
23 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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