From 0732a429763c56fdb38729feca96736ea1e8d275 Mon Sep 17 00:00:00 2001 From: Pei Xueke Date: Tue, 1 Jul 2025 14:19:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90Ruby=E5=88=B0Python=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20-=204/10=E6=A8=A1=E5=9D=97=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=EF=BC=8C6/10=E6=A8=A1=E5=9D=97=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=AD=98=E6=A0=B9=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已完成翻译: ✅ suw_load.py - 模块加载器 (SUWLoad.rb) ✅ suw_constants.py - 常量定义 (SUWConstants.rb, 306行) ✅ suw_client.py - TCP客户端 (SUWClient.rb, 118行) ✅ suw_observer.py - 事件观察者 (SUWObserver.rb, 87行) 存根版本: ⏳ suw_impl.py - 核心实现 (SUWImpl.rb, 2019行) [最重要] ⏳ suw_menu.py - 菜单系统 (SUWMenu.rb, 71行) ⏳ suw_unit_point_tool.py - 点工具 (SUWUnitPointTool.rb, 129行) ⏳ suw_unit_face_tool.py - 面工具 (SUWUnitFaceTool.rb, 146行) ⏳ suw_unit_cont_tool.py - 轮廓工具 (SUWUnitContTool.rb, 137行) ⏳ suw_zone_div1_tool.py - 区域分割工具 (SUWZoneDiv1Tool.rb, 107行) 新增: 📦 __init__.py - Python包初始化 📚 README.md - 完整文档和使用指南 总进度: 40% (4/10模块完成) 下一步: 翻译SUWImpl.rb核心实现 (2019行) --- blenderpython/README.md | 190 +++++++ blenderpython/__init__.py | 105 ++++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 3544 bytes .../__pycache__/suw_client.cpython-313.pyc | Bin 0 -> 13124 bytes .../__pycache__/suw_constants.cpython-313.pyc | Bin 0 -> 15661 bytes .../__pycache__/suw_impl.cpython-313.pyc | Bin 0 -> 3583 bytes .../__pycache__/suw_load.cpython-313.pyc | Bin 0 -> 3246 bytes .../__pycache__/suw_menu.cpython-313.pyc | Bin 0 -> 1321 bytes .../__pycache__/suw_observer.cpython-313.pyc | Bin 0 -> 12982 bytes .../suw_unit_cont_tool.cpython-313.pyc | Bin 0 -> 831 bytes .../suw_unit_face_tool.cpython-313.pyc | Bin 0 -> 1066 bytes .../suw_unit_point_tool.cpython-313.pyc | Bin 0 -> 2210 bytes .../suw_zone_div1_tool.cpython-313.pyc | Bin 0 -> 848 bytes blenderpython/suw_client.py | 322 ++++++++++++ blenderpython/suw_constants.py | 471 ++++++++++++++++++ blenderpython/suw_impl.py | 82 +++ blenderpython/suw_load.py | 95 ++++ blenderpython/suw_menu.py | 26 + blenderpython/suw_observer.py | 298 +++++++++++ blenderpython/suw_unit_cont_tool.py | 15 + blenderpython/suw_unit_face_tool.py | 18 + blenderpython/suw_unit_point_tool.py | 35 ++ blenderpython/suw_zone_div1_tool.py | 15 + 23 files changed, 1672 insertions(+) create mode 100644 blenderpython/README.md create mode 100644 blenderpython/__init__.py create mode 100644 blenderpython/__pycache__/__init__.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_client.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_constants.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_impl.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_load.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_menu.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_observer.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_unit_cont_tool.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_unit_face_tool.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_unit_point_tool.cpython-313.pyc create mode 100644 blenderpython/__pycache__/suw_zone_div1_tool.cpython-313.pyc create mode 100644 blenderpython/suw_client.py create mode 100644 blenderpython/suw_constants.py create mode 100644 blenderpython/suw_impl.py create mode 100644 blenderpython/suw_load.py create mode 100644 blenderpython/suw_menu.py create mode 100644 blenderpython/suw_observer.py create mode 100644 blenderpython/suw_unit_cont_tool.py create mode 100644 blenderpython/suw_unit_face_tool.py create mode 100644 blenderpython/suw_unit_point_tool.py create mode 100644 blenderpython/suw_zone_div1_tool.py diff --git a/blenderpython/README.md b/blenderpython/README.md new file mode 100644 index 0000000..c52ab04 --- /dev/null +++ b/blenderpython/README.md @@ -0,0 +1,190 @@ +# BlenderPython - SUWood Ruby到Python翻译项目 + +## 📋 项目概述 + +这是一个将SketchUp的SUWood Ruby插件翻译为Python版本的项目,目标是在Blender环境中运行。 + +## 📁 文件结构 + +``` +blenderpython/ +├── __init__.py # 包初始化文件 +├── README.md # 本说明文档 +├── suw_load.py # ✅ 模块加载器 (已完成) +├── suw_constants.py # ✅ 常量定义 (已完成) +├── suw_client.py # ✅ TCP客户端 (已完成) +├── suw_observer.py # ✅ 事件观察者 (已完成) +├── suw_impl.py # ⏳ 核心实现 (存根版本) +├── suw_menu.py # ⏳ 菜单系统 (存根版本) +├── suw_unit_point_tool.py # ⏳ 点工具 (存根版本) +├── suw_unit_face_tool.py # ⏳ 面工具 (存根版本) +├── suw_unit_cont_tool.py # ⏳ 轮廓工具 (存根版本) +└── suw_zone_div1_tool.py # ⏳ 区域分割工具 (存根版本) +``` + +## ✅ 翻译进度 + +### 已完成的模块 (4/10) + +1. **suw_load.py** - 模块加载器 + - 原文件: `SUWLoad.rb` (13行) + - 状态: ✅ 完全翻译 + - 功能: 加载所有SUWood模块 + +2. **suw_constants.py** - 常量定义 + - 原文件: `SUWConstants.rb` (306行) + - 状态: ✅ 完全翻译 + - 功能: 定义所有常量、路径管理、核心功能函数 + +3. **suw_client.py** - TCP客户端 + - 原文件: `SUWClient.rb` (118行) + - 状态: ✅ 完全翻译 + - 功能: 网络通信、命令处理、消息队列 + +4. **suw_observer.py** - 事件观察者 + - 原文件: `SUWObserver.rb` (87行) + - 状态: ✅ 完全翻译 + - 功能: 监听Blender事件、工具变化、选择变化 + +### 待翻译的模块 (6/10) + +5. **suw_impl.py** - 核心实现 ⏳ + - 原文件: `SUWImpl.rb` (2019行) + - 状态: 存根版本 + - 优先级: **🔥 高** + - 说明: 这是最重要的文件,包含主要业务逻辑 + +6. **suw_menu.py** - 菜单系统 ⏳ + - 原文件: `SUWMenu.rb` (71行) + - 状态: 存根版本 + - 优先级: 中 + +7. **suw_unit_point_tool.py** - 点工具 ⏳ + - 原文件: `SUWUnitPointTool.rb` (129行) + - 状态: 存根版本 + - 优先级: 中 + +8. **suw_unit_face_tool.py** - 面工具 ⏳ + - 原文件: `SUWUnitFaceTool.rb` (146行) + - 状态: 存根版本 + - 优先级: 中 + +9. **suw_unit_cont_tool.py** - 轮廓工具 ⏳ + - 原文件: `SUWUnitContTool.rb` (137行) + - 状态: 存根版本 + - 优先级: 中 + +10. **suw_zone_div1_tool.py** - 区域分割工具 ⏳ + - 原文件: `SUWZoneDiv1Tool.rb` (107行) + - 状态: 存根版本 + - 优先级: 中 + +## 🚀 使用方法 + +### 1. 导入包 +```python +import blenderpython + +# 检查版本 +print(blenderpython.get_version()) + +# 检查依赖 +deps = blenderpython.check_dependencies() +print(deps) +``` + +### 2. 使用已翻译的模块 +```python +# 使用常量 +from blenderpython.suw_constants import SUWood +print(SUWood.SUSceneNew) + +# 使用客户端 +from blenderpython.suw_client import get_client, start_command_processor +client = get_client() +start_command_processor() + +# 使用观察者 +from blenderpython.suw_observer import register_observers +register_observers() +``` + +### 3. 测试功能 +```python +# 运行模块加载测试 +python -m blenderpython.suw_load + +# 运行客户端测试 +python -m blenderpython.suw_client + +# 运行观察者测试 +python -m blenderpython.suw_observer +``` + +## 🔧 开发指南 + +### 翻译原则 +1. **保持功能等价**: Python版本应实现与Ruby版本相同的功能 +2. **适配Blender**: 将SketchUp API调用转换为Blender API +3. **类型安全**: 使用Python类型提示提高代码质量 +4. **错误处理**: 添加适当的异常处理 +5. **文档完整**: 每个函数都应有清楚的文档字符串 + +### 代码风格 +- 使用Python PEP 8代码风格 +- 函数名使用snake_case +- 类名使用PascalCase +- 常量使用UPPER_CASE +- 添加类型提示 + +### 测试要求 +- 每个模块都应该可以独立运行测试 +- 主要功能应该有单元测试 +- 与Blender API的集成应该有集成测试 + +## 📚 原Ruby文件信息 + +| 文件名 | 行数 | 大小 | 主要功能 | +|--------|------|------|----------| +| SUWLoad.rb | 13 | 362B | 模块加载 | +| SUWConstants.rb | 306 | 8.8KB | 常量定义 | +| SUWClient.rb | 118 | 2.8KB | 网络通信 | +| SUWObserver.rb | 87 | 2.8KB | 事件观察 | +| SUWImpl.rb | 2019 | 70KB | **核心实现** | +| SUWMenu.rb | 71 | 2.4KB | 菜单系统 | +| SUWUnitPointTool.rb | 129 | 3.9KB | 点工具 | +| SUWUnitFaceTool.rb | 146 | 4.6KB | 面工具 | +| SUWUnitContTool.rb | 137 | 4.2KB | 轮廓工具 | +| SUWZoneDiv1Tool.rb | 107 | 3.1KB | 区域分割 | + +## 🎯 下一步计划 + +1. **优先翻译 SUWImpl.rb** (2019行) + - 这是最核心的文件,包含主要业务逻辑 + - 分阶段翻译,先翻译关键方法 + +2. **完善工具类** + - 翻译各种工具类的完整功能 + - 适配Blender的工具系统 + +3. **集成测试** + - 在Blender环境中测试完整功能 + - 修复兼容性问题 + +4. **文档完善** + - 添加API文档 + - 创建使用示例 + - 编写用户指南 + +## 📞 技术支持 + +如需帮助或有问题,请检查: +1. 模块导入是否正确 +2. Blender API是否可用 +3. 网络连接是否正常 +4. 依赖项是否满足 + +--- + +**总进度**: 4/10 模块完成 (40%) +**下一个里程碑**: 完成SUWImpl.rb翻译 (预计+35%进度) \ No newline at end of file diff --git a/blenderpython/__init__.py b/blenderpython/__init__.py new file mode 100644 index 0000000..5b05601 --- /dev/null +++ b/blenderpython/__init__.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +BlenderPython - SUWood Python翻译包 +原Ruby代码翻译为Python版本,适配Blender环境 + +主要模块: +- suw_constants: 常量定义 +- suw_client: TCP客户端通信 +- suw_observer: 事件观察者 +- suw_impl: 核心实现(待翻译) +- suw_menu: 菜单系统(待翻译) +- 各种工具模块(待翻译) +""" + +__version__ = "1.0.0" +__author__ = "Ruby to Python Translator" +__description__ = "SUWood Ruby代码的Python翻译版本" + +# 导入主要模块 +try: + from . import suw_constants + from . import suw_client + from . import suw_observer + from . import suw_load + + # 尝试导入其他模块(如果存在) + try: + from . import suw_impl + except ImportError: + print("⚠️ suw_impl 模块待翻译") + + try: + from . import suw_menu + except ImportError: + print("⚠️ suw_menu 模块待翻译") + + print("✅ BlenderPython SUWood 包加载成功") + +except ImportError as e: + print(f"❌ 包加载错误: {e}") + +# 包级别的便捷函数 +def get_version(): + """获取版本信息""" + return __version__ + +def get_modules(): + """获取已加载的模块列表""" + import sys + package_name = __name__ + modules = [] + + for module_name in sys.modules: + if module_name.startswith(package_name + '.'): + modules.append(module_name.split('.')[-1]) + + return modules + +def check_dependencies(): + """检查依赖项""" + dependencies = { + "bpy": "Blender Python API", + "socket": "网络通信", + "json": "JSON处理", + "threading": "多线程支持" + } + + available = {} + for dep, desc in dependencies.items(): + try: + __import__(dep) + available[dep] = True + except ImportError: + available[dep] = False + + return available + +if __name__ == "__main__": + print(f"🚀 BlenderPython SUWood v{__version__}") + print("=" * 50) + + # 显示模块信息 + modules = get_modules() + print(f"📦 已加载模块: {modules}") + + # 检查依赖 + deps = check_dependencies() + print("\n🔍 依赖检查:") + for dep, available in deps.items(): + status = "✅" if available else "❌" + print(f" {status} {dep}") + + print("\n📚 待翻译的Ruby文件:") + pending_files = [ + "SUWImpl.rb (核心实现,2019行)", + "SUWMenu.rb (菜单系统)", + "SUWUnitPointTool.rb (点工具)", + "SUWUnitFaceTool.rb (面工具)", + "SUWUnitContTool.rb (轮廓工具)", + "SUWZoneDiv1Tool.rb (区域分割工具)" + ] + + for file in pending_files: + print(f" ⏳ {file}") \ No newline at end of file diff --git a/blenderpython/__pycache__/__init__.cpython-313.pyc b/blenderpython/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7b9b6c8eff5dcaac748d99203a0e2cb37ed3494 GIT binary patch literal 3544 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 a^34>XU5b1wyWK|LebKnxLVs(~0{tIgH!@)W literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_client.cpython-313.pyc b/blenderpython/__pycache__/suw_client.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1c44a59f91697bbfb7b4a548a17ace328367ab8 GIT binary patch literal 13124 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 literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_impl.cpython-313.pyc b/blenderpython/__pycache__/suw_impl.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d675dc77fa1eeda0eff15f20c38576eff1f49e74 GIT binary patch literal 3583 zcmbss|4$qD^>e;E`!MF4Aq}HRxBv|}O9D-rC1D{cOQNW}mz3 zM5j#dm#ieAK-M^;DIK7u4M;7}Wm?GyN&f^#4J$ciQk8Kw{8R@fb=psRpF7(eWQ!*4 zBi*~tz0Z3ezwhsPH80POV0q5^I)9ym(BH{NW?9pl)!)D-h6IF>Kna$YsU{1yG|`xD zVldOhVwOjxr~(Oe84{TN7K000YmvadLSdVSgEQ-X%E0qT|F(H@Dv-yP!RDiVmdrj) z!L5CFT1-%9#{y+9^A&)?5%tD(9&3+%bvVEz8t+=&nHG_v^#HUlYdC|PbWX0{q|NL zkVhi^!R-JZBHo_Kqpo?p82eqw0K{9C}YJRAkmr8^%i&O{TR zeVn{AyEJu4o9s)({sb2_D{VcCvmYf#->@$e8@L#iJKarW_l8uuG2Ejv$AdmuWljbq z*+(cc%a-1(_QNKIJP1?Z1dBjnTCiY7pfL-N6&@QroWS55fh|MW?#UIbWoUnz9XkXY zVDkhHV7`#!DR|Bz*vrtX3@#LM0aqkA06Kvy&r>YqdrCaTgoXwF=NO@YP-gGiBoyMO zI%rpsYD-&oIS=TRDgcJ}gEGCs!4Lr?Vv8vHhI6dK z2CWmC1iUWvN!Y|t3u-1L8l4Bg(SlQI3E~PM@K-;o~tDph0NG8pp&)(h@zAgISK|E&Ntl6v*+8gzV(Iapk7a4_4AgJBti0g+ir8>o+ErCTk?=$A$it)yH4 zQMAbA#q)nK&mDMz5+0z0;zqT?+gKr!LMCjwutAucX^A29)e7a)Sc0jPK?u_d5j`<; zZu$Ld%hB_RixX~z*fceA{U&clyI9NtUUfi32YhnCFDgO5=_}Iqs+yNQdp!~k;O_zb zW0uJQ8qpUDc(GcPwM_=~3~)%503e#nEBkEsxGf8rH(y1*w>}_N+dj`#=vGr{_+4LC zdEjK=(f$7=Y|Q>|p&$a%lr?ZhmiI>x_*8S&Oa_*~H8Zu68}${+(zQ?~Z1%jB_*Gv5 zy>{N5=~S6Rc(3m$9ju9yF-0lsvKmP?c{@2b39UmLo;IAM(vUAI4K7fM;l9*J{}gYW zXj35a z=;W=5#O2$G_ugHc8&)=hbg7Z|26*kUGdC`U!^Z;GA%U_G_Ie_()h#oY^dWO{SI=`b z|F`VWse1?4$xdqi!{Kv0Y2xdT{)^~&X(8L8i%6a7rfl9B8X4$A8-(96&s7^a#`(JZ zqTJIRP_5lw?Cp|#*+c*yG6(1h=Z8%UZ8OF&)I^189<|UF=%>z1+VQA^-Hz5$!-l*i z5K^c$GfTjj)~yS7okf}*V2UwVK~xH^rAybjC2mbZ_#~b&o_l7(U%sAH%CtzprkZ=@?mC#H%|uI7k|_)j4sX1DNh5{ z!+{&TcKe($cqgvhOx%b;2rj<^1H1Bye#`I1_{6|q^4edXIKpq3+%rjx+l)UGYkwfz zE<64qT&!9lX@lVoOw!YEo9-ggkc?(ta_YpH7f&A-#51SGGtG@|;bh~P6ME#UG)$!% z9Hw%Ijc_}HkHHlr=Oi*tLcmhgBde8SzAHr3?yu0U@0bHrBlQRoxFS=wQcE0_JaQt+ q8n?Rcp`G^;8|Ny=j>T&Ze^u2GuRIdB9=(U2Cur->=F!h}7yKKQ7wixK literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_load.cpython-313.pyc b/blenderpython/__pycache__/suw_load.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..445b227cb8b874931c491ec3ba1237b43dbf67e4 GIT binary patch literal 3246 zcmb7G>r-3D72o^NgR6@K2#}C%xI7#zV*?Gw2DyInm?k#Z=2p|ppjIP;u+@O1*(;?r zNoV9qT+42!#*@UM4lZ(@YTDG!B<*l6 zhMGCVJk4TOKpwUZd35byOJso8>cSEmJk&r*VUMN{SijvTQXUTC8S?SkY0XAJ)ktJT z-67M{Jmv+W>p?HZ#j0)q-HgquZUo(eEvjAwdI>I3bq@4WT&n6O(93X{s`H>*u~pTJ zK`+PUkX1z;63zKl2&e^#rN=027t0(7SBX|A9i9zhx$7B(e0z{)U$JJd`d1dTtLjjD zE?tQ3SZhTgL%b1hQax;*YRGOmdS4PJ1K=d>WiFQCisCyu@wD@cPJE;BEDcm zf(|3;2#keCLFd%L(SSb~ffi3%q2rPtKj+8Lwvu)<7>Ibop+GR=jf6tPscf}-#5d&s zb}vk}-gZ0`^m~T`&wVFvt1e;8AB;xpVUb@tRm~<9l9e83rDGa0_T*XDCAbiF^WrPU>aWNF+AXkO>Fz{db7Zk4o zo`Yy`blHJD=?LT(DMN43%0A!Lwt(*tDUMEqKZh1%m7K|5pkn@jP>|F!4~)yvP17QL>nMT=FM1U`fcsJ z(x0ALo{DK3pP9Uto_I&w>2anxd@5Q7)?ZxtrLg$P$LZ@AH7l1ePHz)>qnpVX9RFkD zs5DrHGgJ@l`%b-5GSG9({nS8Dx3}*|xBGC9sF;SL7z1_zTv*Zje;SY?lA`y8!~Wo~ zq6=dHt-}5ouKmBF_nLkV$u|E&^P8^ZHxp){SrA+%p zwst<*>go1N&qhLF&+$=+@5lK%tn%1HSTRZ%8$P8J51sT6J)55jS3$s5_)B9@#L(X> z?Xe?^w#K;WUS)0K`NY`uHo0NfOy#b)Y2M-WfYdu8*!lyU#OrTjh&n%h&xo%0s!3QV{j zwgliRY+q0xBLZ9aQ|q8xPas;228o?2BITyn0|X%X7y!EquE;((Tms3#RoIElAYqXz zg}@Wpwf?nr6abUW>i}?S72u5k@U_?lAazqfzev zF>cNQOaoSPe-zfI(L}ura~|rY?-CTMCF&sKKrwA3{niIdAHBVJ`K27!##sSgn{nDD zI1ShWrV8gJ4?qY`qZu);_YjLDaT!T`{LcxD1?|8O48ih0BbG+Aw9 z*lMIuenqb_O40dA9Et2A&oaY^LeC-Thn`pYrH>-sB(u{PR%s0FxCaY&RcgPK$H%dQg zOYJ){yZfl@Iwo@iDOSuFDhOxHTT{lS&o?!l>x=i^<4Y&HCo3n72|Cf9Xq3yg-sPJf zFsOQ4R*!gxY;wx1^Q$i_P|1cBL=|W>=IlkjBe8)Fg2FAaZY>>r; znu8pDo8u1d*4-}OcyOog_D&t>iqY#G^96!lZ?qPyveTDi`8QAd4A9OhcYwc6j&IrX zKT5P5;&IvOm(S;pfAQwancqyg1iTRjqU3CZ`#0Xx;%5o~6qX*J|2dJS<{0PEkvhaz!OF=hO&hPl0%yq$2PA&G_(4uql}=vj(g z;Spu}92G4f`vX0)SSLDu5u7vE&zS3Drg_AiHJ&z}wVbw0P#4=Ko}abWr%*#yZ=nuQ z3vA5->sVknF0j=L?B)fwA^&sygDsM|mK59i(9D(7GAem!H&f*g1uL~RyA`o~e0-Lv z`WrFYo?@FpiEnx72+Xc4Q>)4+W-A&`_s4qUy(AJ}d$m3>@@7+FQ_}YVBk$;*ZS9d; zdgWe^%>D4M?2ob)h;@9G9ixyDu5%ieMndzANh%@9TvLkOrcSkQj@>`Q?*AsMBPL%< cWP3i-@9io>cgpl#?aUp!wX2o6)2ajgzo33;8UO$Q literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_menu.cpython-313.pyc b/blenderpython/__pycache__/suw_menu.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad8c881071f24d4382b1087092aac35982ff1192 GIT binary patch literal 1321 zcmZ8hTWb?R6rQ~#*{)42ma0V1wW1ijAl{9|7yD`um(T|nhFsPfO*V0M7pWjb5UpA* zMI%+KjfmQU)ndh4Ed2|8>4W zXP>eN{h*8H;FpcYPB1D+Mg*-#ok(V)NM;W}G#!LdSnzQN7;i11UOpBuhhQbj$sXy1 zs+rO*>13aAwy46{YJkt@=CPtj5_zP&)43^B?zQ(yGdHC-y2&j--}BNgCq9s zN`0zUuZ@bLGd1R19R%;p184Y+|C*m)1{O!k3oouL%siU^Sav3!*Jo-AZ-(sG15V{# z{o&P9X1Z^__N-pLB4#{_9{y8z5Y`o>fD%y1$_(LTmhdtcMI<2efI&H+gyf(S+Rw|O zC_2XzK^90$Z%a(HLd(<_SHUgjW&m&$i&;N?knd-lS;Tj5 ze{8?JWe?o(KlXJBl#HM8IJMBr*Y08mEXgw_TaXa#4=*X0}qqk>NV(<3v! zE?{Gv*$M%6|0BEh*^^;z1INJY`Po>qo70MI~g_M@fP#Mh;1jCg@Nv@QY zWFpY=Nj8G^@ji)Mj5k+9u=Am2->iBP7oUD|3;6v>Mf%SlmoD+ru47Vy&6@f3M$lG#JEt*nt5usVtm0&<(GF>V|*q@bR(bBRILcxA&j%d490|hC`cQC z#pwniv`eiHjCCVru|K?}$)BE1Kt?w}sP`PL%YQs9q019} ojBJEm+P3;8fF+J$m|wvl!~bauGhM$Bg^h>_&IWhPp`9+1zeC!aY5)KL literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_observer.cpython-313.pyc b/blenderpython/__pycache__/suw_observer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c22fd063720b0206774330d75a777cea168a0f9f GIT binary patch literal 12982 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 EUzJ)RbpQYW literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_unit_cont_tool.cpython-313.pyc b/blenderpython/__pycache__/suw_unit_cont_tool.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ffdd937e6fcf1b527467986c8932c15ed87a3e4 GIT binary patch literal 831 zcmZ8f&ubGw6rRa$wsB1?B4WuwM=!dlr3QL1hzILA2y5`*!jNpX?c#Pf&dee`2)(G) zgO?;CsH7KBK|OddXvqK2OAjvIbkp{h62z--@}tB&*l*v=_ul(vzny+2qa%pgLwnW6 z2>p_aaj2uDe+iNXG7&>_=o~U}7MY2~#E@m!O2D472;DLWT2YN;Sb|hHi+4!TcLiAr zd=Z3<+ztYdTqd{H#cJTkpI&yizU@BU?7se}$IqMH7f*h-zvc;a$tKw&>*raS5Pi4) zet)B$CwraGal00`-o}qwx;;=F!AKwVPs2t7S%_gcal*t*F%wKRl`LXOQ)Svp($J#W zDV6;r&=sBq03rw~lR9wX$+JqI4nu)VKK(2}1KmC3Og!NTG7^RG6jbz@^>-xRd=-Wc3rJM%c*|VQA<>PcyjA25<5q6rYHq>0>F|3Zs9I&u@hc7+zpDd3Egn9g zAvi~Eu#O7pU3EQ5QR>P~8qH08^Zb}egBU*wuz`N2GTX<_@1!pDVTnGVf-xf}_@y-m zCg_x|1(nc~d79GuVaXdYvy?8oj0?~89X|kihSEx4Q_7^V(Md|VC<)i5B_Wtw4u!*k zlfq02vy=pBKu^IKA80C8|E5)J93Tm4gtcw$$_~m6&hrmm4f;O- literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_unit_face_tool.cpython-313.pyc b/blenderpython/__pycache__/suw_unit_face_tool.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ada159d171a11dcd6a649eed0bd91f87d0f194a6 GIT binary patch literal 1066 zcmZ8gQA-q26uz@FyF0GBN@i_FaZ4x{RLt_BC3;Ay>_LKQNoeA-?v6S+?re8v2!lWf z%S3(2jUYt{q7ABt9@4Z0e}WGQHuTnx=2I*C;A`jZ?3(O>`R=**oO922&bd>ltqmZM z$tUsQ6CQ+qvrSoql2T{|C5ezi4Y%RjW9G1)bUwY_Ui!B4Vsd-_BgO$|c5(aF^W56k2!dR7llLZM`6CtIt6a|#ku#`ZOj492+n0^cCw%GV}C1- z_Vz?@SfE~Tri=__x7*$nRg-2|umwZW1}m%vD*%Jo*SaHe)KI7)cXnLv?2vmDW5m=` z@_?o!6ACS_sp7n4!+Jz4m@65C!4i{AHZ^98gsAM0*rAF>x0fw7kk4oV7)M(t&;1ZX zTMfsi#jT^E>8f91?M8iDW+Zce<@|c{dgDg$#-@0)0KU+JVlDs4_rWkBTQWAPK(+uO zqk6*9m|jE3Lo240EOmqos?;zwHK`&UkH%;Qc!quoEqOuk_Hs+zdBoOC++9~1_KSNro`qUrfc)ai zh01^8@I!N!zc^D%f3n#=D7gUw+^j4kJyM{KH?|qhef>j!D#QgeWEywUwz*9)~0OwCilF5p7(j* z_xU})=efB+z>lDP_))xn$b-s_NMPEL;OcTYO3a8ZSaWwV z&YVX{Plz?fVCEMhd-zCN(fM7flGgdRRW-#w$L|`@`_;5{ZN$#qsvJLNU--svof@^z z4wWZ<2=ib=OsEAh*o1ril{5L-!82jL^6@Qe?2>hK?EdX6td+-4S;x*>6XVv&Ve9B8 ze!u;F-ac|V%+E}HX^-4!ya5bmFAUCHK0GsZzFZu%uU)E4P0ZZ*+`68%hi_KS9qBQ8 z2g(y)S8|8_amNuyQgmQA7X>klM1&c@!6h)*Ex52paEq+qfscjHD|!W==-cWSxOTM9 zgE^t89pNUy54vXfT10lMR|rs@UkHM(Sy+NwlC7boX6r(~XPeLdY5qI41&80yMqiAcYra@6YH6KER zqMfPv@WS}j6tSQ!x+_&91=>p^Ih;-b)1u>$_Xob zw(gM82FumaGgo*gnsr5(H<=F(`8xQ>&K+UCJLJVI?8L+$>?49Bq6vh_W-u@;9Fz>;rIaC5 zmUP0*%z(*Bl0sG`^YMiN+*DHq;ZE&k5QFIVmF-#oJu2E(XPfUWZ_9G`R;a}Ewb@p^<{LZU)r*$#tYw70Gkm`m(n_MieD+gk_{2z|i zRF4rG!iqsu27iK2O>^tfC^%eS5!yOetW-onihD@ShdN2$ zh6x2ReLs00A&BF4Kut^gRYQ}dgnBS-u2~f1NAypCt;U0J0BC(S&*ZfHItc0ZJT9y) zEHAuP3O+y0y+Bck*Ha8E79?h4Fz2B_J&pUpgfyCJsHYKBM^V!>CVySCC^5;ytqXZO zJ7iry=fKriSq;Ll>gX4j=QGr)w_)Dd$r~h&9lTW-w{8@93TQW=OPI7E6GJYFxyfZz zP0ii*EP<3C5^r9FvKP@d-cDEc9`p^y*{f5&r@yYlk2J literal 0 HcmV?d00001 diff --git a/blenderpython/__pycache__/suw_zone_div1_tool.cpython-313.pyc b/blenderpython/__pycache__/suw_zone_div1_tool.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..829339b8bccfcbc3e3f77c5fca165c472309693e GIT binary patch literal 848 zcmZWn&ubGw6rS1LY~z|(M1&Gh7ca74($eU^_wgHlIQX-uiG2n_Sfs}x1TlsB5c2U`fK}ZPKPp41w|Q=pQc4^ zzwz;4wUN{L^DQ2RyuQZQH+kzlf6~%QJ=p;ahCto(@S%w;L@|slnV8C^L={s`AsRCk zsxHP1HJTh#IyeDU!6g77JgPEijuhD?587J8R9`?4Iw{tYX!6%QhXp zYdwE07Ca=6z)4aDD~T_>tgS?GLLAre35ll0&YvDK2@qo^0anqEcyjmD^jV*U)z*^GCt4nSPqc2n>Ku`10NO%^CewytT=n zZT#NZ$r~~~4F&z?s)+tqjpA4PXSznu04t1V7XUgk#`sWGu<|#d;F&`tAc3&Dt7i64 Hwl~iaqA~>Z literal 0 HcmV?d00001 diff --git a/blenderpython/suw_client.py b/blenderpython/suw_client.py new file mode 100644 index 0000000..f914fee --- /dev/null +++ b/blenderpython/suw_client.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Client - Python翻译版本 +原文件: SUWClient.rb +用途: TCP客户端,与服务器通信 +""" + +import socket +import json +import struct +import threading +import time +from typing import List, Dict, Any, Optional + +# 常量定义 +TCP_SERVER_PORT = 7999 +OP_CMD_REQ_GETCMDS = 0x01 +OP_CMD_REQ_SETCMD = 0x03 +OP_CMD_RES_GETCMDS = 0x02 +OP_CMD_RES_SETCMD = 0x04 + +class SUWClient: + """SUWood 客户端类""" + + def __init__(self, host="127.0.0.1", port=TCP_SERVER_PORT): + self.host = host + self.port = port + self.sock = None + self.seqno = 0 + self.connect() + + def connect(self): + """连接到服务器""" + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + print(f"✅ 连接到服务器 {self.host}:{self.port}") + except Exception as e: + print(f"❌ 连接失败: {e}") + self.sock = None + + def reconnect(self): + """重新连接""" + if self.sock: + try: + self.sock.close() + except: + pass + + self.connect() + + def send_msg(self, cmd: int, msg: str): + """发送消息""" + if not self.sock: + print("❌ 未连接到服务器") + return False + + try: + opcode = (cmd & 0xffff) | 0x01010000 + self.seqno += 1 + + # 打包消息:[消息长度, 操作码, 序列号, 保留字段] + msg_bytes = msg.encode('utf-8') + header = struct.pack('iiii', len(msg_bytes), opcode, self.seqno, 0) + + full_msg = header + msg_bytes + self.sock.send(full_msg) + return True + + except Exception as e: + print(f"❌ 发送消息失败: {e}") + return False + + def recv_msg(self) -> Optional[str]: + """接收消息""" + if not self.sock: + print("❌ 未连接到服务器") + return None + + try: + # 接收头部(16字节) + header = self.sock.recv(16) + if len(header) < 16: + return None + + # 解包获取消息长度 + msg_len = struct.unpack('iiii', header)[0] + + # 接收消息内容 + msg = b"" + to_recv_len = msg_len + + while to_recv_len > 0: + chunk = self.sock.recv(to_recv_len) + if not chunk: + break + msg += chunk + to_recv_len = msg_len - len(msg) + + return msg.decode('utf-8') + + except Exception as e: + print(f"❌ 接收消息失败: {e}") + return None + +# 全局客户端实例 +_client_instance = None + +def get_client(): + """获取客户端实例""" + global _client_instance + if _client_instance is None: + _client_instance = SUWClient() + return _client_instance + +def get_cmds() -> List[Dict[str, Any]]: + """获取命令列表""" + msg = json.dumps({ + "cmd": "get_cmds", + "params": {"from": "su"} + }) + + client = get_client() + cmds = [] + + try: + if client.send_msg(OP_CMD_REQ_GETCMDS, msg): + res = client.recv_msg() + if res: + res_hash = json.loads(res) + if res_hash.get('ret') == 1: + cmds = res_hash.get('data', {}).get('cmds', []) + + except Exception as e: + print("========= get_cmds err is: =========") + print(e) + print("========= get_cmds res is: =========") + print(res if 'res' in locals() else "No response") + client.reconnect() + + return cmds + +def set_cmd(cmd: str, params: Dict[str, Any]): + """设置命令""" + cmds = { + "cmd": "set_cmd", + "params": params.copy() + } + + cmds["params"]["from"] = "su" + cmds["params"]["cmd"] = cmd + + msg = json.dumps(cmds) + client = get_client() + + try: + if client.send_msg(OP_CMD_REQ_SETCMD, msg): + client.recv_msg() # 接收响应但不处理 + + except Exception as e: + print(f"❌ set_cmd 错误: {e}") + client.reconnect() + +class CommandProcessor: + """命令处理器""" + + def __init__(self): + self.cmds_queue = [] + self.pause = 0 + self.running = False + self.timer_thread = None + + def start(self): + """启动命令处理器""" + if self.running: + return + + self.running = True + self.timer_thread = threading.Thread(target=self._timer_loop, daemon=True) + self.timer_thread.start() + print("✅ 命令处理器已启动") + + def stop(self): + """停止命令处理器""" + self.running = False + if self.timer_thread: + self.timer_thread.join(timeout=2) + print("⛔ 命令处理器已停止") + + def _timer_loop(self): + """定时器循环""" + while self.running: + try: + if self.pause > 0: + self.pause -= 1 + else: + self._process_commands() + + time.sleep(1) # 1秒间隔 + + except Exception as e: + print(f"❌ 命令处理循环错误: {e}") + time.sleep(1) + + def _process_commands(self): + """处理命令""" + try: + # 获取新命令 + swcmds0 = get_cmds() + swcmds = self.cmds_queue + swcmds0 + self.cmds_queue.clear() + + # 处理每个命令 + for swcmd in swcmds: + self._execute_command(swcmd) + + except Exception as e: + print(f"❌ 处理命令时出错: {e}") + + def _execute_command(self, swcmd: Dict[str, Any]): + """执行单个命令""" + try: + data = swcmd.get("data") + + if isinstance(data, str): + # 直接执行字符串命令(注意安全性) + print(f"执行字符串命令: {data}") + # 在实际应用中,这里应该更安全地执行命令 + + elif isinstance(data, dict) and "cmd" in data: + cmd = data.get("cmd") + print(f"执行命令: {cmd}, 数据: {data}") + + if self.pause > 0: + self.cmds_queue.append(swcmd) + elif cmd.startswith("pause_"): + self.pause = data.get("value", 1) + else: + pre_pause_time = data.get("pre_pause", 0) + if pre_pause_time > 0: + data_copy = data.copy() + del data_copy["pre_pause"] + swcmd_copy = swcmd.copy() + swcmd_copy["data"] = data_copy + self.pause = pre_pause_time + self.cmds_queue.append(swcmd_copy) + else: + # 执行命令 + self._call_suwood_method(cmd, data) + + after_pause_time = data.get("after_pause", 0) + if after_pause_time > 0: + self.pause = after_pause_time + + except Exception as e: + print(f"❌ 执行命令时出错: {e}") + + def _call_suwood_method(self, cmd: str, data: Dict[str, Any]): + """调用SUWood方法""" + try: + # 这里需要导入SUWImpl并调用相应方法 + from .suw_impl import SUWImpl + + # 获取SUWImpl实例 + impl_instance = SUWImpl.get_instance() + + # 调用方法 + if hasattr(impl_instance, cmd): + method = getattr(impl_instance, cmd) + method(data) + else: + print(f"⚠️ 方法不存在: {cmd}") + + except ImportError: + print("⚠️ SUWImpl 模块未找到") + except Exception as e: + print(f"❌ 调用SUWood方法时出错: {e}") + +# 全局命令处理器实例 +_processor_instance = None + +def get_processor(): + """获取命令处理器实例""" + global _processor_instance + if _processor_instance is None: + _processor_instance = CommandProcessor() + return _processor_instance + +def start_command_processor(): + """启动命令处理器""" + processor = get_processor() + processor.start() + +def stop_command_processor(): + """停止命令处理器""" + processor = get_processor() + processor.stop() + +# 自动启动命令处理器(可选) +if __name__ == "__main__": + print("🚀 SUW客户端测试") + + # 测试连接 + client = get_client() + if client.sock: + print("连接成功,测试获取命令...") + cmds = get_cmds() + print(f"获取到 {len(cmds)} 个命令") + + # 启动命令处理器 + start_command_processor() + + try: + # 保持运行 + while True: + time.sleep(10) + except KeyboardInterrupt: + print("\n停止客户端...") + stop_command_processor() + else: + print("连接失败") \ No newline at end of file diff --git a/blenderpython/suw_constants.py b/blenderpython/suw_constants.py new file mode 100644 index 0000000..187311d --- /dev/null +++ b/blenderpython/suw_constants.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUWood Constants - Python翻译版本 +原文件: SUWConstants.rb +用途: 定义常量、路径管理和核心功能函数 +""" + +import os +from pathlib import Path + +class SUWood: + """SUWood 主要常量和功能类""" + + # 场景操作常量 + SUSceneNew = 1 # 清除之前的订单 + SUSceneOpen = 2 # 清除之前的订单 + SUSceneSave = 3 + SUScenePrice = 4 + + # 单元操作常量 + SUUnitPoint = 11 + SUUnitFace = 12 + SUUnitDelete = 13 + SUUnitContour = 14 + + # 区域操作常量 + SUZoneFront = 20 + SUZoneDiv1 = 21 + SUZoneResize = 22 + SUZoneCombine = 23 + SUZoneReplace = 24 + SUZoneMaterial = 25 + SUZoneHandle = 26 + SUZoneCloth = 27 + SUZoneLight = 28 + + # 空间位置常量 + VSSpatialPos_F = 1 # 前 + VSSpatialPos_K = 2 # 后 + VSSpatialPos_L = 3 # 左 + VSSpatialPos_R = 4 # 右 + VSSpatialPos_B = 5 # 底 + VSSpatialPos_T = 6 # 顶 + + # 单元轮廓常量 + VSUnitCont_Zone = 1 # 区域轮廓 + VSUnitCont_Part = 2 # 部件轮廓 + VSUnitCont_Work = 3 # 挖洞轮廓 + + # 版本常量 + V_Dealer = 1000 + V_Machining = 1100 + V_Division = 1200 + V_PartCategory = 1300 + V_Contour = 1400 + V_Color = 1500 + V_Profile = 1600 + V_Surf = 1700 + V_StretchPart = 1800 + V_Material = 1900 + V_Connection = 2000 + V_HardwareSchema = 2050 + V_HardwareSet = 2100 + V_Hardware = 2200 + V_Groove = 2300 + V_DesignParam = 2400 + V_ProfileSchema = 2500 + V_StructPart = 2600 + V_CraftPart = 2700 + V_SeriesPart = 2800 + V_Drawer = 2900 + V_DesignTemplate = 3000 + V_PriceTemplate = 3100 + V_MachineCut = 3200 + V_MachineCNC = 3300 + V_CorpLabel = 3400 + V_CorpCAM = 3500 + V_PackLabel = 3600 + V_Unit = 5000 + + # 路径常量 + PATH = os.path.dirname(__file__) + + def __init__(self): + """初始化SUWood实例""" + pass + + @classmethod + def icon_path(cls, icon_name, ext='png'): + """获取图标路径""" + return f"{cls.PATH}/icon/{icon_name}.{ext}" + + @classmethod + def unit_path(cls): + """获取单元路径""" + try: + from .suw_impl import SUWImpl + return f"{SUWImpl.server_path}/drawings/Unit" + except ImportError: + return f"{cls.PATH}/drawings/Unit" + + @classmethod + def suwood_path(cls, ref_v): + """根据版本值获取SUWood路径""" + try: + from .suw_impl import SUWImpl + server_path = SUWImpl.server_path + except ImportError: + server_path = cls.PATH + + path_mapping = { + cls.V_Material: f"{server_path}/images/texture", + cls.V_StretchPart: f"{server_path}/drawings/StretchPart", + cls.V_StructPart: f"{server_path}/drawings/StructPart", + cls.V_Unit: f"{server_path}/drawings/Unit", + cls.V_Connection: f"{server_path}/drawings/Connection", + cls.V_HardwareSet: f"{server_path}/drawings/HardwareSet", + cls.V_Hardware: f"{server_path}/drawings/Hardware", + } + + return path_mapping.get(ref_v, server_path) + + @classmethod + def suwood_pull_size(cls, pos): + """根据位置获取拉手尺寸类型""" + size_mapping = { + 1: "HW", # 右上 + 2: "W", # 右中 + 3: "HW", # 右下 + 4: "H", # 中上 + 6: "H", # 中下 + 11: "HW", # 右上-竖 + 12: "W", # 右中-竖 + 13: "HW", # 右下-竖 + 14: "H", # 中上-竖 + 16: "H", # 中下-竖 + 21: "HW", # 右上-横 + 22: "W", # 右中-横 + 23: "HW", # 右下-横 + 24: "H", # 中上-横 + 26: "H", # 中下-横 + } + return size_mapping.get(pos) + + @classmethod + def scene_save(cls): + """保存场景""" + try: + import bpy # Blender Python API + + scene = bpy.context.scene + order_id = scene.get("order_id") + if order_id is None: + return + + data = { + "method": cls.SUSceneSave, + "order_id": order_id + } + cls.set_cmd("r00", data) + + if not bpy.data.filepath: + from .suw_impl import SUWImpl + scene_path = Path(f"{SUWImpl.server_path}/blender") + scene_path.mkdir(exist_ok=True) + + order_code = scene.get("order_code", "untitled") + filepath = scene_path / f"{order_code}.blend" + bpy.ops.wm.save_as_mainfile(filepath=str(filepath)) + else: + bpy.ops.wm.save_mainfile() + + except ImportError: + print("Blender API not available - scene_save not implemented") + + @classmethod + def scene_price(cls): + """场景价格计算""" + try: + import bpy + scene = bpy.context.scene + order_id = scene.get("order_id") + if order_id is None: + return + + params = { + "method": cls.SUScenePrice, + "order_id": order_id + } + cls.set_cmd("r00", params) + + except ImportError: + print("Blender API not available - scene_price not implemented") + + @classmethod + def import_unit(cls, uid, values, mold): + """点击创体(产品UID)""" + # 原本激活SketchUp工具,这里需要适配到Blender + try: + from .suw_unit_point_tool import SUWUnitPointTool + # 创建单元点工具 + width = values.get("width", 0) * 0.001 # 转换为米 + depth = values.get("depth", 0) * 0.001 + height = values.get("height", 0) * 0.001 + + tool = SUWUnitPointTool(width, depth, height, uid, mold) + # 在Blender中激活工具的逻辑需要根据具体实现 + print(f"激活单元点工具: {uid}, 尺寸: {width}x{depth}x{height}") + + except ImportError: + print("SUWUnitPointTool not available") + + @classmethod + def import_face(cls, uid, values, mold): + """选面创体(产品UID)""" + try: + from .suw_unit_face_tool import SUWUnitFaceTool + tool = SUWUnitFaceTool(cls.VSSpatialPos_F, uid, mold) + print(f"激活单元面工具: {uid}") + + except ImportError: + print("SUWUnitFaceTool not available") + + @classmethod + def front_view(cls): + """前视图""" + try: + from .suw_impl import SUWImpl + + uid = SUWImpl.selected_uid + obj = SUWImpl.selected_obj + + if uid is None or obj is None: + print("请先选择正视于的基准面!") + return + + params = { + "method": cls.SUZoneFront, + "uid": uid, + "oid": obj + } + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def delete_unit(cls): + """删除单元""" + try: + import bpy + from .suw_impl import SUWImpl + + scene = bpy.context.scene + order_id = scene.get("order_id") + uid = SUWImpl.selected_uid + obj = SUWImpl.selected_obj + + if uid is None: + print("请先选择待删除的柜体!") + return + elif order_id is None: + print("当前柜体不是场景方案的柜体!") + return + + # 在实际应用中,这里应该有确认对话框 + # 现在简化为直接执行 + + params = { + "method": cls.SUUnitDelete, + "order_id": order_id, + "uid": uid + } + if obj: + params["oid"] = obj + + cls.set_cmd("r00", params) + + except ImportError: + print("Blender API or SUWImpl not available") + + @classmethod + def combine_unit(cls, uid, values, mold): + """模块拼接""" + try: + from .suw_impl import SUWImpl + + selected_zone = SUWImpl.selected_zone + if selected_zone is None: + print("请先选择待拼接的空区域!") + return + + params = { + "method": cls.SUZoneCombine, + "uid": selected_zone.get("uid"), + "zid": selected_zone.get("zid"), + "source": uid + } + if mold: + params["module"] = mold + + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def replace_unit(cls, uid, values, mold): + """模块/产品替换""" + try: + from .suw_impl import SUWImpl + + if SUWImpl.selected_zone is None and (mold == 1 or mold == 2): + print("请先选择待替换的区域!") + return + elif SUWImpl.selected_obj is None and (mold == 3): + print("请先选择待替换的部件!") + return + + params = { + "method": cls.SUZoneReplace, + "source": uid, + "module": mold + } + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def replace_mat(cls, uid, values, mat_type): + """材料替换""" + try: + from .suw_impl import SUWImpl + + selected_zone = SUWImpl.selected_zone + if selected_zone is None: + print("请先选择待替换材料的区域!") + return + + params = { + "method": cls.SUZoneMaterial, + "mat_id": uid, + "type": mat_type + } + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def replace_handle(cls, width, height, set_id, conn_id): + """替换拉手""" + try: + from .suw_impl import SUWImpl + + selected_zone = SUWImpl.selected_zone + if selected_zone is None: + print("请先选择待替换拉手的区域!") + return + + params = { + "method": cls.SUZoneHandle, + "uid": selected_zone.get("uid"), + "zid": selected_zone.get("zid"), + "conn_id": conn_id, + "set_id": set_id + } + + if width is not None and width != "": + params["width"] = int(width) + if height is not None and height != "": + params["height"] = int(height) + + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def clear_current(cls, ref_v): + """清除当前选择""" + try: + from .suw_impl import SUWImpl + + if (ref_v == 2102 or ref_v == 2103) and SUWImpl.selected_zone: + params = { + "uid": SUWImpl.selected_uid + } + cls.set_cmd("r01", params) + SUWImpl.instance.sel_clear() + + except ImportError: + print("SUWImpl not available") + + @classmethod + def replace_clothes(cls, front, back, set_id, conn_id): + """挂衣杆替换""" + try: + from .suw_impl import SUWImpl + + selected_zone = SUWImpl.selected_zone + if selected_zone is None: + print("请先选择待替换衣杆的区域!") + return + + params = { + "method": cls.SUZoneCloth, + "uid": selected_zone.get("uid"), + "zid": selected_zone.get("zid"), + "conn_id": conn_id, + "set_id": set_id + } + + if front != 0: + params["front"] = front + if back != 0: + params["back"] = back + + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def replace_lights(cls, front, back, set_id, conn_id): + """灯带替换""" + try: + from .suw_impl import SUWImpl + + selected_zone = SUWImpl.selected_zone + if selected_zone is None: + print("请先选择待替换灯带的区域!") + return + + # 处理连接ID(可能是数组) + if isinstance(conn_id, list): + conns = ",".join(map(str, conn_id)) + else: + conns = str(conn_id) + + params = { + "method": cls.SUZoneLight, + "uid": selected_zone.get("uid"), + "zid": selected_zone.get("zid"), + "conn_id": conns, + "set_id": set_id + } + + if front != 0: + params["front"] = front + if back != 0: + params["back"] = back + + cls.set_cmd("r00", params) + + except ImportError: + print("SUWImpl not available") + + @classmethod + def set_cmd(cls, cmd_type, params): + """设置命令""" + try: + from .suw_impl import SUWImpl + SUWImpl.set_cmd(cmd_type, params) + except ImportError: + print(f"Command: {cmd_type}, Params: {params}") + +# 创建全局实例 +suwood = SUWood() \ No newline at end of file diff --git a/blenderpython/suw_impl.py b/blenderpython/suw_impl.py new file mode 100644 index 0000000..98a6319 --- /dev/null +++ b/blenderpython/suw_impl.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Implementation - Python存根版本 +原文件: SUWImpl.rb (2019行) +用途: 核心实现类,SUWood的主要功能 + +注意: 这是存根版本,需要进一步翻译完整的Ruby代码 +""" + +from typing import Optional, Any, Dict, List + +class SUWImpl: + """SUWood核心实现类 - 存根版本""" + + _instance = None + selected_uid = None + selected_obj = None + selected_zone = None + server_path = "/default/path" # 默认服务器路径 + + def __init__(self): + """初始化SUWImpl实例""" + pass + + @classmethod + def get_instance(cls): + """获取单例实例""" + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def startup(self): + """启动SUWood系统""" + print("🚀 SUWood系统启动 (存根版本)") + + def sel_clear(self): + """清除选择""" + SUWImpl.selected_uid = None + SUWImpl.selected_obj = None + SUWImpl.selected_zone = None + print("🧹 清除选择") + + def sel_local(self, obj: Any): + """设置本地选择""" + if hasattr(obj, 'get'): + SUWImpl.selected_uid = obj.get("uid") + SUWImpl.selected_obj = obj + print(f"🎯 选择对象: {SUWImpl.selected_uid}") + else: + print("⚠️ 无效的选择对象") + + def scaled_start(self): + """开始缩放操作""" + print("📏 开始缩放操作") + + def scaled_finish(self): + """完成缩放操作""" + print("✅ 完成缩放操作") + + @classmethod + def set_cmd(cls, cmd_type: str, params: Dict[str, Any]): + """设置命令""" + try: + from .suw_client import set_cmd + set_cmd(cmd_type, params) + except ImportError: + print(f"设置命令: {cmd_type}, 参数: {params}") + +# 待翻译的方法列表(从原Ruby文件中) +METHODS_TO_TRANSLATE = [ + "startup", + "sel_clear", + "sel_local", + "scaled_start", + "scaled_finish", + # ... 还有2000+行的其他方法需要翻译 +] + +print("📝 SUWImpl存根版本已加载") +print(f"⏳ 待翻译方法数量: {len(METHODS_TO_TRANSLATE)}") +print("💡 提示: 这是存根版本,需要翻译完整的SUWImpl.rb文件 (2019行)") \ No newline at end of file diff --git a/blenderpython/suw_load.py b/blenderpython/suw_load.py new file mode 100644 index 0000000..c717314 --- /dev/null +++ b/blenderpython/suw_load.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Load Module - Python翻译版本 +原文件: SUWLoad.rb +用途: 加载所有SUWood相关模块 +""" + +import sys +import os +from pathlib import Path + +# 添加当前目录到Python路径 +current_dir = Path(__file__).parent +sys.path.insert(0, str(current_dir)) + +# 导入所有SUWood模块 +try: + from . import suw_constants + from . import suw_impl + from . import suw_client + from . import suw_observer + from . import suw_unit_point_tool + from . import suw_unit_face_tool + from . import suw_unit_cont_tool + from . import suw_zone_div1_tool + from . import suw_menu + + print("✅ SUWood 所有模块加载成功") + +except ImportError as e: + print(f"⚠️ 模块加载警告: {e}") + print("部分模块可能尚未创建或存在依赖问题") + +# 模块列表(对应原Ruby文件) +REQUIRED_MODULES = [ + 'suw_constants', # SUWConstants.rb + 'suw_impl', # SUWImpl.rb + 'suw_client', # SUWClient.rb + 'suw_observer', # SUWObserver.rb + 'suw_unit_point_tool', # SUWUnitPointTool.rb + 'suw_unit_face_tool', # SUWUnitFaceTool.rb + 'suw_unit_cont_tool', # SUWUnitContTool.rb + 'suw_zone_div1_tool', # SUWZoneDiv1Tool.rb + 'suw_menu' # SUWMenu.rb +] + +def check_modules(): + """检查所有必需模块是否存在""" + missing_modules = [] + + for module_name in REQUIRED_MODULES: + module_file = current_dir / f"{module_name}.py" + if not module_file.exists(): + missing_modules.append(module_name) + + if missing_modules: + print(f"❌ 缺少模块: {', '.join(missing_modules)}") + return False + else: + print("✅ 所有必需模块文件都存在") + return True + +def load_all_modules(): + """加载所有模块""" + loaded_modules = [] + failed_modules = [] + + for module_name in REQUIRED_MODULES: + try: + __import__(f'blenderpython.{module_name}') + loaded_modules.append(module_name) + except ImportError as e: + failed_modules.append((module_name, str(e))) + + print(f"✅ 成功加载模块: {len(loaded_modules)}/{len(REQUIRED_MODULES)}") + + if failed_modules: + print("❌ 加载失败的模块:") + for module, error in failed_modules: + print(f" - {module}: {error}") + + return loaded_modules, failed_modules + +if __name__ == "__main__": + print("🚀 SUWood Python模块加载器") + print("=" * 40) + + # 检查模块文件 + check_modules() + + # 尝试加载所有模块 + loaded, failed = load_all_modules() + + print(f"\n📊 加载结果: {len(loaded)}/{len(REQUIRED_MODULES)} 个模块成功加载") \ No newline at end of file diff --git a/blenderpython/suw_menu.py b/blenderpython/suw_menu.py new file mode 100644 index 0000000..43743c3 --- /dev/null +++ b/blenderpython/suw_menu.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Menu - Python存根版本 +原文件: SUWMenu.rb +用途: 菜单系统 + +注意: 这是存根版本,需要进一步翻译完整的Ruby代码 +""" + +class SUWMenu: + """SUWood菜单系统 - 存根版本""" + + def __init__(self): + """初始化菜单系统""" + pass + + def create_menu(self): + """创建菜单""" + print("📋 创建SUWood菜单 (存根版本)") + + def add_menu_item(self, label: str, command: str): + """添加菜单项""" + print(f"➕ 添加菜单项: {label} -> {command}") + +print("�� SUWMenu存根版本已加载") \ No newline at end of file diff --git a/blenderpython/suw_observer.py b/blenderpython/suw_observer.py new file mode 100644 index 0000000..cd01350 --- /dev/null +++ b/blenderpython/suw_observer.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Observer - Python翻译版本 +原文件: SUWObserver.rb +用途: 观察者类,监听Blender中的事件 +""" + +from typing import Optional, List, Any + +try: + import bpy + from bpy.app.handlers import persistent + BLENDER_AVAILABLE = True +except ImportError: + BLENDER_AVAILABLE = False + # 定义一个假的persistent装饰器 + def persistent(func): + return func + print("⚠️ Blender API 不可用,观察者功能将被禁用") + +class SUWToolsObserver: + """工具观察者类 - 监听工具变化""" + + cloned_zone = None + + def __init__(self): + self.current_tool = None + + def on_active_tool_changed(self, context, tool_name: str, tool_id: int): + """当活动工具改变时调用""" + try: + from .suw_impl import SUWImpl + + # 工具ID常量(对应SketchUp的工具ID) + MOVE_TOOL_ID = 21048 + ROTATE_TOOL_ID = 21129 + SCALE_TOOL_ID = 21236 + + if tool_id == SCALE_TOOL_ID: + SUWImpl.get_instance().scaled_start() + else: + SUWImpl.get_instance().scaled_finish() + + except ImportError: + print(f"工具变化: {tool_name} (ID: {tool_id})") + +class SUWSelectionObserver: + """选择观察者类 - 监听选择变化""" + + def __init__(self): + self.last_selection = [] + + def on_selection_bulk_change(self, selection: List[Any]): + """当选择批量改变时调用""" + try: + from .suw_impl import SUWImpl + from .suw_client import set_cmd + + if len(selection) <= 0: + # 检查是否有订单ID且之前有选择 + if self._has_order_id() and SUWImpl.selected_uid: + set_cmd("r01", {}) # 切换到订单编辑界面 + + SUWImpl.get_instance().sel_clear() # 清除数据 + return + + # 过滤SUWood对象 + suw_objs = self._filter_suw_objects(selection) + + if not suw_objs: + if self._has_order_id() and SUWImpl.selected_uid: + set_cmd("r01", {}) + SUWImpl.get_instance().sel_clear() + + elif len(suw_objs) == 1: + # 选择单个SUWood对象 + self._clear_selection() + SUWImpl.get_instance().sel_local(suw_objs[0]) + + except ImportError: + print(f"选择变化: {len(selection)} 个对象") + + def _filter_suw_objects(self, selection: List[Any]) -> List[Any]: + """过滤SUWood对象""" + suw_objs = [] + + for obj in selection: + if self._is_suw_object(obj): + suw_objs.append(obj) + + return suw_objs + + def _is_suw_object(self, obj: Any) -> bool: + """检查是否是SUWood对象""" + if not BLENDER_AVAILABLE: + return False + + # 检查对象是否有SUWood属性 + return ( + obj and + hasattr(obj, 'get') and + obj.get("uid") is not None + ) + + def _has_order_id(self) -> bool: + """检查是否有订单ID""" + if not BLENDER_AVAILABLE: + return False + + scene = bpy.context.scene + return scene.get("order_id") is not None + + def _clear_selection(self): + """清除选择""" + if BLENDER_AVAILABLE: + bpy.ops.object.select_all(action='DESELECT') + +class SUWModelObserver: + """模型观察者类 - 监听模型事件""" + + def on_save_model(self, context): + """当模型保存时调用""" + try: + from .suw_client import set_cmd + from .suw_constants import SUWood + + if not BLENDER_AVAILABLE: + return + + scene = bpy.context.scene + order_id = scene.get("order_id") + + if order_id is None: + return + + params = { + "method": SUWood.SUSceneSave, + "order_id": order_id + } + set_cmd("r00", params) + + except ImportError: + print("模型保存事件") + +class SUWAppObserver: + """应用观察者类 - 监听应用级事件""" + + def __init__(self): + self.tools_observer = SUWToolsObserver() + self.selection_observer = SUWSelectionObserver() + self.model_observer = SUWModelObserver() + + def on_new_model(self, context): + """当新建模型时调用""" + try: + from .suw_impl import SUWImpl + from .suw_client import set_cmd + from .suw_constants import SUWood + + SUWImpl.get_instance().startup() + + # 注册观察者 + self._register_observers() + + params = { + "method": SUWood.SUSceneNew + } + set_cmd("r00", params) + + except ImportError: + print("新建模型事件") + + def on_open_model(self, context, filepath: str): + """当打开模型时调用""" + try: + from .suw_impl import SUWImpl + from .suw_client import set_cmd + from .suw_constants import SUWood + + SUWImpl.get_instance().startup() + + # 注册观察者 + self._register_observers() + + if not BLENDER_AVAILABLE: + return + + scene = bpy.context.scene + order_id = scene.get("order_id") + + # 如果有订单ID,清除相关实体 + if order_id is not None: + self._clear_suw_entities() + + params = { + "method": SUWood.SUSceneOpen + } + if order_id is not None: + params["order_id"] = order_id + + set_cmd("r00", params) + + except ImportError: + print(f"打开模型事件: {filepath}") + + def _register_observers(self): + """注册观察者""" + if BLENDER_AVAILABLE: + # 在Blender中注册相关的处理器 + self._register_handlers() + + def _register_handlers(self): + """注册Blender处理器""" + if not BLENDER_AVAILABLE: + return + + # 注册保存处理器 + if self._save_handler not in bpy.app.handlers.save_pre: + bpy.app.handlers.save_pre.append(self._save_handler) + + # 注册加载处理器 + if self._load_handler not in bpy.app.handlers.load_post: + bpy.app.handlers.load_post.append(self._load_handler) + + @persistent + def _save_handler(self, context): + """保存处理器""" + self.model_observer.on_save_model(context) + + @persistent + def _load_handler(self, context): + """加载处理器""" + filepath = bpy.data.filepath + self.on_open_model(context, filepath) + + def _clear_suw_entities(self): + """清除SUWood实体""" + if not BLENDER_AVAILABLE: + return + + scene = bpy.context.scene + objects_to_delete = [] + + for obj in scene.objects: + if obj.get("uid") is not None: + objects_to_delete.append(obj) + + # 删除对象 + for obj in objects_to_delete: + bpy.data.objects.remove(obj, do_unlink=True) + +# 全局观察者实例 +_app_observer = None + +def get_app_observer(): + """获取应用观察者实例""" + global _app_observer + if _app_observer is None: + _app_observer = SUWAppObserver() + return _app_observer + +def register_observers(): + """注册所有观察者""" + observer = get_app_observer() + observer._register_observers() + print("✅ SUWood 观察者已注册") + +def unregister_observers(): + """注销所有观察者""" + if not BLENDER_AVAILABLE: + return + + observer = get_app_observer() + + # 移除处理器 + try: + if observer._save_handler in bpy.app.handlers.save_pre: + bpy.app.handlers.save_pre.remove(observer._save_handler) + + if observer._load_handler in bpy.app.handlers.load_post: + bpy.app.handlers.load_post.remove(observer._load_handler) + + print("✅ SUWood 观察者已注销") + + except Exception as e: + print(f"❌ 注销观察者时出错: {e}") + +if __name__ == "__main__": + print("🚀 SUW观察者测试") + + if BLENDER_AVAILABLE: + print("Blender API 可用,注册观察者...") + register_observers() + else: + print("Blender API 不可用,创建观察者实例进行测试...") + observer = get_app_observer() + print(f"观察者创建成功: {observer.__class__.__name__}") \ No newline at end of file diff --git a/blenderpython/suw_unit_cont_tool.py b/blenderpython/suw_unit_cont_tool.py new file mode 100644 index 0000000..5bf1acf --- /dev/null +++ b/blenderpython/suw_unit_cont_tool.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Unit Contour Tool - Python存根版本 +原文件: SUWUnitContTool.rb +用途: 轮廓工具 +""" + +class SUWUnitContTool: + """SUWood轮廓工具 - 存根版本""" + + def __init__(self): + print("🔧 创建轮廓工具") + +print("📝 SUWUnitContTool存根版本已加载") \ No newline at end of file diff --git a/blenderpython/suw_unit_face_tool.py b/blenderpython/suw_unit_face_tool.py new file mode 100644 index 0000000..c618825 --- /dev/null +++ b/blenderpython/suw_unit_face_tool.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Unit Face Tool - Python存根版本 +原文件: SUWUnitFaceTool.rb +用途: 面工具,用于在面上创建单元 +""" + +class SUWUnitFaceTool: + """SUWood面工具 - 存根版本""" + + def __init__(self, spatial_pos: int, uid: str, mold: int): + self.spatial_pos = spatial_pos + self.uid = uid + self.mold = mold + print(f"🔧 创建面工具: 位置 {spatial_pos}, UID: {uid}") + +print("📝 SUWUnitFaceTool存根版本已加载") \ No newline at end of file diff --git a/blenderpython/suw_unit_point_tool.py b/blenderpython/suw_unit_point_tool.py new file mode 100644 index 0000000..210ecf6 --- /dev/null +++ b/blenderpython/suw_unit_point_tool.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Unit Point Tool - Python存根版本 +原文件: SUWUnitPointTool.rb +用途: 点工具,用于创建单元 + +注意: 这是存根版本,需要进一步翻译完整的Ruby代码 +""" + +class SUWUnitPointTool: + """SUWood点工具 - 存根版本""" + + def __init__(self, width: float, depth: float, height: float, uid: str, mold: int): + """初始化点工具""" + self.width = width + self.depth = depth + self.height = height + self.uid = uid + self.mold = mold + print(f"🔧 创建点工具: {width}x{depth}x{height}, UID: {uid}") + + def activate(self): + """激活工具""" + print("⚡ 激活点工具") + + def on_mouse_down(self, x: float, y: float, z: float): + """鼠标按下事件""" + print(f"🖱️ 点击位置: ({x}, {y}, {z})") + + def create_unit(self, position): + """在指定位置创建单元""" + print(f"📦 创建单元: 位置 {position}, 尺寸 {self.width}x{self.depth}x{self.height}") + +print("📝 SUWUnitPointTool存根版本已加载") \ No newline at end of file diff --git a/blenderpython/suw_zone_div1_tool.py b/blenderpython/suw_zone_div1_tool.py new file mode 100644 index 0000000..407f58b --- /dev/null +++ b/blenderpython/suw_zone_div1_tool.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SUW Zone Division Tool - Python存根版本 +原文件: SUWZoneDiv1Tool.rb +用途: 区域分割工具 +""" + +class SUWZoneDiv1Tool: + """SUWood区域分割工具 - 存根版本""" + + def __init__(self): + print("🔧 创建区域分割工具") + +print("📝 SUWZoneDiv1Tool存根版本已加载") \ No newline at end of file