diff --git a/client.py b/client.py
index f622e78..13697fc 100644
--- a/client.py
+++ b/client.py
@@ -1,8 +1,17 @@
import socket
import json
import time
+import sys
from datetime import datetime
+# 设置标准输出编码为 UTF-8(解决 Windows 控制台编码问题)
+if sys.platform.startswith('win'):
+ import codecs
+ sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
+ sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach())
+ # 设置标准输入编码
+ sys.stdin = codecs.getreader('utf-8')(sys.stdin.detach())
+
class JSONSocketClient:
def __init__(self, host='localhost', port=8888):
self.host = host
diff --git a/encoding_fix.py b/encoding_fix.py
new file mode 100644
index 0000000..c5e686f
--- /dev/null
+++ b/encoding_fix.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Windows 控制台编码修复模块
+用于解决 Windows 下 Unicode 字符显示问题
+"""
+
+import sys
+import os
+
+def setup_console_encoding():
+ """设置控制台编码为 UTF-8"""
+ if sys.platform.startswith('win'):
+ try:
+ # 方法1: 设置环境变量
+ os.environ['PYTHONIOENCODING'] = 'utf-8'
+
+ # 方法2: 重新配置标准流
+ import codecs
+ import io
+
+ # 检查是否已经是正确的编码
+ if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding.lower() not in ['utf-8', 'utf8']:
+ # 只有在需要时才重新配置
+ if hasattr(sys.stdout, 'detach'):
+ sys.stdout = io.TextIOWrapper(
+ sys.stdout.detach(),
+ encoding='utf-8',
+ errors='replace'
+ )
+ sys.stderr = io.TextIOWrapper(
+ sys.stderr.detach(),
+ encoding='utf-8',
+ errors='replace'
+ )
+ if hasattr(sys.stdin, 'detach'):
+ sys.stdin = io.TextIOWrapper(
+ sys.stdin.detach(),
+ encoding='utf-8',
+ errors='replace'
+ )
+
+ return True
+
+ except Exception as e:
+ print(f"Warning: Could not set UTF-8 encoding: {e}")
+ return False
+ return True
+
+def safe_print(text, fallback_text=None):
+ """安全打印,如果遇到编码错误则使用备用文本"""
+ try:
+ print(text)
+ except UnicodeEncodeError:
+ if fallback_text:
+ try:
+ print(fallback_text)
+ except UnicodeEncodeError:
+ print("Message with special characters (encoding issue)")
+ else:
+ # 移除非ASCII字符的简单版本
+ ascii_text = text.encode('ascii', errors='ignore').decode('ascii')
+ print(ascii_text if ascii_text.strip() else "Message (encoding issue)")
+
+def get_safe_emoji_text(emoji_text, fallback_text):
+ """获取安全的emoji文本,如果不支持则返回备用文本"""
+ try:
+ # 测试是否能编码
+ emoji_text.encode(sys.stdout.encoding or 'utf-8')
+ return emoji_text
+ except (UnicodeEncodeError, LookupError):
+ return fallback_text
+
+# 预定义的安全文本映射
+EMOJI_FALLBACKS = {
+ "🚀": "[START]",
+ "📍": "[ADDR]",
+ "⏰": "[TIME]",
+ "✅": "[OK]",
+ "🔗": "[CONN]",
+ "📨": "[MSG]",
+ "❌": "[ERROR]",
+ "🔌": "[DISC]",
+ "📋": "[LIST]",
+ "🖥️": "[SERVER]",
+ "💻": "[CLIENT]",
+ "📁": "[FILE]",
+ "🎯": "[RESULT]",
+ "⚠️": "[WARN]",
+ "📊": "[REPORT]",
+ "🧪": "[TEST]",
+ "🎮": "[INTERACTIVE]",
+ "📤": "[SEND]",
+ "📥": "[RECV]",
+ "👉": "[INPUT]",
+ "👋": "[EXIT]",
+ "🎉": "[SUCCESS]",
+ "🔧": "[FIX]",
+ "⛔": "[STOP]",
+ "💡": "[TIP]"
+}
+
+def safe_format_message(message):
+ """将消息中的emoji替换为安全的文本"""
+ for emoji, fallback in EMOJI_FALLBACKS.items():
+ message = message.replace(emoji, fallback)
+ return message
+
+# 自动设置编码
+if __name__ != "__main__":
+ setup_console_encoding()
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..66aef72
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,26 @@
+# Socket JSON 传输系统依赖包列表
+#
+# 本项目使用 Python 标准库,无需安装额外包
+# Python 版本要求: >= 3.6
+#
+# 如果您使用的是 Python 3.6+,所有功能都应该正常工作
+# 如果遇到编码问题,请确保使用支持 UTF-8 的终端
+
+# 标准库依赖(无需安装):
+# - socket: 网络通信
+# - json: JSON 数据处理
+# - threading: 多线程支持
+# - subprocess: 进程管理
+# - datetime: 时间处理
+# - os: 操作系统接口
+# - sys: 系统相关功能
+# - time: 时间功能
+# - codecs: 编码处理
+
+# 安装方法(如果需要):
+# pip install -r requirements.txt
+
+# 注意: 如果您在 Windows 上遇到编码问题,请:
+# 1. 使用 Windows Terminal 或 VS Code 终端
+# 2. 或在 PowerShell/CMD 中运行: chcp 65001
+# 3. 确保终端支持 UTF-8 编码
\ No newline at end of file
diff --git a/ruby.rar b/ruby.rar
new file mode 100644
index 0000000..60d1c9c
Binary files /dev/null and b/ruby.rar differ
diff --git a/ruby/ruby/SUWClient.rb b/ruby/ruby/SUWClient.rb
new file mode 100644
index 0000000..f9f2398
--- /dev/null
+++ b/ruby/ruby/SUWClient.rb
@@ -0,0 +1,118 @@
+require "socket"
+require "json"
+
+Sketchup::require "SUWImpl"
+
+TCP_SERVER_PORT =7999 unless defined?(TCP_SERVER_PORT)
+OP_CMD_REQ_GETCMDS =0x01 unless defined?(OP_CMD_REQ_GETCMDS)
+OP_CMD_REQ_SETCMD =0x03 unless defined?(OP_CMD_REQ_SETCMD)
+OP_CMD_RES_GETCMDS =0x02 unless defined?(OP_CMD_RES_GETCMDS)
+OP_CMD_RES_SETCMD =0x04 unless defined?(OP_CMD_RES_SETCMD)
+class SUWclient
+ @@sock =TCPSocket.new("127.0.0.1", TCP_SERVER_PORT)
+ @@seqno=0
+
+ def self.reconnect
+ @@sock.close
+ @@sock=TCPSocket.new("127.0.0.1", TCP_SERVER_PORT)
+ end
+
+ def self.sendmsg(cmd,msg)
+ opcode=(cmd&0xffff)|(0x01010000)
+ @@seqno = @@seqno + 1
+ m=[msg.size(),opcode,@@seqno,0].pack("i*")+msg
+ @@sock.write(m)
+ end
+
+ def self.recvmsg
+ len=@@sock.recv(16).unpack("i*")[0]
+
+ msg=""
+ to_recv_len=len
+ while to_recv_len>0
+ msg+= @@sock.recv(to_recv_len)
+ to_recv_len=len-msg.size()
+ end
+ msg
+ end
+end
+
+def get_cmds
+ msg="{\"cmd\":\"get_cmds\",\"params\":{\"from\":\"su\"}}"
+ begin
+ SUWclient.sendmsg(OP_CMD_REQ_GETCMDS,msg)
+ res=SUWclient.recvmsg
+ cmds=[]
+ res_hash=JSON.parse(res)
+
+ if res_hash['ret'] == 1
+ cmds =res_hash['data'].fetch('cmds',[])
+ end
+
+ rescue => e
+ puts "========= get_cmds err is: ========="
+ puts e
+ puts "========= get_cmds res is: ========="
+ puts res
+ SUWclient.reconnect
+ end
+
+ cmds
+end
+
+def set_cmd(cmd, params)
+ cmds = {}
+ cmds.store("cmd", "set_cmd")
+ cmds.store("params", params)
+ params.store("from", "su")
+ params.store("cmd", cmd)
+ msg = JSON.generate(cmds)
+ begin
+ SUWclient.sendmsg(OP_CMD_REQ_SETCMD, msg)
+ SUWclient.recvmsg
+ rescue => e
+ puts e
+ SUWclient.reconnect
+ end
+end
+
+@cmdsqueue=[]
+@pause=0
+$tid = UI.start_timer(1, true){
+ if @pause > 0
+ @pause = @pause -1
+ else
+ swcmds0 = get_cmds
+ swcmds = @cmdsqueue + swcmds0
+ @cmdsqueue.clear
+ #Sketchup.send_action "showRubyPanel:"
+ swcmds.each{|swcmd|
+ data = swcmd.fetch("data")
+ if data.is_a?(String)
+ eval(data)
+ elsif data.is_a?(Hash) && data.key?("cmd")
+ cmd = data.fetch("cmd")
+puts data
+ if @pause > 0
+ @cmdsqueue << swcmd
+ elsif cmd.start_with? "pause_"
+ @pause= data.fetch("value", 1)
+ else
+ pre_pause_time = data.fetch("pre_pause",0)
+ if pre_pause_time > 0
+ data.delete("pre_pause")
+ swcmd.store("data",data)
+ @pause = pre_pause_time
+ @cmdsqueue<< swcmd
+ else
+ eval("SUWood::SUWimpl.instance.#{cmd}(data)")
+ after_pause_time=data.fetch("after_pause",0)
+ if after_pause_time > 0
+ @pause = after_pause_time
+ end
+ end
+ end
+ end
+ }
+ end
+}
\ No newline at end of file
diff --git a/ruby/ruby/SUWConstants.rb b/ruby/ruby/SUWConstants.rb
new file mode 100644
index 0000000..4acc18c
--- /dev/null
+++ b/ruby/ruby/SUWConstants.rb
@@ -0,0 +1,305 @@
+require 'pathname'
+
+module 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 = File.dirname(__FILE__)
+
+ module_function
+ def icon_path(icon_name, ext = 'png')
+ "#{PATH}/icon/#{icon_name}.#{ext}"
+ end
+
+ def unit_path()
+ "#{SUWimpl.server_path}/drawings/Unit"
+ end
+
+ def suwood_path(ref_v)
+ case ref_v
+ when V_Material
+ "#{SUWimpl.server_path}/images/texture"
+ when V_StretchPart
+ "#{SUWimpl.server_path}/drawings/StretchPart"
+ when V_StructPart
+ "#{SUWimpl.server_path}/drawings/StructPart"
+ when V_Unit
+ "#{SUWimpl.server_path}/drawings/Unit"
+ when V_Connection
+ "#{SUWimpl.server_path}/drawings/Connection"
+ when V_HardwareSet
+ "#{SUWimpl.server_path}/drawings/HardwareSet"
+ when V_Hardware
+ "#{SUWimpl.server_path}/drawings/Hardware"
+ else
+ "#{SUWimpl.server_path}"
+ end
+ end
+
+ def suwood_pull_size(pos)
+ case pos
+ when 1 then "HW" #右上
+ when 2 then "W" #右中
+ when 3 then "HW" #右下
+ when 4 then "H" #中上
+ when 6 then "H" #中下
+ when 11 then "HW" #右上-竖
+ when 12 then "W" #右中-竖
+ when 13 then "HW" #右下-竖
+ when 14 then "H" #中上-竖
+ when 16 then "H" #中下-竖
+ when 21 then "HW" #右上-横
+ when 22 then "W" #右中-横
+ when 23 then "HW" #右下-横
+ when 24 then "H" #中上-横
+ when 26 then "H" #中下-横
+ else nil
+ end
+ end
+
+ def scene_save()
+ at_model = Sketchup.active_model
+ order_id = at_model.get_attribute("sw", "order_id", nil)
+ return if order_id.nil?
+
+ data = {}
+ data.store("method", SUSceneSave)
+ data.store("order_id", order_id)
+ set_cmd("r00", data)
+
+ if at_model.path.empty?
+ scene_path = Pathname.new("#{SUWimpl.server_path}/sketchup").expand_path
+ unless Dir.exist?(scene_path)
+ Dir.mkdir(scene_path)
+ end
+ order_code = at_model.get_attribute("sw", "order_code")
+ at_model.save("#{scene_path}/#{order_code}.skp")
+ else
+ at_model.save
+ end
+ end
+
+ def scene_price()
+ at_model = Sketchup.active_model
+ order_id = at_model.get_attribute("sw", "order_id", nil)
+ return if order_id.nil?
+
+ params = {}
+ params.store("method", SUScenePrice)
+ params.store("order_id", order_id)
+ set_cmd("r00", params)
+ end
+
+ def import_unit(uid, values, mold)#点击创体(产品UID)
+ Sketchup.active_model.select_tool(SUWUnitPointTool.new(values["width"].mm, values["depth"].mm, values["height"].mm, uid, mold))
+ end
+
+ def import_face(uid, values, mold)#选面创体(产品UID)
+ Sketchup.active_model.select_tool(SUWUnitFaceTool.new(VSSpatialPos_F, uid, mold))
+ end
+
+ def front_view()
+ uid = SUWimpl.selected_uid
+ obj = SUWimpl.selected_obj
+ if uid.nil? || obj.nil?
+ UI.messagebox("请先选择正视于的基准面!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneFront)
+ params.store("uid", uid)
+ params.store("oid", obj)
+ set_cmd("r00", params)
+ end
+
+ def delete_unit()
+ order_id = Sketchup.active_model.get_attribute("sw", "order_id", nil)
+ uid = SUWimpl.selected_uid
+ obj = SUWimpl.selected_obj
+ if uid.nil?
+ UI.messagebox("请先选择待删除的柜体!")
+ return
+ elsif order_id.nil?
+ UI.messagebox("当前柜体不是场景方案的柜体!")
+ return
+ elsif UI.messagebox("是否确定删除当前选择的柜体?", MB_YESNO) == IDNO
+ return
+ end
+
+ params = {}
+ params.store("method", SUUnitDelete)
+ params.store("order_id", order_id)
+ params.store("uid", uid)
+ params.store("oid", obj) if obj
+ set_cmd("r00", params)
+ end
+
+ def combine_unit(uid, values, mold)#模块拼接
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择待拼接的空区域!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneCombine)
+ params.store("uid", selected_zone.get_attribute("sw","uid"))
+ params.store("zid", selected_zone.get_attribute("sw","zid"))
+ params.store("source", uid)
+ params.store("module", mold) if mold
+ set_cmd("r00", params)
+ end
+
+ def replace_unit(uid, values, mold)#模块/产品替换
+ if SUWimpl.selected_zone.nil? && (mold == 1 || mold == 2)
+ UI.messagebox("请先选择待替换的区域!"); return
+ elsif SUWimpl.selected_obj.nil? && (mold == 3)
+ UI.messagebox("请先选择待替换的部件!"); return
+ end
+
+ params = {}
+ params.store("method", SUZoneReplace)
+ params.store("source", uid)
+ params.store("module", mold)
+ set_cmd("r00", params)
+ end
+
+ def replace_mat(uid, values, type)#材料替换
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择待替换材料的区域!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneMaterial)
+ params.store("mat_id", uid)
+ params.store("type", type)
+ set_cmd("r00", params)
+ end
+
+ def replace_handle(width, height, set_id, conn_id)
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择待替换拉手的区域!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneHandle)
+ params.store("uid", selected_zone.get_attribute("sw","uid"))
+ params.store("zid", selected_zone.get_attribute("sw","zid"))
+ params.store("conn_id", conn_id)
+ params.store("set_id", set_id)
+ params.store("width", width.to_i) unless width.nil? || width == ""
+ params.store("width", height.to_i) unless height.nil? || height == ""
+ set_cmd("r00", params)
+ end
+
+ def clear_current(ref_v)
+ if (ref_v == 2102 || ref_v == 2103) && SUWimpl.selected_zone
+ params = {}#没有柜体/区域参数, 切换到订单编辑界面
+ params.store("uid", SUWimpl.selected_uid)
+ set_cmd("r01", params)
+ SUWimpl.instance.sel_clear
+ end
+ end
+
+ #挂衣杆替换
+ def replace_clothes(front, back, set_id, conn_id)
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择待替换衣杆的区域!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneCloth)
+ params.store("uid", selected_zone.get_attribute("sw","uid"))
+ params.store("zid", selected_zone.get_attribute("sw","zid"))
+ params.store("conn_id", conn_id)
+ params.store("set_id", set_id)
+ params.store("front", front) unless front == 0
+ params.store("back", back) unless back == 0
+ set_cmd("r00", params)
+ end
+
+ #灯带替换
+ def replace_lights(front, back, set_id, conn_id)
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择待替换灯带的区域!")
+ return
+ end
+
+ conns = conn_id.class == Array ? conn_id.join(",") : conn_id
+ params = {}
+ params.store("method", SUZoneLight)
+ params.store("uid", selected_zone.get_attribute("sw","uid"))
+ params.store("zid", selected_zone.get_attribute("sw","zid"))
+ params.store("conn_id", conns)
+ params.store("set_id", set_id)
+ params.store("front", front) unless front == 0
+ params.store("back", back) unless back == 0
+ set_cmd("r00", params)
+ end
+end
diff --git a/ruby/ruby/SUWImpl.rb b/ruby/ruby/SUWImpl.rb
new file mode 100644
index 0000000..f0c3b72
--- /dev/null
+++ b/ruby/ruby/SUWImpl.rb
@@ -0,0 +1,2019 @@
+require "singleton"
+
+module Geom
+ class Point3d
+ def self.parse(value)
+ if value.nil? || value.strip == "" then nil
+ else
+ xyz = value.gsub(/\(*\)*/, "").split(",").collect{|axis| axis.strip.to_f}
+ Point3d.new(xyz[0].mm, xyz[1].mm, xyz[2].mm)
+ end
+ end
+
+ def to_s(unit="mm", digits = -1)
+ if unit=="cm"
+ "(#{x.to_f.to_cm.round(3)}, #{y.to_f.to_cm.round(3)}, #{z.to_f.to_cm.round(3)})"
+ elsif digits == -1
+ "(#{x.to_f.to_mm}, #{y.to_f.to_mm}, #{z.to_f.to_mm})"
+ else
+ "(#{x.to_f.to_mm.round(digits)}, #{y.to_f.to_mm.round(digits)}, #{z.to_f.to_mm.round(digits)})"
+ end
+ end
+ # def to_s(); "(#{x.to_f.decimal(1)}, #{y.to_f.decimal(1)}, #{z.to_f.decimal(1)})" end
+ end
+
+ class Vector3d
+ def self.parse(value)
+ if value.nil? || value.strip == "" then nil
+ else
+ xyz = value.gsub(/\(*\)*/, "").split(",").collect{|axis| axis.strip.to_f}
+ Vector3d.new(xyz[0].mm, xyz[1].mm, xyz[2].mm)
+ end
+ end
+ def to_s(unit="mm")
+ if unit=="cm"
+ "(#{x.to_f.to_cm.round(3)}, #{y.to_f.to_cm.round(3)}, #{z.to_f.to_cm.round(3)})"
+ elsif unit =="in"
+ "(#{x.to_f}, #{y.to_f}, #{z.to_f})"
+ else
+ "(#{x.to_f.to_mm}, #{y.to_f.to_mm}, #{z.to_f.to_mm})"
+ end
+ end
+ end
+
+ class Transformation
+ def self.parse(data)
+ o = Geom::Point3d.parse(data.fetch("o"))
+ x = Geom::Vector3d.parse(data.fetch("x"))
+ y = Geom::Vector3d.parse(data.fetch("y"))
+ z = Geom::Vector3d.parse(data.fetch("z"))
+ Geom::Transformation.new(x, y, z, o)
+ end
+
+ def store(data)
+ data.store("o", origin.to_s("mm"))
+ data.store("x", xaxis.to_s("in"))
+ data.store("y", yaxis.to_s("in"))
+ data.store("z", zaxis.to_s("in"))
+ end
+ end
+end
+
+module Sketchup
+ class Face
+ def to_json(trans_arr = nil, digits = -1, arced = true)
+ edges = outer_loop.edges.collect{|edge|
+ edge.curve.is_a?(Sketchup::ArcCurve) && arced ? edge.curve : edge
+ }.uniq
+
+ if edges.empty?
+ {}#返回值
+ else
+ zaxis = normal
+ segs = edges.collect{|object|
+ seg = Hash.new
+ p1 = object.is_a?(Sketchup::Edge) ? object.start.position : object.first_edge.start.position
+ p2 = object.is_a?(Sketchup::Edge) ? object.end.position : object.last_edge.end.position
+ c = object.is_a?(Sketchup::Edge) ? nil : object.center
+ if trans_arr
+ trans_arr.each{|trans|
+ p1.transform!(trans)
+ p2.transform!(trans)
+ c.transform!(trans) if c
+ }
+ end
+ seg.store("s", p1.to_s("mm", digits))
+ seg.store("e", p2.to_s("mm", digits))
+ if c
+ seg.store("c", c.to_s("mm", digits))
+ seg.store("a1", object.start_angle.to_f)
+ seg.store("a2", object.end_angle.to_f)
+ end
+ seg
+ }
+
+ if trans_arr
+ trans_arr.each{|trans|
+ zaxis.transform!(trans)
+ }
+ end
+ {:zaxis => zaxis.normalize.to_s("in"), :segs => segs}#返回值
+ end
+ end
+ end
+end
+
+module SUWood
+ MAT_TYPE_NORMAL = 0 unless defined?(MAT_TYPE_NORMAL)
+ MAT_TYPE_OBVERSE = 1 unless defined?(MAT_TYPE_OBVERSE)
+ MAT_TYPE_NATURE = 2 unless defined?(MAT_TYPE_NATURE)
+
+ class SUWimpl
+ include Singleton
+
+ @@selected_uid = nil
+ @@selected_obj = nil#zid/pid/cp
+ @@selected_zone = nil
+ @@selected_part = nil
+ @@scaled_zone = nil
+ @@server_path = nil
+ attr_accessor :added_contour
+
+ def startup()
+ @door_layer = Sketchup.active_model.layers.add("DOOR_LAYER") #门板开向线的图层
+ @drawer_layer = Sketchup.active_model.layers.add("DRAWER_LAYER")
+ @door_layer.visible = true
+ @drawer_layer.visible = true
+
+ @added_contour = false
+
+ @textures = {}
+ add_mat_rgb("mat_normal", 0.1, 128, 128, 128)#Gray
+ add_mat_rgb("mat_select", 0.5, 255, 0, 0) #Red
+ add_mat_rgb("mat_default", 0.9, 255, 250, 250)
+ add_mat_rgb("mat_obverse", 1.0, 3, 70, 24) #Green
+ add_mat_rgb("mat_reverse", 1.0, 249, 247, 174)#Yellow
+ add_mat_rgb("mat_thin", 1.0, 248, 137, 239)#Pink purple
+ add_mat_rgb("mat_machine", 1.0, 0, 0, 255)#Blue
+
+ @unit_param = {}#key uid, params such as w/d/h/order_id
+ @unit_trans = {}#key uid, trans of unit
+
+ @zones = {}#key uid/oid
+ @parts = {}#key uid/cp, second key is component root oid
+ @hardwares = {}#key uid/cp, second key is hardware root oid
+ @machinings = {}#key uid, array, child entity of part or hardware
+ @dimensions = {}#key uid, array
+ @labels = Sketchup.active_model.entities.add_group
+ @door_labels= Sketchup.active_model.entities.add_group
+
+ @part_mode = false
+ @hide_none = false
+ @mat_type = MAT_TYPE_NORMAL
+
+ @selected_faces = []
+ @selected_parts = []
+ @selected_hws = []
+ @menu_handle = 0
+ @back_material = false
+
+ default_surfs = [{"f"=>1, "p"=>1, "segs"=>[["(0,0,1000)", "(0,0,0)"], ["(0,0,0)", "(1000,0,0)"], ["(1000,0,0)", "(1000,0,1000)"], ["(1000,0,1000)", "(0,0,1000)"]], "vx"=>"(0,0,-1)", "vz"=>"(0,-1,0)"},
+ {"f"=>4, "p"=>4, "segs"=>[["(1000,0,1000)", "(1000,0,0)"], ["(1000,0,0)", "(1000,1000,0)"], ["(1000,1000,0)", "(1000,1000,1000)"], ["(1000,1000,1000)", "(1000,0,1000)"]], "vx"=>"(0,0,-1)", "vz"=>"(1,0,0)"},
+ {"f"=>2, "p"=>2, "segs"=>[["(0,1000,1000)", "(0,1000,0)"], ["(0,1000,0)", "(1000,1000,0)"], ["(1000,1000,0)", "(1000,1000,1000)"], ["(1000,1000,1000)", "(0,1000,1000)"]], "vx"=>"(0,0,-1)", "vz"=>"(0,-1,0)"},
+ {"f"=>3, "p"=>3, "segs"=>[["(0,0,1000)", "(0,0,0)"], ["(0,0,0)", "(0,1000,0)"], ["(0,1000,0)", "(0,1000,1000)"], ["(0,1000,1000)", "(0,0,1000)"]], "vx"=>"(0,0,-1)", "vz"=>"(1,0,0)"},
+ {"f"=>5, "p"=>5, "segs"=>[["(0,0,0)", "(1000,0,0)"], ["(1000,0,0)", "(1000,1000,0)"], ["(1000,1000,0)", "(0,1000,0)"], ["(0,1000,0)", "(0,0,0)"]], "vx"=>"(1,0,0)", "vz"=>"(0,0,1)"},
+ {"f"=>6, "p"=>6, "segs"=>[["(0,0,1000)", "(1000,0,1000)"], ["(1000,0,1000)", "(1000,1000,1000)"], ["(1000,1000,1000)", "(0,1000,1000)"], ["(0,1000,1000)", "(0,0,1000)"]], "vx"=>"(1,0,0)", "vz"=>"(0,0,1)"}]
+ @@default_zone = Sketchup.active_model.entities.add_group
+ default_surfs.each{|surf|
+ face = create_face(@@default_zone, surf)
+ face.set_attribute("sw", "p", surf.fetch("p"))
+ }
+ @@default_zone.visible = false
+
+ data = {"children"=>[{"child"=>7468, "surf"=>{"f"=>1, "p"=>1, "segs"=>[["(600,0,2400)", "(600,0,50)"], ["(600,0,50)", "(600,1932,50)"], ["(600,1932,50)", "(600,1932,2400)"], ["(600,1932,2400)", "(600,0,2400)"]], "vx"=>"(0,0,-1)", "vz"=>"(1,0,0)"}},
+ {"child"=>7469, "surf"=>{"f"=>4, "p"=>4, "segs"=>[["(600,1932,2400)", "(600,1932,50)"], ["(600,1932,50)", "(0,1932,50)"], ["(0,1932,50)", "(0,1932,2400)"], ["(0,1932,2400)", "(600,1932,2400)"]], "vx"=>"(0,0,-1)", "vz"=>"(0,1,0)"}},
+ {"child"=>7470, "surf"=>{"f"=>2, "p"=>2, "segs"=>[["(0,0,2400)", "(0,0,50)"], ["(0,0,50)", "(0,1932,50)"], ["(0,1932,50)", "(0,1932,2400)"], ["(0,1932,2400)", "(0,0,2400)"]], "vx"=>"(0,0,-1)", "vz"=>"(1,0,0)"}},
+ {"child"=>7471, "surf"=>{"f"=>3, "p"=>3, "segs"=>[["(600,0,2400)", "(600,0,50)"], ["(600,0,50)", "(0,0,50)"], ["(0,0,50)", "(0,0,2400)"], ["(0,0,2400)", "(600,0,2400)"]], "vx"=>"(0,0,-1)", "vz"=>"(0,1,0)"}},
+ {"child"=>7472, "surf"=>{"f"=>5, "p"=>5, "segs"=>[["(600,0,50)", "(600,1932,50)"], ["(600,1932,50)", "(0,1932,50)"], ["(0,1932,50)", "(0,0,50)"], ["(0,0,50)", "(600,0,50)"]], "vx"=>"(0,1,0)", "vz"=>"(0,0,1)"}},
+ {"child"=>7473, "surf"=>{"f"=>6, "p"=>6, "segs"=>[["(600,0,2400)", "(600,1932,2400)"], ["(600,1932,2400)", "(0,1932,2400)"], ["(0,1932,2400)", "(0,0,2400)"], ["(0,0,2400)", "(600,0,2400)"]], "vx"=>"(0,1,0)", "vz"=>"(0,0,1)"}}],
+ "cmd"=>"c03", "depth"=>600, "height"=>2350,
+ "t_point"=>{"o"=>"(0,0,0)", "x"=>"(1,0,0)", "y"=>"(0,1,0)", "z"=>"(0,0,1)"},
+ "t_scale"=>{"o"=>"(0,0,0)", "x"=>"(1.932,0,0)", "y"=>"(0,0.6,0)", "z"=>"(0,0,2.35)"},
+ "trans"=>{"o"=>"(600,0,50)", "x"=>"(0,1.932,0)", "y"=>"(-0.6,0,0)", "z"=>"(0,0,2.35)"},
+ "uid"=>"426c2d06c000000e",
+ "v_trans"=>{"o"=>"(600,0,50)", "x"=>"(0,1,0)", "y"=>"(-1,0,0)", "z"=>"(0,0,1)"},
+ "width"=>1932, "zid"=>6147, "zip"=>1166}
+ end
+
+ def add_mat_rgb(id, alpha, r, g, b)
+ mat = Sketchup.active_model.materials.add
+ mat.color = Sketchup::Color.new(r, g, b)
+ mat.alpha = alpha
+ @textures.store(id, mat)
+ end
+
+ def get_zones(data)
+ uid = data.fetch("uid")
+ unless @zones.key?(uid)
+ @zones.store(uid, Hash.new)
+ end
+ @zones.fetch(uid)
+ end
+
+ def get_parts(data)
+ uid = data.fetch("uid")
+ unless @parts.key?(uid)
+ @parts.store(uid, Hash.new)
+ end
+ @parts.fetch(uid)
+ end
+
+ def get_hardwares(data)
+ uid = data.fetch("uid")
+ unless @hardwares.key?(uid)
+ @hardwares.store(uid, Hash.new)
+ end
+ @hardwares.fetch(uid)
+ end
+
+ def c11(data) #part_obverse
+ @mat_type = data.fetch("v", false) ? MAT_TYPE_OBVERSE : MAT_TYPE_NORMAL
+ parts = get_parts(data)
+ parts.each{|root, part|
+ textured_part(part, false) unless part.nil? || !part.valid? || @selected_parts.include?(part)
+ }
+ end
+
+ def c30(data) #part_nature
+ @mat_type = data.fetch("v", false) ? MAT_TYPE_NATURE : MAT_TYPE_NORMAL
+ parts = get_parts(data)
+ parts.each{|root, part|
+ textured_part(part, false) unless part.nil? || !part.valid? || @selected_parts.include?(part)
+ }
+ end
+
+ def set_config(data)
+ if data.key?("server_path")
+ @@server_path = data.fetch("server_path")
+ end
+ if data.key?("order_id")
+ Sketchup.active_model.set_attribute("sw", "order_id", data.fetch("order_id"))
+ end
+ if data.key?("order_code")
+ Sketchup.active_model.set_attribute("sw", "order_code", data.fetch("order_code"))
+ end
+ if data.key?("back_material")
+ @back_material = data.fetch("back_material")
+ end
+ if data.key?("part_mode")
+ @part_mode = data.fetch("part_mode")
+ end
+ if data.key?("hide_none")
+ @hide_none = data.fetch("hide_none")
+ end
+ if data.key?("unit_drawing")
+ puts "#{data.fetch("drawing_name")}:\t#{data.fetch("unit_drawing")}"
+ end
+ if data.key?("zone_corner")
+ zones = get_zones(data)
+ zone = zones.fetch(data.fetch("zid"), nil)
+ if zone
+ zone.set_attribute("sw", "cor", data.fetch("zone_corner"))
+ end
+ end
+ end
+
+ def c02(data) #add_texture
+ ckey = data.fetch("ckey")
+ if @textures.include?(ckey)
+ texture = @textures.fetch(ckey)
+ if texture.valid?
+ return
+ end
+ end
+
+ material = Sketchup.active_model.materials.add
+ material.texture = data.fetch("src")
+ material.alpha = data.fetch("alpha")
+ texture = material.texture
+ unless texture.nil?
+ texture.size = [texture.image_width.mm, texture.image_height.mm]
+ @textures.store(ckey, material)
+ end
+ end
+
+ def get_texture(key)#key maybe nil when edge etc.
+ if key && @textures.include?(key)
+ @textures.fetch(key)
+ else
+ @textures.fetch("mat_default")
+ end
+ end
+
+ def c03(data) #add_zone
+ uid = data.fetch("uid")
+ zid = data.fetch("zid")
+ zones = get_zones(data)
+ elements = data.fetch("children")
+ if data.key?("trans")
+ poses = {}
+ elements.each{|element|
+ surf = element.fetch("surf")
+ poses.store(surf.fetch("p"), element.fetch("child"))
+ }
+ scale = Geom::Transformation.scaling(data.fetch("w").mm.to_f / 1000.0,
+ data.fetch("d").mm.to_f / 1000.0,
+ data.fetch("h").mm.to_f / 1000.0)
+ trans = Geom::Transformation::parse(data.fetch("t"))
+ group = @@default_zone.copy
+ group.transform!(scale)#单独缩放, 否则无效
+ group.transform!(trans)
+ group.visible = true
+ group.entities.each{|entity|
+ if entity.class == Sketchup::Face
+ p = entity.get_attribute("sw", "p")
+ entity.set_attribute("sw", "child", poses.fetch(p))
+ if p == 1
+ entity.layer = @door_layer
+ end
+ end
+ }
+ else
+ group = Sketchup.active_model.entities.add_group
+ elements.each{|element|
+ surf = element.fetch("surf")
+ face = create_face(group, surf)
+ if surf.fetch("p") == 1
+ face.layer = @door_layer
+ end
+ face.set_attribute("sw", "child", element.fetch("child"))
+ }
+ end
+ group.set_attribute("sw", "uid", uid)
+ group.set_attribute("sw", "zid", zid)
+ group.set_attribute("sw", "zip", data.fetch("zip", -1))
+ group.set_attribute("sw", "typ", "zid")
+ group.set_attribute("sw", "cor", data.fetch("cor")) if data.key?("cor")
+
+ if @unit_trans.key?(uid)
+ group.transform!(@unit_trans.fetch(uid))
+ end
+ group.make_unique
+ group.definition.behavior.no_scale_mask = 127#限制所有方向的拉伸
+ zones.store(zid, group)
+ end
+
+ def c06(data) #add_wall
+ zones = get_zones(data)
+ zid = data.fetch("zid")
+ zone = zones.fetch(zid, nil)
+ elements = data.fetch("children")
+ elements.each{|element|
+ surf = element.fetch("surf")
+ face = create_face(zone, surf)
+ if surf.fetch("p") == 1
+ face.layer = @door_layer
+ end
+ face.set_attribute("sw", "child", element.fetch("child"))
+ }
+ end
+
+ def c04(data) #add_part
+ def add_part_profile(face, index, profiles)
+ profile = profiles.fetch(index, nil)
+ color = profile.nil? ? nil : profile.fetch("ckey", nil)
+ scale = profile.nil? ? nil : profile.fetch("scale", nil)
+ angle = profile.nil? ? nil : profile.fetch("angle", nil)
+ type = profile.nil? ? "0" : profile.fetch("typ", "0")
+ current =
+ if @mat_type == MAT_TYPE_OBVERSE
+ case type
+ when 1 then "mat_obverse" #thick profile
+ when 2 then "mat_thin" #thin profile
+ else "mat_reverse" #none profile
+ end
+ else
+ color
+ end
+ face.set_attribute("sw", "typ", "e#{type}")
+ textured_surf(face, @back_material, current, color, scale, angle)
+ end
+
+ def add_part_board(part, data, antiz, profiles)
+ leaf = part.entities.add_group
+ color = data.fetch("ckey")
+ scale = data.fetch("scale", nil)
+ angle = data.fetch("angle", nil)
+ color2 = data.fetch("ckey2", nil)
+ scale2 = data.fetch("scale2", nil)
+ angle2 = data.fetch("angle2", nil)
+
+ leaf.set_attribute("sw", "ckey", color)
+ leaf.set_attribute("sw", "scale", scale) if scale
+ leaf.set_attribute("sw", "angle", angle) if angle
+ if data.key?("sects")
+ sects = data.fetch("sects")
+ sects.each{|sect|
+ segs = sect.fetch("segs")
+ surf = sect.fetch("sect")
+ paths = create_paths(part, segs)
+ follow_me(leaf, surf, paths, color, scale, angle)
+ }
+ leaf2 = leaf.entities.add_group
+ add_part_surf(leaf2, data, antiz, color, scale, angle, color2, scale2, angle2, profiles)
+ else
+ add_part_surf(leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles)
+ end
+
+ leaf
+ end
+
+ def add_part_surf(leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles)
+ #will store attr when add face, also reverse the rev's normal
+ obv = data.fetch("obv")
+ rev = data.fetch("rev")
+ obv_type = "o"
+ obv_save = color
+ obv_scale = scale
+ obv_angle = angle
+ rev_type = "r"
+ rev_save = color2.nil? ? color : color2
+ rev_scale = color2.nil? ? scale : scale2
+ rev_angle = color2.nil? ? angle : angle2
+ if antiz#交换type/save/scale/angle
+ obv_type, rev_type = rev_type, obv_type
+ obv_save, rev_save = rev_save, obv_save
+ obv_scale, rev_scale = rev_scale, obv_scale
+ obv_angle, rev_angle = rev_angle, obv_angle
+ end
+ obv_show = @mat_type == MAT_TYPE_OBVERSE ? "mat_obverse" : obv_save
+ rev_show = @mat_type == MAT_TYPE_OBVERSE ? "mat_reverse" : rev_save
+
+ series1 = []
+ series2 = []
+ create_face(leaf, obv, obv_show, obv_scale, obv_angle, series1, false, @back_material, obv_save, obv_type)#obv
+ create_face(leaf, rev, rev_show, rev_scale, rev_angle, series2, true, @back_material, rev_save, rev_type)#rev
+ add_part_edges(leaf, series1, series2, obv, rev, profiles)
+ end
+
+ def add_part_edges(leaf, series1, series2, obv, rev, profiles = nil)
+ unplanar = false
+ series1.each_index{|index|
+ pts1 = series1[index]
+ pts2 = series2[index]
+ pts1.each_index{|i|
+ next if i.zero?
+
+ pts = [pts1[i-1], pts1[i], pts2[i], pts2[i-1]]
+ begin
+ face = leaf.entities.add_face(pts)
+ face.edges.each_index{|k|
+ face.edges[k].hidden = true if [1, 3].include?(k)
+ }
+ add_part_profile(face, index, profiles) unless profiles.nil?
+ rescue
+ unplanar = true
+ puts "Points are not planar #{index}: #{i}"
+ puts pts
+ end
+ }
+ }
+
+ if unplanar
+ segs_o = obv.fetch("segs"); pts_o = []; segs_o.each{|seg| pts_o << seg[0]}
+ segs_r = rev.fetch("segs"); pts_r = []; segs_r.each{|seg| pts_r << seg[0]}
+ puts "==========================="
+ puts "obv:\t#{pts_o.join("\t")}"
+ puts "rev:\t#{pts_r.join("\t")}"
+ puts "series1:\t#{series1.join("\t")}"
+ puts "series2:\t#{series2.join("\t")}"
+ puts "==========================="
+ end
+ end
+
+ def add_part_stretch(part, data)
+ copmensates = data.fetch("copmensates")
+ trim_surfs = data.fetch("trim_surfs")
+ baselines = create_paths(part, data.fetch("baselines"))
+ inst = nil
+ if data.include?("sid") && copmensates.empty? && trim_surfs.empty? && baselines.length == 1
+ file = "#{SUWood::suwood_path(V_StretchPart)}/#{data.fetch("sid")}.skp"
+ if File.exist?(file) && File.size(file) > 0
+ begin
+ defi = Sketchup.active_model.definitions.load(file)
+ inst = part.entities.add_instance(defi, ORIGIN)
+ inst.set_attribute("sw", "typ", "cp")
+ xrate = baselines[0].length.to_mm / inst.bounds.width.to_mm
+ trans = Geom::Transformation::parse(data)
+ inst.transform!(Geom::Transformation.scaling(xrate, 1, 1))
+ inst.transform!(trans)
+ rescue => e
+ puts e
+ inst = nil
+ end
+ end
+ end
+ if inst
+ leaf = part.entities.add_group
+ surf = data.fetch("sect")
+ surf.store("segs", data.fetch("bounds"))
+ follow_me(leaf, surf, baselines, nil)
+ leaf.set_attribute("sw", "virtual", true)
+ leaf.visible = false
+ else
+ thick = data.fetch("thick").mm
+ leaf = part.entities.add_group
+ zaxis = Geom::Vector3d.parse(data.fetch("zaxis"))
+ color = data.fetch("ckey")
+ sect = data.fetch("sect")
+ follow_me(leaf, sect, baselines, color)
+ copmensates.each{|copmensate|
+ points = copmensate.collect{|point| Geom::Point3d.parse(point)}
+ path = part.entities.add_line(points[0].offset(zaxis, -1.mm), points[0].offset(zaxis, thick + 1.mm))#要稍微大点!
+ trimmer = part.entities.add_group
+ face_t = trimmer.entities.add_face(points)
+ face_t.followme(path)
+
+ trimmed = trimmer.trim(leaf)
+ leaf = trimmed if trimmed
+ path.erase! if path && path.valid?
+ trimmer.erase! if trimmer && trimmer.valid?
+ }
+ trim_surfs.each{|trim_surf|
+ trimmer = part.entities.add_group
+ face_t = create_face(trimmer, trim_surf)
+ point = face_t.edges[0].start.position
+ path = part.entities.add_line(point.offset(zaxis, -1.mm), point.offset(zaxis, thick + 1.mm))#要稍微大点!
+ face_t.followme(path)
+
+ trimmed = trimmer.trim(leaf)
+ leaf = trimmed if trimmed
+ path.erase! if path && path.valid?
+ trimmer.erase! if trimmer && trimmer.valid?
+ }
+ leaf.set_attribute("sw", "ckey", color)
+ end
+ leaf
+ end
+
+ def add_part_arc(part, data, antiz, profiles)
+ leaf = part.entities.add_group
+ obv = data.fetch("obv")
+ color = data.fetch("ckey")
+ scale = data.fetch("scale", nil)
+ angle = data.fetch("angle", nil)
+ color2 = data.fetch("ckey2", nil)
+ scale2 = data.fetch("scale2", nil)
+ angle2 = data.fetch("angle2", nil)
+
+ leaf.set_attribute("sw", "ckey", color)
+ leaf.set_attribute("sw", "scale", scale) if scale
+ leaf.set_attribute("sw", "angle", angle) if angle
+
+ center_o = Geom::Point3d.parse(data.fetch("co"))
+ center_r = Geom::Point3d.parse(data.fetch("cr"))
+ path = leaf.entities.add_line(center_o, center_r)
+ series = []
+ normal = follow_me(leaf, obv, path, color, scale, angle, false, series, true)
+
+ if series.size == 4
+ count = 0
+ edge1 = false
+ edge3 = false
+ face2 = color2.nil?
+ leaf.entities.each{|entity|
+ next unless entity.class == Sketchup::Face
+
+ if entity.normal.parallel?(normal)
+ if center_o.on_plane?(entity.plane)
+ add_part_profile(entity, 2, profiles)
+ count += 1
+ else
+ add_part_profile(entity, 0, profiles)
+ count += 1
+ end
+ else
+ entity.edges.each{|edge|
+ s = edge.start.position
+ e = edge.end.position
+ if !edge1 && series[1].include?(s) && series[1].include?(e)#edge1
+ add_part_profile(entity, 1, profiles)
+ count += 1
+ edge1 = true
+ break
+ elsif !edge3 && series[3].include?(s) && series[3].include?(e)#edge3
+ add_part_profile(entity, 3, profiles)
+ count += 1
+ edge3 = true
+ break
+ elsif !face2 && series[2].include?(s) && series[2].include?(e)#face2
+ textured_surf(entity, @back_material, color2, color2, scale2, angle2)
+ count += 1
+ face2 = true
+ break
+ end
+ } unless edge1 && edge3 && face2
+ end
+
+ if !color2.nil? && count == 5 || color2.nil? && count == 4
+ break
+ end
+ }
+ end
+
+ leaf
+ end
+
+ uid = data.fetch("uid")
+ root = data.fetch("cp")
+ parts = get_parts(data)
+ part = parts.fetch(root, nil)
+ added = false
+ if part.nil?
+ part = Sketchup.active_model.entities.add_group
+ parts.store(root, part)
+ added = true
+ else
+ part.entities.each{|leaf|
+ leaf.erase! if leaf.valid? && leaf.get_attribute("sw", "typ") == "cp"
+ }
+ end
+
+ part.set_attribute("sw", "uid", uid)
+ part.set_attribute("sw", "zid", data.fetch("zid"))
+ part.set_attribute("sw", "pid", data.fetch("pid"))
+ part.set_attribute("sw", "cp", root)
+ part.set_attribute("sw", "typ", "cp")
+ layer = data.fetch("layer")
+ if layer == 1
+ part.layer = @door_layer
+ elsif layer == 2
+ part.layer = @drawer_layer
+ end
+
+ #开门和拉抽屉功能
+ drawer_type= data.fetch("drw",0)
+ part.set_attribute("sw", "drawer", drawer_type)
+ if drawer_type == 73 or drawer_type == 74 #DR_LP/DR_RP
+ part.set_attribute("sw", "dr_depth",data.fetch("drd",0))
+ end
+ if drawer_type == 70
+ part.set_attribute("sw", "drawer_dir",Geom::Vector3d.parse(data.fetch("drv")))
+ end
+ door_type= data.fetch("dor",0)
+ part.set_attribute("sw", "door", door_type)
+ if door_type == 10 or door_type == 15
+ part.set_attribute("sw", "door_width",data.fetch("dow",0))
+ part.set_attribute("sw", "door_pos",data.fetch("dop","F"))
+ end
+
+ inst = nil
+ if data.include?("sid")
+ mirr = data.fetch("mr", nil)
+ mirr = mirr.nil? ? "" : "_" + mirr
+ file = "#{SUWood::suwood_path(V_StructPart)}/#{data.fetch("sid")}#{mirr}.skp"
+ puts file
+ if File.exist?(file) && File.size(file) > 0
+ begin
+ defi = Sketchup.active_model.definitions.load(file)
+ inst = part.entities.add_instance(defi, ORIGIN)
+ inst.set_attribute("sw", "typ", "cp")
+ xrate = data.fetch("l") / inst.bounds.width.to_mm
+ yrate = data.fetch("w") / inst.bounds.height.to_mm
+ trans = Geom::Transformation::parse(data.fetch("trans"))
+ inst.transform!(Geom::Transformation.scaling(xrate, yrate, 1))
+ inst.transform!(trans)
+ rescue => e
+ puts e
+ inst = nil
+ end
+ end
+ end
+
+ if inst
+ leaf = part.entities.add_group
+ if data.fetch("typ") == 3
+ center_o = Geom::Point3d.parse(data.fetch("co"))
+ center_r = Geom::Point3d.parse(data.fetch("cr"))
+ path = leaf.entities.add_line(center_o, center_r)
+ follow_me(leaf, data.fetch("obv"), path, nil)
+ else
+ obv = data.fetch("obv")
+ rev = data.fetch("rev")
+ series1 = []
+ series2 = []
+ create_face(leaf, obv, nil, nil, nil, series1)#obv
+ create_face(leaf, rev, nil, nil, nil, series2)#rev
+ add_part_edges(leaf, series1, series2, obv, rev)
+ end
+ leaf.set_attribute("sw", "typ", "cp")
+ leaf.set_attribute("sw", "virtual", true)
+ leaf.visible = false
+ finals = data.fetch("finals", [])
+ finals.each{|final|
+ next unless final.fetch("typ") == 2
+ stretch = add_part_stretch(part, final)
+ if stretch
+ stretch.set_attribute("sw", "typ", "cp")
+ stretch.set_attribute("sw", "mn", final.fetch("mn"))
+ end
+ }
+ else
+ finals = data.fetch("finals", [])
+ finals.each{|final|
+ profiles = Hash.new
+ ps = final.fetch("ps", nil)
+ ps.each{|p|
+ p.fetch("idx").split(",").each{|idx|
+ profiles.store(idx.to_i, p)
+ }
+ } if ps
+
+ leaf =
+ case final.fetch("typ")
+ when 1 then add_part_board(part, final, final.fetch("antiz", false), profiles)
+ when 2 then add_part_stretch(part, final)
+ when 3 then add_part_arc(part, final, final.fetch("antiz", false), profiles)
+ end
+ if leaf
+ leaf.set_attribute("sw", "typ", "cp")
+ leaf.set_attribute("sw", "mn", final.fetch("mn"))
+ else
+ puts "leaf is nil"
+ end
+ }
+ end
+
+ if added && @unit_trans.key?(uid)
+ part.transform!(@unit_trans.fetch(uid))
+ end
+ part.make_unique
+ part.definition.behavior.no_scale_mask = 127#限制所有方向的拉伸
+ end
+
+ def c05(data) #add_machining, 无需unit_trans
+ uid = data.fetch("uid")
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+ unless @machinings.key?(uid)
+ @machinings.store(uid, [])
+ end
+ machinings = @machinings.fetch(uid)
+
+ items = data.fetch("items")
+ items.each{|work|
+ cp = work.fetch("cp")
+ component = parts.key?(cp) ? parts.fetch(cp) : hardwares.fetch(cp, nil)
+ if component.nil? || component.deleted?
+ next
+ elsif work.fetch("trim3d") == 1
+ work_trimmed(component, work)
+ else
+ special = work.fetch("special")
+ machining = component.entities.add_group
+ machinings << machining
+ machining.set_attribute("sw", "typ", "work")
+ machining.set_attribute("sw", "special", special)
+ p1 = Geom::Point3d.parse(work.fetch("p1"))
+ p2 = Geom::Point3d.parse(work.fetch("p2"))
+ if work.key?("tri")
+ tri = Geom::Point3d.parse(work.fetch("tri"))
+ p3 = Geom::Point3d.parse(work.fetch("p3"))
+ path = component.entities.add_line(p1, p3)
+ pts = [tri, tri.offset(p2 - p1), p1.offset(p1 - tri)]
+ face = machining.entities.add_face(pts)
+ elsif work.key?("surf")
+ path = component.entities.add_line(p1, p2)
+ surf = work.fetch("surf")
+ face = create_face(machining, surf)
+ else
+ path = component.entities.add_line(p1, p2)
+ za = (p2 - p1).normalize
+ dia = work.fetch("dia").mm
+ cir = machining.entities.add_circle(p1, za, dia / 2.0)
+ face = machining.entities.add_face(cir)
+ end
+ if special == 0 && work.fetch("cancel") == 0
+ texture = get_texture("mat_machine")
+ face.material = texture
+ face.back_material = texture
+ end
+ face.followme(path)
+ path.erase! if path && path.valid?
+ end
+ }
+ end
+
+ def c08(data) #add_hardware
+ hardwares = get_hardwares(data)
+ parts = get_parts(data)
+
+ uid = data.fetch("uid")
+ zid = data.fetch("zid")
+ pid = data.fetch("pid")
+ items = data.fetch("items")
+ items.each{|item|
+ elem = nil
+ file = "#{SUWood::suwood_path(V_Hardware)}/#{item.fetch("uid")}.skp"
+ cp = item.fetch("cp")
+ ps = item.key?("ps") ? Geom::Point3d.parse(item.fetch("ps")) : nil
+ pe = item.key?("pe") ? Geom::Point3d.parse(item.fetch("pe")) : nil
+ if File.exist?(file) && File.size(file) > 0
+ begin
+ defi = Sketchup.active_model.definitions.load(file)
+ elem = Sketchup.active_model.entities.add_instance(defi, ORIGIN)
+ if ps && pe
+ xrate = ps.distance(pe).to_mm / elem.bounds.width.to_mm
+ elem.transform!(Geom::Transformation.scaling(xrate, 1, 1))
+ end
+ trans = Geom::Transformation::parse(item)
+ elem.transform!(trans)
+ rescue => e
+ puts e
+ elem = nil
+ end
+ end
+ if elem.nil? && ps && pe
+ elem = Sketchup.active_model.entities.add_group
+ path = Sketchup.active_model.entities.add_line(ps, pe)
+ sect = item.fetch("sect")
+ color = item.fetch("ckey")
+ follow_me(elem, sect, path, color)
+ elem.set_attribute("sw", "ckey",color)
+ end
+
+ if elem
+ elem.set_attribute("sw", "uid", uid)
+ elem.set_attribute("sw", "zid", zid)
+ elem.set_attribute("sw", "pid", pid)
+ elem.set_attribute("sw", "cp", cp)
+ if item.key?("part")
+ elem.set_attribute("sw", "part", item.fetch("part"))
+ end
+ if item.key?("typ")
+ typ = item.fetch("typ")
+ elem.set_attribute("sw", "typ", typ)
+ if typ == "pull"
+ elem.layer = @door_layer
+ end
+ else
+ elem.set_attribute("sw", "typ", "hw")
+ end
+ hardwares.store(cp, elem)
+ end
+
+ if elem && @unit_trans.key?(uid)
+ elem.transform!(@unit_trans.fetch(uid))
+ end
+
+ if elem && item.key?("work")
+ work = item.fetch("work")
+ part = parts.fetch(work.fetch("cp"), nil)
+ if part
+ work_trimmed(part, work)
+ end
+ end
+ }
+ end
+
+ def work_trimmed(part, work)
+ leaves = []
+ part.entities.each{|leaf|
+ leaves << leaf if leaf.get_attribute("sw", "typ") == "cp"
+ }
+
+ leaves.each{|leaf|
+ next if leaf.deleted?
+
+ attries = {}
+ leaf.attribute_dictionaries['sw'].each{|key, value|
+ attries.store(key, value)
+ }
+
+ trimmer = part.entities.add_group
+ p1 = Geom::Point3d.parse(work.fetch("p1"))
+ p2 = Geom::Point3d.parse(work.fetch("p2"))
+ if work.key?("tri")
+ tri = Geom::Point3d.parse(work.fetch("tri"))
+ p3 = Geom::Point3d.parse(work.fetch("p3"))
+ path = part.entities.add_line(p1, p3)
+ pts = [tri, tri.offset(p2 - p1), p1.offset(p1 - tri)]
+ face = trimmer.entities.add_face(pts)
+ elsif work.key?("surf")
+ path = part.entities.add_line(p1, p2)
+ surf = work.fetch("surf")
+ face = create_face(trimmer, surf)
+ else
+ path = part.entities.add_line(p1, p2)
+ za = (p2 - p1).normalize
+ dia = work.fetch("dia").mm
+ cir = trimmer.entities.add_circle(p1, za, dia / 2.0)
+ face = trimmer.entities.add_face(cir)
+ end
+
+ if work.fetch("differ", false)
+ texture = get_texture("mat_default")
+ face.material = texture
+ face.back_material = texture
+ end
+
+ face.followme(path)
+ trimmed = trimmer.trim(leaf)
+ attries.each{|key, value|
+ trimmed.set_attribute("sw", key, value)
+ } if trimmed
+ path.erase! if path.valid?
+ trimmer.erase! if trimmer.valid?
+
+ if work.fetch("differ", false)
+ texture = get_texture("mat_default")
+ trimmed.entities.each{|entity|
+ if entity.class == Sketchup::Face && (entity.material == texture || entity.back_material == texture)
+ entity.set_attribute("sw", "differ", true)
+ end
+ }
+ end
+ }
+ end
+
+ def c12(data) #add_contour
+ @added_contour = true
+ if data.key?("surf")
+ surf = data.fetch("surf")
+ xaxis = Geom::Vector3d.parse(surf.fetch("vx"))
+ zaxis = Geom::Vector3d.parse(surf.fetch("vz"))
+ segs = surf.fetch("segs")
+ edges = []
+ segs.each{|seg|
+ if seg.key?("c")
+ c = Geom::Point3d.parse(seg.fetch("c"))
+ r = seg.fetch("r").mm
+ a1 = seg.fetch("a1")
+ a2 = seg.fetch("a2")
+ n = seg.fetch("n")
+ edges.concat(Sketchup.active_model.entities.add_arc(c, xaxis, zaxis, r, a1, a2, n))
+ else
+ s = Geom::Point3d.parse(seg.fetch("s"))
+ e = Geom::Point3d.parse(seg.fetch("e"))
+ edges << Sketchup.active_model.entities.add_line(s, e)
+ end
+ }
+ begin
+ Sketchup.active_model.entities.add_face(edges)
+ rescue => e
+ #do nothing
+ end
+ end
+ Sketchup.send_action "viewTop:"
+ Sketchup.send_action "viewZoomExtents:"
+ end
+
+ def add_surf(data)
+ surf = data.fetch("surf")
+ create_face(Sketchup.active_model, surf)
+ end
+
+ def c07(data) #add_dim
+ uid = data.fetch("uid")
+ dims = data.fetch("dims")
+ unless @dimensions.key?(uid)
+ @dimensions.store(uid, [])
+ end
+ dimensions = @dimensions.fetch(uid)
+ dims.each{|dim|
+ p1 = Geom::Point3d.parse(dim.fetch("p1"))
+ p2 = Geom::Point3d.parse(dim.fetch("p2"))
+ d = Geom::Vector3d.parse(dim.fetch("d"))
+ if @unit_trans.key?(uid)
+ trans = @unit_trans.fetch(uid)
+ p1.transform!(trans)
+ p2.transform!(trans)
+ d.transform!(trans)
+ end
+
+ t = dim.fetch("t")
+ entity = Sketchup.active_model.entities.add_dimension_linear(p1, p2, d)
+ entity.has_aligned_text = true
+ entity.arrow_type = Sketchup::Dimension::ARROW_NONE
+ entity.text = t
+ dimensions << entity
+ }
+ end
+
+ def c09(data) #del_entity #machining为part的子项,不需要单独删除
+ sel_clear#清除所有选择
+
+ uid = data.fetch("uid")
+ typ = data.fetch("typ")#zid/cp/work/hw/pull/wall
+ oid = data.fetch("oid")
+
+ if typ == "wall"
+ zones = get_zones(data)
+ zone = zones.fetch(oid, nil)
+ wall = data.fetch("wall")
+ zone.entities.each{|entity|
+ if entity.valid? && entity.class == Sketchup::Face && wall == entity.get_attribute("sw", "child")
+ entity.erase!
+ end
+ }
+ zone.entities.each{|entity|
+ if entity.valid? && entity.class == Sketchup::Edge && entity.faces.size <= 0
+ entity.erase!
+ end
+ }
+ else
+ del_entities(get_zones(data), typ, oid) if ["uid", "zid"].include?(typ)
+ del_entities(get_parts(data), typ, oid)
+ del_entities(get_hardwares(data), typ, oid)
+
+ @labels.entities.clear! if ["uid", "zid"].include?(typ) && @labels.valid?
+ @door_labels.entities.clear! if ["uid", "zid"].include?(typ) && @door_labels.valid?
+
+ machinings = @machinings.fetch(uid, [])
+ machinings.delete_if{|entity| entity.deleted?}
+ end
+
+ if typ == "uid"
+ @mat_type = MAT_TYPE_NORMAL
+ c0c(data)#del_dim
+ end
+ end
+
+ def c0a(data) #del_machining #modified by xt, 20180613
+ uid = data.fetch("uid")
+ typ = data.fetch("typ")#type is unit or source
+ oid = data.fetch("oid")
+ special = data.fetch("special", 1)
+ machinings = @machinings.fetch(uid, [])
+ machinings.each{|entity|
+ if entity.valid? &&
+ (typ == "uid" || entity.get_attribute("sw", typ) == oid) &&
+ (special == 1 || special == 0 && entity.get_attribute("sw", "special") == 0)#added by xt, 20180613
+ entity.erase!
+ end
+ }
+ machinings.delete_if{|entity| entity.deleted?}
+ end
+
+ def del_entities(entities, typ, oid)
+ entities.each{|key, entity|
+ if entity.valid? && (typ == "uid" || entity.get_attribute("sw", typ) == oid)
+ entity.erase!
+ end
+ }
+ entities.delete_if{|key, entity| entity.deleted?}
+ end
+
+ def c0c(data) #del_dim
+ uid = data.fetch("uid")
+ if @dimensions.key?(uid)
+ dimensions = @dimensions.fetch(uid)
+ dimensions.each{|dim| dim.erase!}
+ @dimensions.delete(uid)
+ end
+ end
+
+ def c15(data) #sel_unit
+ sel_clear
+ zones = get_zones(data)
+ zones.each{|zid, zone|
+ if zone.valid?
+ leaf = is_leaf_zone(zid, zones)
+ zone.hidden = leaf ? @hide_none : true
+ end
+ }
+ end
+
+ def is_leaf_zone(zip, zones)
+ zones.each{|zid, zone|
+ if zone.valid? && zone.get_attribute("sw","zip") == zip
+ return(false)
+ end
+ }
+ true
+ end
+
+ def set_children_hidden(uid, zid)
+ zones = get_zones({"uid" => uid})
+ children = get_child_zones(zones, zid, true)
+ children.each {|child|
+ child_id = child.fetch("zid")
+ child_zone = zones.fetch(child_id)
+ child_zone.hidden = true
+ }
+ end
+
+ def get_child_zones(zones, zip, myself = false) #本地运行
+ children = []
+ zones.each{|zid, entity|
+ if entity.valid? && entity.get_attribute("sw", "zip") == zip
+ grandchildren = get_child_zones(zones, zid, false)
+ child = {}
+ child.store("zid", zid)
+ child.store("leaf", grandchildren.length == 0)
+ children.push(child)
+ children.concat(grandchildren)
+ end
+ }
+
+ if myself
+ child = {}
+ child.store("zid", zip)
+ child.store("leaf", children.length == 0)
+ children.push(child)
+ end
+
+ children
+ end
+
+ def c16(data) #sel_zone
+ sel_zone_local(data)
+ end
+
+ #开门和拉抽屉功能
+ def c10(data) #set_doorinfo
+ parts = get_parts(data)
+ doors = data.fetch("drs",[])
+ doors.each {|door|
+ root=door.fetch("cp",0)
+ door_dir=door.fetch("dov","")
+ ps=Geom::Point3d.parse(door.fetch("ps"))
+ pe=Geom::Point3d.parse(door.fetch("pe"))
+ offset=Geom::Vector3d.parse(door.fetch("off"))
+ if root > 0 && parts.key?(root)
+ part = parts.fetch(root)
+ part.set_attribute("sw","door_dir",door_dir)
+ part.set_attribute("sw","door_ps",ps)
+ part.set_attribute("sw","door_pe",pe)
+ part.set_attribute("sw","door_offset",offset)
+ end
+ }
+ end
+
+ def c1a(data) #open_doors
+ uid = data.fetch("uid")
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+ mydoor = data.fetch("cp",0)
+ value = data.fetch("v",false)
+ parts.each{|root, part|
+ next unless mydoor == 0 or mydoor == root
+
+ door_type=part.get_attribute("sw", "door",0)
+ next unless door_type > 0
+
+ is_open=part.get_attribute("sw","door_open",false)
+ next if is_open == value
+
+ next unless door_type == 10 or door_type==15
+ door_ps = part.get_attribute("sw","door_ps")
+ door_pe = part.get_attribute("sw","door_pe")
+ door_off = part.get_attribute("sw","door_offset")
+ if door_type == 10 #平开门
+ if door_ps.nil? || door_pe.nil? || door_off.nil?
+ next
+ end
+ if @unit_trans.key?(uid)
+ trans = @unit_trans.fetch(uid)
+ door_ps.transform!(trans)
+ door_pe.transform!(trans)
+ door_off.transform!(trans)
+ end
+ trans_r = Geom::Transformation.rotation(door_ps, (door_pe-door_ps), 1.5708) #开90度
+ trans_t = Geom::Transformation.translation(door_off)
+ trans_a = trans_r*trans_t
+ else
+ if door_off.nil?
+ next
+ end
+ if @unit_trans.key?(uid)
+ door_off.transform!(@unit_trans.fetch(uid))
+ end
+ trans_a= Geom::Transformation.translation(door_off)
+ end
+ if is_open
+ trans_a.invert!
+ end
+ part.set_attribute("sw","door_open",!is_open)
+ part.transform!(trans_a)
+ hardwares.each{|key, hardware|
+ if hardware.get_attribute("sw", "part", nil) == root
+ hardware.transform!(trans_a)
+ end
+ }
+ }
+ end
+
+ def c1b(data) #slide_drawers
+ uid = data.fetch("uid")
+ zones = get_zones(data)
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+
+ drawers={}
+ depths ={}
+ parts.each{|root, part|
+ drawer_type=part.get_attribute("sw", "drawer",0)
+ if drawer_type> 0
+ if drawer_type == 70 #DR_DP
+ drawers.store( part.get_attribute("sw","pid"), part.get_attribute("sw","drawer_dir") )
+ end
+ if drawer_type == 73 or drawer_type == 74 #DR_LP/DR_RP
+ depths.store( part.get_attribute("sw","pid"), part.get_attribute("sw","dr_depth",0))
+ end
+ end
+ }
+
+ offsets={}
+ drawers.each{|drawer,dir|
+ zone = zones.fetch(drawer, nil)
+ next if zone.nil?
+
+ vector = dir
+ dr_depth=depths.fetch(drawer,300).mm
+ vector.length= dr_depth*0.9
+ if @unit_trans.key?(uid)
+ vector.transform!(@unit_trans.fetch(uid))
+ end
+ offsets.store(drawer, vector)
+ }
+
+ value = data.fetch("v",false)
+ offsets.each{|drawer,vector|
+ zone = zones.fetch(drawer, nil)
+ next if zone.nil?
+
+ is_open=zone.get_attribute("sw","drawer_open",false)
+ next if is_open == value
+
+ trans_a = Geom::Transformation.translation(vector)
+ trans_a.invert! if is_open #==true
+
+ zone.set_attribute("sw","drawer_open",!is_open)
+ parts.each{|root, part|
+ if part.get_attribute("sw", "pid") == drawer
+ part.transform!(trans_a);
+ end
+ }
+
+ hardwares.each{|root, hardware|
+ if hardware.get_attribute("sw", "pid") == drawer
+ hardware.transform!(trans_a)
+ end
+ }
+ }
+ end
+
+ def c17(data) #sel_elem
+ if @part_mode
+ sel_part_parent(data)
+ else
+ sel_zone_local(data)
+ end
+ end
+
+ def c0d(data) #parts_seqs
+ parts = get_parts(data)
+ seqs = data.fetch("seqs")
+ seqs.each{ |d|
+ root=d.fetch("cp")#部件id
+ seq=d.fetch("seq")#顺序号
+ pos=d.fetch("pos")#位置
+ name=d.fetch("name",nil)#板件名称
+ size=d.fetch("size",nil)#尺寸即长*宽*厚
+ mat=d.fetch("mat",nil)#材料(包括材质/颜色),可能为空
+
+ e_part= parts.fetch(root,nil)
+ if e_part
+ e_part.set_attribute("sw","seq",seq)
+ e_part.set_attribute("sw","pos",pos)
+ if name
+ e_part.set_attribute("sw","name",name)
+ end
+ if size
+ e_part.set_attribute("sw","size",size)
+ end
+ end
+ }
+ end
+
+ def c0e(data)#explode_zones
+ if @labels.valid?
+ @labels.entities.clear!
+ else
+ @labels= Sketchup.active_model.entities.add_group
+ end
+
+ if @door_labels.valid?
+ @door_labels.entities.clear!
+ else
+ @door_labels= Sketchup.active_model.entities.add_group
+ end
+
+ uid = data.fetch("uid")
+ zones = get_zones(data)
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+ jzones = data.fetch("zones")
+ jzones.each{|zone|
+ zoneid = zone.fetch("zid")
+ offset = Geom::Vector3d.parse(zone.fetch("vec"))
+ if @unit_trans.key?(uid)
+ offset.transform!(@unit_trans.fetch(uid))
+ end
+ trans_a = Geom::Transformation.translation(offset)
+ if zones.key?(zoneid)
+ azone = zones.fetch(zoneid)
+ azone.transform!(trans_a)
+ end
+ }
+
+ jparts = data.fetch("parts")
+ jparts.each{|jpart|
+ pid=jpart.fetch("pid")
+ offset = Geom::Vector3d.parse(jpart.fetch("vec"))
+ if @unit_trans.key?(uid)
+ offset.transform!(@unit_trans.fetch(uid))
+ end
+ trans_a = Geom::Transformation.translation(offset)
+ parts.each{|root, part|
+ if part.get_attribute("sw", "pid") == pid
+ part.transform!(trans_a)
+ end
+ }
+ hardwares.each{|root, hardware|
+ if hardware.get_attribute("sw", "pid") == pid
+ hardware.transform!(trans_a)
+ end
+ }
+ }
+
+ if data.fetch("explode", false)#part seq text
+ parts.each{|root, part|
+ center=part.bounds.center.clone
+ pos=part.get_attribute("sw","pos",1)
+ if pos == 1 then vector=Geom::Vector3d.new(0, -1, 0)#F
+ elsif pos == 2 then vector=Geom::Vector3d.new(0, 1, 0)#K
+ elsif pos == 3 then vector=Geom::Vector3d.new(-1, 0, 0)#L
+ elsif pos == 4 then vector=Geom::Vector3d.new(1, 0, 0)#R
+ elsif pos == 5 then vector=Geom::Vector3d.new(0, 0, -1)#B
+ else vector=Geom::Vector3d.new(0, 0, 1)#T
+ end
+ vector.length = 100.mm
+ if @unit_trans.key?(uid)
+ vector.transform!(@unit_trans.fetch(uid))
+ end
+ ord_seq=part.get_attribute("sw","seq",0)
+ if part.layer == @door_layer
+ text =@door_labels.entities.add_text ord_seq.to_s, center, vector
+ else
+ text=@labels.entities.add_text ord_seq.to_s, center, vector
+ end
+ text.material="red"
+ }
+ end
+ end
+
+ def sel_local(obj)
+ uid = obj.get_attribute("sw", "uid")
+ zid = obj.get_attribute("sw", "zid")
+ typ = obj.get_attribute("sw", "typ")
+ pid = obj.get_attribute("sw", "pid", -1)
+ cp = obj.get_attribute("sw", "cp", -1)
+ params = {}
+ params.store("uid", uid)
+ params.store("zid", zid)
+ if typ == "zid"
+ if @@selected_uid == uid && @@selected_obj == zid
+ return
+ end
+ elsif typ == "cp"#part or slide
+ if @@selected_uid == uid && (@@selected_obj == pid || @@selected_obj == cp)
+ return
+ end
+ else
+ sel_clear
+ return
+ end
+
+ if typ == "cp" && @part_mode
+ params.store("pid", pid)
+ params.store("cp", cp)
+ sel_part_local(params)
+ else
+ params.store("pid", -1)
+ params.store("cp", -1)
+ sel_zone_local(params)
+ end
+ set_cmd("r01", params)#select_client
+ end
+
+ def sel_zone_local(data) #本地运行
+ sel_clear
+ uid = data.fetch("uid")
+ zid = data.fetch("zid")
+ zones = get_zones(data)
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+ children = get_child_zones(zones, zid, true)#children中包括自己
+ children.each{|child|
+ childid = child.fetch("zid")
+ childzone = zones.fetch(childid)
+ leaf = child.fetch("leaf")#没有下级区域
+ parts.each{|v_root, part|
+ if part.get_attribute("sw", "zid") == childid
+ textured_part(part, true)
+ end
+ }
+
+ hardwares.each{|v_root, hw|
+ if hw.get_attribute("sw", "zid") == childid
+ textured_hw(hw, true)
+ end
+ }
+
+ if !leaf || @hide_none
+ childzone.hidden = true
+ else
+ childzone.hidden = false
+ childzone.entities.each{|face|
+ if face.class == Sketchup::Face
+ textured_face(face, true)
+ end
+ }
+ end
+
+ if childid == zid
+ @@selected_uid = uid
+ @@selected_obj = zid
+ @@selected_zone = childzone
+ end
+ }
+
+ if @@selected_zone && Module.const_defined?("SuwoodResourceLibrary")
+ ref_v = SuwoodResourceLibrary::Browser.get_current_dialog#获取当前资源管理器ref_v
+ corner = @@selected_zone.get_attribute("sw", "cor", 0)
+ poses = []
+ if ref_v == 2102#挂衣杆
+ poses =
+ case corner
+ when 1 then [51,52,55,56]#常规转角
+ when 2 then [53]#直拼转角, 宽向划分
+ when 3 then [54]#直拼转角, 长向划分
+ when 4 then [57]#斜拼转角
+ else [51]#一字型
+ end
+ elsif ref_v == 2103#灯带
+ poses =
+ case corner
+ when 1 then [51,52,53,54]#常规转角
+ when 2 then [53]#直拼转角, 宽向划分
+ when 3 then [54]#直拼转角, 长向划分
+ when 4 then [57]#斜拼转角
+ else [51]#一字型
+ end
+ end
+ if poses.length > 0
+ SuwoodResourceLibrary::Browser.suwood_refresh_data(ref_v, poses)#刷新pos数据
+ end
+ end
+ end
+
+ def sel_part_parent(data)#from server
+ sel_clear
+ zones = get_zones(data)
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+
+ uid = data.fetch("uid")
+ zid = data.fetch("zid")
+ pid = data.fetch("pid")
+ parted = false
+ parts.each{|v_root, part|
+ if part.get_attribute("sw", "pid") == pid
+ textured_part(part, true)
+ @@selected_uid = uid
+ @@selected_obj = pid
+ parted = true
+ end
+ }
+ hardwares.each{|v_root, hw|
+ if hw.get_attribute("sw", "pid") == pid
+ textured_hw(hw, true)
+ end
+ }
+
+ children = get_child_zones(zones, zid, true)
+ children.each{|child|
+ childid = child.fetch("zid")
+ childzone = zones.fetch(childid)
+ leaf = child.fetch("leaf")#没有下级区域
+ if leaf && childid == zid
+ unless @hide_none
+ childzone.hidden = false
+ childzone.entities.each{|face|
+ if face.class == Sketchup::Face
+ selected = face.get_attribute("sw", "child") == pid
+ textured_face(face, selected)
+ if selected
+ @@selected_uid = uid
+ @@selected_obj = pid
+ end
+ end
+ }
+ end
+ elsif !leaf && childid == zid
+ unless parted
+ childzone.hidden = false
+ childzone.entities.each{|face|
+ if face.class == Sketchup::Face && face.get_attribute("sw", "child") == pid
+ textured_face(face, true)
+ @@selected_uid = uid
+ @@selected_obj = pid
+ end
+ }
+ end
+ elsif leaf && !@hide_none
+ childzone.hidden = false
+ childzone.entities.each{|face|
+ if face.class == Sketchup::Face
+ textured_face(face, false)
+ end
+ }
+ else#not leaf or hidden leaf
+ childzone.hidden = true
+ end
+ }
+ end
+
+ def sel_part_local(data)#called by client directly
+ sel_clear
+ parts = get_parts(data)
+ hardwares = get_hardwares(data)
+
+ uid = data.fetch("uid")
+ cp = data.fetch("cp")
+ if parts.key?(cp)
+ part = parts.fetch(cp)
+ if part && part.valid?
+ textured_part(part, true)
+ end
+ @@selected_part = part
+ elsif hardwares.key?(cp)
+ hw = hardwares.fetch(cp)
+ if hw && hw.valid?
+ textured_hw(hw, true)
+ end
+ end
+ @@selected_uid = uid
+ @@selected_obj = cp
+ end
+
+ def sel_clear()
+ @@selected_uid = nil
+ @@selected_obj = nil
+ @@selected_zone = nil
+ @@selected_part = nil
+ @selected_faces.each{|face| textured_face(face, false) if face && face.valid?}; @selected_faces.clear
+ @selected_parts.each{|part| textured_part(part, false) if part && part.valid?}; @selected_parts.clear
+ @selected_hws.each {|hw | textured_hw( hw, false) if hw && hw.valid?}; @selected_hws.clear
+ end
+
+ def textured_face(face, selected)
+ @selected_faces << face if selected
+
+ color = selected ? "mat_select" : "mat_normal"
+ texture = get_texture(color)
+ face.material = texture
+ if @back_material or texture.alpha < 1
+ face.back_material = texture
+ else
+ face.back_material = nil
+ end
+ end
+
+ def textured_part(part, selected)
+ def face_color(face, leaf)
+ color = nil
+ if face.get_attribute("sw", "differ", false)
+ color = "mat_default"
+ else
+ if @mat_type == MAT_TYPE_OBVERSE
+ type = face.get_attribute("sw", "typ")
+ if type == "o" || type == "e1" then color = "mat_obverse"
+ elsif type == "e2" then color = "mat_thin"
+ elsif type == "r" || type == "e0" then color = "mat_reverse"
+ end
+ end
+ color = face.get_attribute("sw", "ckey") if color.nil?
+ color = leaf.get_attribute("sw", "ckey") if color.nil?
+ end
+ color
+ end
+
+ part.entities.each{|leaf|
+ if leaf.class == Sketchup::ComponentInstance && leaf.get_attribute("sw", "typ") == "cp"
+ break
+ end
+ }
+ @selected_parts << part if selected
+ part.entities.each{|leaf|
+ next unless leaf.class == Sketchup::ComponentInstance || leaf.class == Sketchup::Group
+ next if leaf.class == Sketchup::ComponentInstance && leaf.get_attribute("sw", "typ") != "cp"#五金
+ next if leaf.get_attribute("sw", "typ") == "work"
+ next if leaf.get_attribute("sw", "typ") == "pull"
+
+ if leaf.class == Sketchup::ComponentInstance #模型部件
+ leaf.visible = !(selected || @mat_type == MAT_TYPE_NATURE); next
+ elsif leaf.get_attribute("sw", "virtual", false)#虚拟部件(模型部件在选择状态时的替代品)
+ leaf.visible = (selected || @mat_type == MAT_TYPE_NATURE)
+ end
+
+ nature =
+ if selected
+ "mat_select"
+ elsif @mat_type == MAT_TYPE_NATURE
+ case leaf.get_attribute("sw", "mn")
+ when 1 then "mat_obverse" #门板
+ when 2 then "mat_reverse" #柜体
+ when 3 then "mat_thin" #背板
+ end
+ else
+ nil
+ end
+
+ leaf.entities.each{|entity|
+ if entity.class == Sketchup::Face
+ color = nature.nil? ? face_color(entity, leaf) : nature
+ textured_surf(entity, @back_material, color)
+ elsif entity.class == Sketchup::Group
+ entity.entities.each{|entity2|
+ if entity2.class == Sketchup::Face
+ color = nature.nil? ? face_color(entity2, leaf) : nature
+ textured_surf(entity2, @back_material, color)
+ end
+ }
+ end
+ }
+ }
+ end
+
+ def textured_surf(face, back_material, color, saved_color = nil, scale_a = nil, angle_a = nil)
+ def normalize_uvq(uvq); uvq[0] /= uvq[2]; uvq[1] /= uvq[2]; uvq[2] = 1.0; uvq end
+
+ def rotate_texture(face, _scale, _angle, front = true)
+ scale = _scale.nil? ? 1.0 : _scale
+ angle = _angle.nil? ? 0.0 : _angle
+ material = front ? face.material : face.back_material
+ if material && material.texture
+ origin = face.outer_loop.vertices[0].position
+ axes = face.normal.axes
+ samples = []
+ samples << origin # 0,0 | Origin
+ samples << origin.offset(axes.x) # 1,0 | Offset Origin in X
+ samples << origin.offset(axes.x + axes.y) # 1,1 | Offset X in Y
+ samples << origin.offset(axes.y) # 0,1 | Offset Origin in Y
+ writer = Sketchup.create_texture_writer
+ helper = face.get_UVHelper(front, !front, writer)
+ scale_x = Geom::Vector3d.new(axes.x); scale_x.length = scale #scale/25.4
+ scale_y = Geom::Vector3d.new(axes.y); scale_y.length = scale #scale/25.4
+ #axes.x.length=1
+ #axes.y.length=1
+ #axes.z.length=1
+ points_3d = []# XYZ 3D coordinates
+ points_uv = []# UVQ 2D coordinates
+ [0, 1, 2, 3].each{|i|
+ v_3d = Geom::Vector3d.new; v_3d += scale_x if [1, 2].include?(i); v_3d += scale_y if [2, 3].include?(i)
+ v_uv = Geom::Vector3d.new; v_uv += axes.x if [1, 2].include?(i); v_uv += axes.y if [2, 3].include?(i)
+ points_3d << origin.offset(v_3d)
+ point_uv = origin.offset(v_uv)
+ points_uv << (front ? helper.get_front_UVQ(point_uv) : helper.get_back_UVQ(point_uv))
+ }
+
+ trans_a = Geom::Transformation.rotation(points_3d[0], face.normal, angle)
+ points = [points_3d[0], points_uv[0]]
+ (1..3).each{|i|
+ points << points_3d[i].transform(trans_a)
+ points << points_uv[i]
+ }
+ #puts points
+ face.position_material(material, points, front)
+ end
+ face.set_attribute("sw", "rt", true)#设置纹理已旋转
+ end
+
+ begin
+ if saved_color
+ face.set_attribute("sw", "ckey", saved_color)
+ face.set_attribute("sw", "scale", scale_a) if scale_a
+ face.set_attribute("sw", "angle", angle_a) if angle_a
+ end
+
+ texture = get_texture(color)
+ face.material = texture
+ if back_material or texture.alpha < 1
+ face.back_material = texture
+ else
+ face.back_material = nil
+ end
+
+ if face.get_attribute("sw", "ckey") == color
+ scale = face.get_attribute("sw", "scale")
+ angle = face.get_attribute("sw", "angle")
+ if (scale || angle) && face.get_attribute("sw", "rt").nil?#纹理是否已旋转(反复贴图只需首次旋转)
+ rotate_texture(face, scale, angle, true)
+ if back_material or texture.alpha < 1
+ rotate_texture(face, scale, angle, false)
+ end
+ end
+ end
+ rescue => e
+ puts e.backtrace
+ end
+ end
+
+ def textured_hw(hw, selected)
+ return if hw.class == Sketchup::ComponentInstance
+ @selected_hws << hw if selected
+
+ color = selected ? "mat_select" : hw.get_attribute("sw", "ckey")
+ texture = get_texture(color)#TODO angled the texture
+ hw.entities.each{|entity|#Group are machinings
+ if entity.class == Sketchup::Face
+ entity.material = texture
+ entity.back_material = texture
+ end
+ }
+ end
+
+ def c18(data) #hide_door
+ @door_layer.visible = !data.fetch("v")
+ #@drawer_layer.visible= !params.fetch("v")
+ @door_labels.visible = !data.fetch("v") if @door_labels.valid?
+ end
+
+ def c28(data) #hide_drawer
+ #@door_layer.visible = !params.fetch("v")
+ @drawer_layer.visible= !data.fetch("v")
+ @door_labels.visible = !data.fetch("v") if @door_labels.valid?
+ end
+
+ def show_message(data)
+ Sketchup.set_status_text(data.fetch("message"))
+ end
+
+ def c0f(data) #view_front
+ Sketchup.send_action "viewFront:"
+ Sketchup.send_action "viewZoomExtents:"
+ Sketchup.active_model.active_view.invalidate
+ end
+
+ def c23(data) #view_left
+ Sketchup.send_action "viewLeft:"
+ Sketchup.send_action "viewZoomExtents:"
+ Sketchup.active_model.active_view.invalidate
+ end
+
+ def c24(data) #view_right
+ Sketchup.send_action "viewRight:"
+ Sketchup.send_action "viewZoomExtents:"
+ Sketchup.active_model.active_view.invalidate
+ end
+
+ def c25(data) #view_back
+ Sketchup.send_action "viewBack:"
+ Sketchup.send_action "viewZoomExtents:"
+ Sketchup.active_model.active_view.invalidate
+ end
+
+ def c00(data)#add_folder
+ if data.fetch("ref_v", 0) > 0
+ SuwoodResourceLibrary::FolderPathData.init_folder_data(data)
+ end
+ end
+
+ def c01(data) #edit_unit, 仅当场景中创建时有效
+ unit_id = data.fetch("unit_id")
+ if data.key?("params")
+ params = data.fetch("params")
+ if params.key?("trans")
+ jtran = params.delete("trans")
+ trans = Geom::Transformation::parse(jtran)
+ @unit_trans.store(unit_id, trans)
+ sleep(0.5)
+ end
+ if @unit_param.key?(unit_id)
+ values = @unit_param.fetch(unit_id)
+ params.each{|key, value| values.store(key, value)}
+ params = values
+ end
+ @unit_param.store(unit_id, params)
+ end
+ end
+
+ def c14(data) #pre_save_pixmap
+ sel_clear
+ c0c(data) #del_dim
+ c0a(data) #del_machining
+ zones = get_zones(data)
+ zones.values.each{|zone|
+ if zone && zone.valid?#当前unit所有zone
+ zone.hidden = true
+ end
+ }
+ Sketchup.send_action "viewFront:"
+ Sketchup.send_action 10513#X透光
+ Sketchup.send_action "renderTextures:"
+ Sketchup.active_model.active_view.zoom_extents
+ Sketchup.active_model.active_view.invalidate
+ end
+
+ def c13(data) #save_pixmap
+ uid = data.fetch("uid")
+ path = data.fetch("path")
+ keys = {
+ :filename => path,
+ :width => 320,
+ :height => 320, #240
+ :antialias => true,
+ :compression => 1.0,
+ :transparent => false
+ }
+ Sketchup.active_model.active_view.write_image(keys)
+
+ if data.fetch("batch", nil)
+ c09(data) #del_entity
+ Sketchup.send_action 10513#X透光
+ Sketchup.send_action "renderTextures:"
+ end
+ params = {:uid => uid}
+ set_cmd("r03", params) #"finish_pixmap"
+ end
+
+ def follow_me(container, surface, path, color, scale = nil, angle = nil, reverse_face = true, series = nil, saved_color = nil)#path is an edge or an array of edge
+ face = create_face(container, surface, color, scale, angle, series, reverse_face, @back_material, saved_color)
+ normal = face.normal.normalize
+
+ face.followme(path)
+ container.entities.each{|myentity|
+ if myentity.class == Sketchup::Edge
+ myentity.hidden = true
+ end
+ }
+ if path.class == Sketchup::Edge
+ container.entities.erase_entities(path) if path.valid?
+ else
+ path.each{|p|container.entities.erase_entities(p) if p.valid?}
+ end
+
+ normal
+ end
+
+ def create_face(container, surface, color = nil, scale = nil, angle = nil, series = nil, reverse_face = false, back_material = true, saved_color = nil, type = nil)
+ begin
+ segs = surface.fetch("segs")
+ edges = create_edges(container, segs, series)
+ face = container.entities.add_face(edges)
+ raise "无法创建Sketchup面对象!" if face.nil?
+
+ zaxis = Geom::Vector3d::parse(surface.fetch("vz"))
+ if series #part surf
+ xaxis = Geom::Vector3d::parse(surface.fetch("vx"))
+
+ if face.normal.samedirection?(zaxis) == reverse_face
+ face = face.reverse!
+ #axes = face.normal.axes
+ #puts "axes nil" if axes.nil?
+ #puts "done\t#{reverse_face ? "true":"false"}\t#{surface.fetch("p")}\t#{segs[0][0]}\t#{surface.fetch("vx")}\t#{surface.fetch("vz")}\t#{axes[0]}\t#{axes[2]}\t#{angle}\t#{xaxis.angle_between(axes[0])}"
+ else
+ #axes = face.normal.axes
+ #puts "axes nil" if axes.nil?
+ #puts "none\t#{reverse_face ? "true":"false"}\t#{surface.fetch("p")}\t#{segs[0][0]}\t#{surface.fetch("vx")}\t#{surface.fetch("vz")}\t#{axes[0]}\t#{axes[2]}\t#{angle}\t#{xaxis.angle_between(axes[0])}"
+ end
+ elsif reverse_face && face.normal.samedirection?(zaxis)
+ face = face.reverse!
+ end
+
+ face.set_attribute("sw", "typ", type) unless type.nil?
+ if color
+ textured_surf(face, back_material, color, saved_color, scale, angle)
+ else
+ textured_surf(face, back_material, "mat_normal")
+ end
+
+ face#return
+ rescue => e
+ segs.each_index{|index|
+ segs[index].each{|point| puts point}
+ }
+ puts e.backtrace
+ end
+ end
+
+ #用于创建当前ContourSurface中所有ContourSegments的SU Edges
+ def create_edges(container, segments, series = nil)
+ entities = container.entities
+ seg_pts = Hash.new
+ segments.each_index{|index|
+ pts = []
+ segments[index].each{|point| pts << Geom::Point3d.parse(point)}
+ seg_pts.store(index, pts)
+ }
+ edges = []; segments.each_index{|this_i|
+ pts_i = seg_pts.fetch(this_i)
+ pts_p = seg_pts.fetch(this_i == 0 ? segments.size - 1 : this_i - 1)
+ pts_n = seg_pts.fetch(this_i == segments.size - 1 ? 0 : this_i + 1)
+ if pts_i.size > 2
+ if pts_p.size > 2
+ prev_p = pts_p[-1]
+ this_p = pts_i[ 0]
+ if prev_p != this_p
+ edges << entities.add_line(prev_p, this_p)
+ end
+ end
+ edges.concat(entities.add_edges(pts_i))
+ series << pts_i if series
+ else
+ point_s = pts_p.size > 2 ? pts_p[-1] : pts_i[ 0]
+ point_e = pts_n.size > 2 ? pts_n[ 0] : pts_i[-1]
+ edges << entities.add_line(point_s, point_e)
+ series << [point_s, point_e] if series
+ end
+ }
+ edges
+ end
+
+ def create_paths(container, segments)
+ entities = container.entities
+ edges = []; segments.each{|seg|
+ if seg.fetch("c", nil).nil?
+ s = Geom::Point3d.parse(seg.fetch("s"))
+ e = Geom::Point3d.parse(seg.fetch("e"))
+ edges << entities.add_line(s, e)
+ else
+ c = Geom::Point3d.parse(seg.fetch("c"))
+ x = Geom::Vector3d.parse(seg.fetch("x"))
+ z = Geom::Vector3d.parse(seg.fetch("z"))
+ r = seg.fetch("r")
+ a1 = seg.fetch("a1")
+ a2 = seg.fetch("a2")
+ n = seg.fetch("n")
+ edges.concat(entities.add_arc(c, x, z, r, a1, a2, n))
+ end
+ }
+ edges
+ end
+
+ def scaled_start
+ if @@scaled_zone || @@selected_zone.nil?
+ return
+ end
+
+ select = @@selected_zone
+ if select.valid?
+ uid = select.get_attribute("sw", "uid")
+ zid = select.get_attribute("sw", "zid")
+ set_children_hidden(uid, zid)#当前所有区域不可见
+
+ @@scaled_zone = select.copy
+ @@scaled_zone.hidden = false
+ @@scaled_zone.make_unique
+ @@scaled_zone.definition.behavior.no_scale_mask = 120#限制除X/Y/Z三个方向外的其他方向拉伸
+ end
+ end
+
+ def scaled_finish
+ if @@scaled_zone.nil?
+ return
+ end
+
+ bounds = @@scaled_zone.bounds
+ select = @@selected_zone
+ uid = select.get_attribute("sw", "uid")
+ zid = select.get_attribute("sw", "zid")
+ if bounds.min != select.bounds.min || bounds.max != select.bounds.max
+ points = []
+ for i in 0..7
+ points << bounds.corner(i).to_s("mm", 1)
+ end
+ params = {}
+ params.store("method", SUZoneResize)
+ params.store("uid", uid)
+ params.store("zid", zid)
+ params.store("points", points)
+ set_cmd("r00", params)
+ else
+ data = {}
+ data.store("uid", uid)
+ data.store("zid", zid)
+ sel_zone_local(data)
+ end
+ @@scaled_zone.erase!
+ @@scaled_zone = nil
+ end
+
+ def self.selected_uid
+ @@selected_uid
+ end
+
+ def self.selected_zone
+ @@selected_zone
+ end
+
+ def self.selected_part
+ @@selected_part
+ end
+
+ def self.selected_obj
+ @@selected_obj
+ end
+
+ def self.server_path
+ @@server_path
+ end
+
+ def self.default_zone
+ @@default_zone
+ end
+ end
+end
\ No newline at end of file
diff --git a/ruby/ruby/SUWLoad.rb b/ruby/ruby/SUWLoad.rb
new file mode 100644
index 0000000..50dfe39
--- /dev/null
+++ b/ruby/ruby/SUWLoad.rb
@@ -0,0 +1,12 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+
+Sketchup::require('SUWConstants')
+Sketchup::require('SUWImpl')
+Sketchup::require('SUWClient')
+Sketchup::require('SUWObserver')
+Sketchup::require('SUWUnitPointTool')
+Sketchup::require('SUWUnitFaceTool')
+Sketchup::require('SUWUnitContTool')
+Sketchup::require('SUWZoneDiv1Tool')
+Sketchup::require('SUWMenu')
+
diff --git a/ruby/ruby/SUWMenu.rb b/ruby/ruby/SUWMenu.rb
new file mode 100644
index 0000000..c9d8c5a
--- /dev/null
+++ b/ruby/ruby/SUWMenu.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+module SUWood
+ # 创建菜单
+ unless file_loaded?(__FILE__) # 只有加载一次
+
+ SUWimpl.instance.startup
+ Sketchup.break_edges = false
+ Sketchup.active_model.selection.add_observer(SUWSelObserver.new)
+ Sketchup.active_model.tools.add_observer(SUWToolsObserver.new)
+ Sketchup.add_observer(SUWAppObserver.new)
+ UI.add_context_menu_handler{|menu|
+ selection = Sketchup.active_model.selection
+ faces = selection.select{|e| e.class == Sketchup::Face}
+ unless faces.length == 1
+ menu.add_item("创建轮廓") {
+ json = faces[0].to_json
+ if json.empty?
+ UI.messagebox("没有选取图形!")
+ else
+ set_cmd("r02", json)#"create_contour"
+ end
+ }
+
+ if SUWimpl.instance.added_contour
+ menu.add_item("取消轮廓") {
+ SUWimpl.instance.added_contour = false
+ set_cmd("r02", {:segs => []}) #"create_contour"
+ }
+ end
+ end
+ }
+
+ # root_toolbar = UI.toolbar("SUWood")
+ #
+ # cmd1 = UI::Command.new("点击创体") {
+ # SUWood::SUWUnitPointTool.set_box
+ # }
+ # cmd1.tooltip = '点击创体' # 设置提示文字
+ # cmd1.small_icon = icon_path('unit_point', 'png')
+ # cmd1.large_icon = icon_path('unit_point', 'png')
+ # root_toolbar.add_item(cmd1) # 添加按钮
+ #
+ # cmd2 = UI::Command.new("选面创体") {
+ # Sketchup.active_model.select_tool(SUWood::SUWUnitFaceTool.new)
+ # }
+ # cmd2.tooltip = '选面创体' # 设置提示文字
+ # cmd2.small_icon = icon_path('unit_face', 'png')
+ # cmd2.large_icon = icon_path('unit_face', 'png')
+ # root_toolbar.add_item(cmd2) # 添加按钮
+ #
+ # cmd3 = UI::Command.new('删除柜体'){
+ # SUWood::delete_unit
+ # }
+ # cmd3.small_icon = icon_path('unit_delete', 'png')
+ # cmd3.large_icon = icon_path('unit_delete', 'png')
+ # cmd3.tooltip = '删除柜体'
+ # root_toolbar.add_item(cmd3)
+ #
+ # cmd4 = UI::Command.new('六面切割'){
+ # Sketchup.active_model.select_tool(SUWood::SWZoneDiv1Tool.new)
+ # }
+ # cmd4.small_icon = icon_path('zone_div1', 'png')
+ # cmd4.large_icon = icon_path('zone_div1', 'png')
+ # cmd4.tooltip = '六面切割'
+ # root_toolbar.add_item(cmd4)
+ #
+ # root_toolbar.show # 展示
+
+ file_loaded(__FILE__)
+ end
+end
\ No newline at end of file
diff --git a/ruby/ruby/SUWObserver.rb b/ruby/ruby/SUWObserver.rb
new file mode 100644
index 0000000..bf3065a
--- /dev/null
+++ b/ruby/ruby/SUWObserver.rb
@@ -0,0 +1,87 @@
+module SUWood
+ class SUWToolsObserver < Sketchup::ToolsObserver
+ @@cloned_zone = nil
+
+ def onActiveToolChanged(tools, tool_name, tool_id)
+ #puts "onActiveToolChanged: #{tool_id}\t#{tool_name}"
+ # 21048 = MoveTool
+ # 21129 = RotateTool
+ if tool_id == 21236#ScaleTool
+ SUWimpl.instance.scaled_start
+ else
+ SUWimpl.instance.scaled_finish
+ end
+ end
+ end
+
+ class SUWSelObserver < Sketchup::SelectionObserver
+ def onSelectionBulkChange(selection)
+ if selection.length <= 0#1次点击, 多次调用
+ return
+ end
+
+ suw_objs = selection.select{|e|
+ e.valid? && (e.class == Sketchup::Group || e.class == Sketchup::ComponentInstance) && e.get_attribute("sw", "uid", nil)
+ }
+
+ if suw_objs.empty?
+ if Sketchup.active_model.get_attribute("sw", "order_id", nil) && SUWimpl.selected_uid
+ set_cmd("r01", {})#没有柜体/区域参数, 切换到订单编辑界面
+ end
+
+ SUWimpl.instance.sel_clear#清除数据
+ elsif suw_objs.length == 1#每次选择1个SUWood对象, 后续如实现多选则由SUWood自己管理
+ selection.clear
+ SUWimpl.instance.sel_local(suw_objs[0])
+ end
+ end
+ end
+
+ #新建1个模型或打开其他模型前, SU提示是否保存当前模型时:
+ #如选择是则保存相应的订单及柜体
+ #如选择否则取消相应订单更改(包含在新建或打开事件中)
+ # FIXME:TODO:草图自动保存时也会调用
+ class SUWModelObserver < Sketchup::ModelObserver
+ def onSaveModel(model)
+ order_id = model.get_attribute("sw", "order_id", nil)
+ return if order_id.nil?
+
+ params = {}
+ params.store("method", SUSceneSave)
+ params.store("order_id", order_id)
+ set_cmd("r00", params)
+ end
+ end
+
+ class SUWAppObserver < Sketchup::AppObserver
+ def onNewModel(model)
+ SUWimpl.instance.startup
+ # model.add_observer(SUWModelObserver.new)
+ model.tools.add_observer(SUWToolsObserver.new)
+ model.selection.add_observer(SUWSelObserver.new)
+
+ params = {}
+ params.store("method", SUSceneNew)
+ set_cmd("r00", params)
+ end
+
+ def onOpenModel(model)
+ SUWimpl.instance.startup
+ # model.add_observer(SUWModelObserver.new)
+ model.tools.add_observer(SUWToolsObserver.new)
+ model.selection.add_observer(SUWSelObserver.new)
+
+ order_id = model.get_attribute("sw", "order_id", nil)
+ model.entities.each {|entity|
+ if entity.class == Sketchup::Group && entity.valid? && entity.get_attribute("sw", "uid", nil)
+ entity.erase!
+ end
+ } unless order_id.nil?
+
+ params = {}
+ params.store("method", SUSceneOpen)
+ params.store("order_id", order_id) unless order_id.nil?
+ set_cmd("r00", params)
+ end
+ end
+end
\ No newline at end of file
diff --git a/ruby/ruby/SUWUnitContTool.rb b/ruby/ruby/SUWUnitContTool.rb
new file mode 100644
index 0000000..bb4ff3b
--- /dev/null
+++ b/ruby/ruby/SUWUnitContTool.rb
@@ -0,0 +1,137 @@
+module SUWood
+ class SUWUnitContTool
+ def self.set_type(cont_type)
+ if cont_type == VSUnitCont_Zone
+ select = SUWimpl.selected_zone
+ if select.nil? || select.deleted?
+ Sketchup::set_status_text('请选择区域'); return
+ end
+ uid = select.get_attribute("sw", "uid")
+ oid = select.get_attribute("sw", "zid")
+ cp = -1
+ else
+ select = SUWimpl.selected_part
+ if select.nil? || select.deleted?
+ Sketchup::set_status_text('请选择部件'); return
+ end
+ uid = select.get_attribute("sw", "uid")
+ oid = select.get_attribute("sw", "pid")
+ cp = select.get_attribute("sw", "cp")
+ end
+ Sketchup.active_model.select_tool(new(cont_type, select, uid, oid, cp))
+ end
+
+ def initialize(cont_type, select, uid, oid, cp = -1)
+ @cont_type, @uid, @oid, @cp = cont_type, uid, oid, cp
+ @select = select
+ if cont_type == VSUnitCont_Zone
+ @tooltip = '请选择区域的面, 并指定对应的轮廓'
+ else#VSUnitCont_Work
+ @tooltip = '请选择板件的面, 并指定对应的轮廓'
+ end
+ end
+
+ def activate
+ Sketchup.set_status_text(@tooltip)
+ end
+
+ def onMouseMove(flags, x, y, view)
+ @ref_face = nil
+ @face_segs = nil
+ ref_face = nil
+ xypicker = view.pick_helper
+ xypicker.do_pick(x, y)
+ xypicker.count.times do |i|
+ path = xypicker.path_at(i)
+ if path[-1].class == Sketchup::Face && !@select.entities.include?(path[-1])
+ ref_face = path[-1]
+ end
+ end
+ if ref_face
+ face_pts = ref_face.outer_loop.vertices.map(&:position)
+ @ref_face = ref_face
+ @face_segs = face_pts.zip(face_pts.rotate)
+ view.invalidate
+ end
+ Sketchup.set_status_text(@tooltip)
+ view.invalidate
+ end
+
+ def onLButtonDown(flags, x, y, view)
+ if @ref_face.nil?
+ UI.messagebox('请选择轮廓')
+ return
+ end
+
+ myself = false
+ depth = 0
+ arced = true
+ case @cont_type
+ when VSUnitCont_Zone then
+ return if UI.messagebox('是否确定创建区域轮廓?', MB_YESNO) == IDNO
+ when VSUnitCont_Part
+ return if UI.messagebox('是否确定创建部件轮廓?', MB_YESNO) == IDNO
+ when VSUnitCont_Work
+ arcs = @ref_face.edges.select{|edge| edge.curve.is_a?(Sketchup::ArcCurve)}
+ if arcs.empty?
+ prompts = ["表面", "深度"]
+ values = ["当前", 0]
+ options = ["当前|反面", ""]
+ inputs = UI.inputbox(prompts, values, options, '挖洞轮廓')
+ return if inputs == false
+
+ myself = inputs[0] == "当前"
+ depth = inputs[1] if inputs[1] > 0
+ else
+ prompts = ["表面", "深度", "圆弧"]
+ values = ["当前", 0, "圆弧"]
+ options = ["当前|反面", "", "圆弧|多段线"]
+ inputs = UI.inputbox(prompts, values, options, '挖洞轮廓')
+ return if inputs == false
+
+ myself = inputs[0] == "当前"
+ depth = inputs[1] if inputs[1] > 0
+ arced = inputs[2] == "圆弧"
+ end
+ end
+
+ params = {}
+ params.store("method", SUUnitContour)
+ params.store("type", @cont_type)
+ params.store("uid", @uid)
+ params.store("oid", @oid)
+ params.store("cp", @cp)
+ params.store("face", @ref_face.to_json(nil, 1, arced))
+ params.store("self", myself)
+ params.store("depth", depth)
+ set_cmd("r00", params)
+
+ edges = []
+ @ref_face.edges.each {|edge|
+ if edge.faces.length == 1
+ edges << edge
+ end
+ }
+ @ref_face.erase!
+ @ref_face = nil
+ edges.each {|edge|
+ if edge.valid?
+ edge.erase!
+ end
+ }
+
+ @face_segs = nil
+ view.invalidate
+ Sketchup.active_model.selection.clear
+ Sketchup.active_model.select_tool(nil)
+ end
+
+ def draw(view)
+ if @face_segs
+ view.drawing_color = Sketchup::Color.new(0, 255, 255)
+ view.line_width = 3
+ view.draw2d(GL_LINES, @face_segs.flat_map{|seg| seg.map{|pt| view.screen_coords(pt)}})
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/ruby/ruby/SUWUnitFaceTool.rb b/ruby/ruby/SUWUnitFaceTool.rb
new file mode 100644
index 0000000..a11cafe
--- /dev/null
+++ b/ruby/ruby/SUWUnitFaceTool.rb
@@ -0,0 +1,145 @@
+module SUWood
+ class SUWUnitFaceTool
+ def initialize(cont_view, source = nil, mold = false)
+ @cont_view, @source, @mold = cont_view, source, mold
+ @tooltip = '请点击要创体的面'
+ end
+
+ def activate
+ Sketchup.set_status_text(@tooltip)
+ end
+
+ def onMouseMove(flags, x, y, view)
+ @ref_face = nil
+ @trans_arr = nil
+ @face_segs = nil
+
+ pickray = view.pickray(x, y)
+ result = Sketchup.active_model.raytest(pickray)
+ if result#FIXME:TODO:墙面支持圆弧
+ path_arr = result[1]
+ ref_face = path_arr.reverse.find{|e| e.is_a?(Sketchup::Face)}
+ if face_valid?(ref_face)
+ face_pts = ref_face.outer_loop.vertices.map(&:position)
+ face_idx = path_arr.find_index(ref_face)
+ if face_idx > 0
+ trans_arr = path_arr[0..face_idx-1].map(&:transformation).reverse!
+ trans_arr.each{|trans| face_pts.each{|pt| pt.transform!(trans)}}
+ @trans_arr = trans_arr
+ end
+ @ref_face = ref_face
+ @face_segs = face_pts.zip(face_pts.rotate)
+ end
+ end
+ Sketchup.set_status_text(@tooltip)
+ view.invalidate
+ end
+
+ def onLButtonDown(flags, x, y, view)
+ if @ref_face.nil?#当前点击是否选择了face
+ xypicker = view.pick_helper(x, y)
+ ref_face = xypicker.picked_face
+ if face_valid?(ref_face)
+ face_pts = ref_face.outer_loop.vertices.map(&:position)
+ @ref_face = ref_face
+ @face_segs = face_pts.zip(face_pts.rotate)
+ view.invalidate
+ end
+ end
+
+ if @ref_face.nil?
+ UI.messagebox('请选择要放置的面')
+ return
+ end
+
+ caption = ""
+ default = 0
+ case @cont_view
+ when VSSpatialPos_F then caption = '深(mm)'; default = 600
+ when VSSpatialPos_R then caption = '宽(mm)'; default = 800
+ when VSSpatialPos_T then caption = '高(mm)'; default = 800
+ else
+ end
+ prompts = ['距左', '距右', '距上', '距下', caption, "重叠"]
+ values = [0, 0, 0, 0, default, "合并"]
+ options = ["", "", "", "", "", "合并|截除"]
+ inputs = UI.inputbox(prompts, values, options, '选面创体')
+ return if inputs == false || inputs[4] < 100
+
+ order_id = Sketchup.active_model.get_attribute("sw", "order_id", nil)
+ fronts = []
+ if @cont_view == VSSpatialPos_T
+ @ref_face.outer_loop.edges.each{|edge|
+ next if edge.curve
+ faces = edge.faces.select{|face| face.normal.perpendicular?(Z_AXIS)}
+ if faces.empty?
+ p1 = edge.start.position
+ p2 = edge.end.position
+ @trans_arr.each{|trans|
+ p1.transform!(trans)
+ p2.transform!(trans)
+ } if @trans_arr
+ seg = Hash.new
+ seg.store("s", p1.to_s("mm", 1))
+ seg.store("e", p2.to_s("mm", 1))
+ fronts << seg
+ end
+ }
+ end
+
+ params = {}
+ params.store("view", @cont_view)
+ params.store("face", @ref_face.to_json(@trans_arr, 1))
+ params.store("left", inputs[0]) if inputs[0] > 0
+ params.store("right", inputs[1]) if inputs[1] > 0
+ params.store("top", inputs[2]) if inputs[2] > 0
+ params.store("bottom", inputs[3]) if inputs[3] > 0
+ params.store("size", inputs[4])
+ params.store("merged", true) if inputs[5] == "合并"
+ params.store("source", @source) unless @source.nil?
+ params.store("module", @mold) if @mold
+ params.store("fronts", fronts) if fronts.length > 0
+
+ data = {}
+ data.store("method", SUUnitFace)
+ data.store("order_id", order_id) unless order_id.nil?
+ data.store("params", params)
+ set_cmd("r00", data)
+
+ edges = []
+ @ref_face.edges.each {|edge|
+ if edge.faces.length == 1
+ edges << edge
+ end
+ }
+ @ref_face.erase!
+ @ref_face = nil
+ edges.each {|edge|
+ if edge.valid?
+ edge.erase!
+ end
+ }
+
+ @trans_arr = nil
+ @face_segs = nil
+ view.invalidate
+ Sketchup.active_model.selection.clear
+ Sketchup.active_model.select_tool(nil)
+ end
+
+ def draw(view)
+ if @face_segs
+ view.drawing_color = Sketchup::Color.new(0, 255, 255)
+ view.line_width = 3
+ view.draw2d(GL_LINES, @face_segs.flat_map{|seg| seg.map{|pt| view.screen_coords(pt)}})
+ end
+ end
+
+ def face_valid?(face)
+ face &&
+ ( @cont_view == VSSpatialPos_F && face.normal.perpendicular?(Z_AXIS) ||
+ @cont_view == VSSpatialPos_R && face.normal.perpendicular?(Z_AXIS) ||
+ @cont_view == VSSpatialPos_T && face.normal.parallel?(Z_AXIS))
+ end
+ end
+end
diff --git a/ruby/ruby/SUWUnitPointTool.rb b/ruby/ruby/SUWUnitPointTool.rb
new file mode 100644
index 0000000..626a8cb
--- /dev/null
+++ b/ruby/ruby/SUWUnitPointTool.rb
@@ -0,0 +1,128 @@
+module SUWood
+ class SUWUnitPointTool
+ def self.set_box
+ prompts = ['宽(mm):', '深(mm):', '高(mm):']
+ defaults = [1200, 600, 800]
+ input = UI.inputbox(prompts, defaults, '输入柜体的三维尺寸')
+ return unless input
+ x_len, y_len, z_len = input.map { |n| n.mm.to_f }
+ Sketchup.active_model.select_tool(new(x_len, y_len, z_len))
+ end
+
+ def initialize(x_len, y_len, z_len, source = nil, mold = false)
+ @x_len, @y_len, @z_len, @source, @mold = x_len, y_len, z_len, source, mold
+ @z_rotation, @x_rotation = 0, 0
+ @current_point = ORIGIN
+ front_pts = [ORIGIN]
+ front_pts << Geom::Point3d.new(x_len, 0, 0)
+ front_pts << Geom::Point3d.new(x_len, 0, z_len)
+ front_pts << Geom::Point3d.new(0, 0, z_len)
+ back_vec = Geom::Vector3d.new(0, y_len, 0)
+ back_pts = front_pts.map { |pt| pt.offset(back_vec) }
+ @front_face = front_pts
+ @box_segs = front_pts.zip(back_pts)
+ @box_segs.concat(front_pts.zip(front_pts.rotate))
+ @box_segs.concat(back_pts.zip(back_pts.rotate))
+ @cursor_id = UI.create_cursor(SUWood.icon_path('cursor_move', 'svg'), 10, 10)
+ @tooltip = '按Ctrl键切换柜体朝向'
+ end
+
+ def activate
+ main_window_focus
+ end
+
+ def deactivate(view) end
+
+ def onSetCursor
+ UI.set_cursor(@cursor_id)
+ end
+
+ def onCancel(reason, view)
+ Sketchup.active_model.select_tool(nil)
+ end
+
+ def onMouseMove(flags, x, y, view)
+ @ip = view.inputpoint(x + 10, y - 5)
+ @current_point = @ip.position
+ view.invalidate
+ end
+
+ def onLButtonDown(flags, x, y, view)
+ @ip = view.inputpoint(x + 10, y - 5)
+ @current_point = @ip.position
+ view.invalidate
+ Sketchup.active_model.selection.clear
+ Sketchup.active_model.select_tool(nil)
+
+ order_id = Sketchup.active_model.get_attribute("sw", "order_id", nil)
+ trans = get_current_trans
+
+ params = {}
+ params.store("width", @x_len.to_mm)
+ params.store("depth", @y_len.to_mm)
+ params.store("height", @z_len.to_mm)
+ params.store("source", @source) unless @source.nil?
+ params.store("module", @mold) if @mold
+ trans.store(params)
+
+ data = {}#uid/folder_id为0
+ data.store("method", SUUnitPoint)
+ data.store("order_id", order_id) unless order_id.nil?
+ data.store("params", params)
+ set_cmd("r00", data)
+ end
+
+ def draw(view)
+ @ip.draw(view) if @ip && @ip.valid? && @ip.display?
+ tr = get_current_trans
+ box_segs = @box_segs.map { |seg| seg.map { |pt| pt.transform(tr) } }
+ front_face = @front_face.map { |pt| pt.transform(tr) }
+
+ view.line_width = 1
+ view.drawing_color = Sketchup::Color.new(0, 0, 0)
+ view.draw(GL_LINES, box_segs.flatten(1))
+
+ view.drawing_color = Sketchup::Color.new(255, 0, 0, 0.3)
+ view.draw(GL_QUADS, front_face) # 参考面
+ Sketchup::set_status_text(@tooltip)
+ end
+
+ def getExtents
+ tr = get_current_trans
+ box_segs = @box_segs.map { |seg| seg.map { |pt| pt.transform(tr) } }
+ bb = Geom::BoundingBox.new
+ bb.add(box_segs.flatten)
+ bb.add(@ip.position) if @ip && @ip.valid?
+ bb
+ end
+
+ def onKeyUp(key, rpt, flags, view)
+ case key
+ when VK_CONTROL
+ @z_rotation -= 1
+ view.invalidate
+ end
+ end
+
+ def get_current_trans
+ trans = Geom::Transformation::translation(@current_point)
+ if @z_rotation != 0
+ origin = trans.origin
+ angle = @z_rotation * Math::PI * 0.5
+ trans = Geom::Transformation.rotation(origin, Z_AXIS, angle) * trans
+ end
+ if @x_rotation != 0
+ origin = trans.origin
+ angle = @x_rotation * Math::PI * 0.5
+ trans = Geom::Transformation.rotation(origin, X_AXIS, angle) * trans
+ end
+ trans
+ end
+
+ def main_window_focus
+ dlg = UI::WebDialog.new("shink_mwf", true, "shink_mwf", 0, 0, 10000, 10000, true)
+ dlg.show
+ dlg.close
+ end
+ end
+end
diff --git a/ruby/ruby/SUWZoneDiv1Tool.rb b/ruby/ruby/SUWZoneDiv1Tool.rb
new file mode 100644
index 0000000..81d6756
--- /dev/null
+++ b/ruby/ruby/SUWZoneDiv1Tool.rb
@@ -0,0 +1,106 @@
+module SUWood
+ class SWZoneDiv1Tool
+ def initialize
+ @pattern = :up
+ reset_status_text
+ end
+
+ def activate
+ Sketchup.set_status_text(@tooltip)
+ Sketchup.active_model.selection.clear
+ end
+
+ def resume(view)
+ Sketchup.set_status_text(@tooltip)
+ end
+
+ def reset_status_text
+ @tooltip = "选择一个要分割的区域, "
+ if @pattern == :up
+ @tooltip << "按方向键进行上下左右分割"
+ else
+ @tooltip << "按方向键上下进行前后分割"
+ end
+ @tooltip << ", 按ctrl键可切换模式"
+ Sketchup.set_status_text(@tooltip)
+ end
+
+ def divide(dir)
+ selected_zone = SUWimpl.selected_zone
+ if selected_zone.nil?
+ UI.messagebox("请先选择要分割的区域!")
+ return
+ end
+
+ dir_name =
+ case dir
+ when VSSpatialPos_T then '上'
+ when VSSpatialPos_B then '下'
+ when VSSpatialPos_L then '左'
+ when VSSpatialPos_R then '右'
+ when VSSpatialPos_F then '前'
+ when VSSpatialPos_K then '后'
+ end
+
+ input = UI.inputbox(["#{dir_name}分割(mm)"], [""], '区域分割')
+ return unless input
+
+ len = input[0].to_i.mm
+ if len <= 0
+ UI.messagebox("输入数值小于等于0!")
+ return
+ end
+
+ params = {}
+ params.store("method", SUZoneDiv1)
+ params.store("uid", selected_zone.get_attribute("sw","uid"))
+ params.store("zid", selected_zone.get_attribute("sw","zid"))
+ params.store("dir", dir)
+ params.store("len", len.to_mm)
+ set_cmd("r00", params)
+ end
+
+ def onLButtonDown(flags, x, y, view)
+ helper = view.pick_helper
+ helper.do_pick(x, y)
+ picked = helper.best_picked
+ if picked.class == Sketchup::Group && picked.valid? && picked.get_attribute("sw", "uid", nil)
+ uid = picked.get_attribute("sw", "uid")
+ zid = picked.get_attribute("sw", "zid")
+ typ = picked.get_attribute("sw", "typ")
+ if typ == "zid" && SUWimpl.selected_zone != picked
+ data = {}
+ data.store("uid", uid)
+ data.store("zid", zid)
+ data.store("pid", -1)
+ data.store("cp", -1)
+ set_cmd("r01", data)#select_client
+ SUWimpl.instance.sel_zone_local(data)
+ end
+ end
+ Sketchup.active_model.selection.clear
+ end
+
+ def onKeyDown(key, rpt, flags, view)
+ if key == VK_CONTROL#切换分割模式
+ @pattern = @pattern == :up ? :back : :up
+ reset_status_text
+ end
+ end
+
+ def onKeyUp(key, rpt, flags, view)
+ case key
+ when VK_UP
+ @pattern == :back ? divide(VSSpatialPos_K) : divide(VSSpatialPos_T)
+ when VK_DOWN
+ @pattern == :back ? divide(VSSpatialPos_F) : divide(VSSpatialPos_B)
+ when VK_LEFT then divide(VSSpatialPos_L) if @pattern == :up
+ when VK_RIGHT then divide(VSSpatialPos_R) if @pattern == :up
+ end
+ end
+
+ def draw(view)
+ Sketchup::set_status_text(@tooltip)
+ end
+ end
+end
diff --git a/ruby/ruby/icon/cursor_move.svg b/ruby/ruby/icon/cursor_move.svg
new file mode 100644
index 0000000..62b1184
--- /dev/null
+++ b/ruby/ruby/icon/cursor_move.svg
@@ -0,0 +1,33 @@
+
+
\ No newline at end of file
diff --git a/ruby/ruby/icon/order_quote.png b/ruby/ruby/icon/order_quote.png
new file mode 100644
index 0000000..97d26da
Binary files /dev/null and b/ruby/ruby/icon/order_quote.png differ
diff --git a/ruby/ruby/icon/unit_delete.png b/ruby/ruby/icon/unit_delete.png
new file mode 100644
index 0000000..a9cddf8
Binary files /dev/null and b/ruby/ruby/icon/unit_delete.png differ
diff --git a/ruby/ruby/icon/unit_delete1.png b/ruby/ruby/icon/unit_delete1.png
new file mode 100644
index 0000000..5e68f8b
Binary files /dev/null and b/ruby/ruby/icon/unit_delete1.png differ
diff --git a/ruby/ruby/icon/unit_face.png b/ruby/ruby/icon/unit_face.png
new file mode 100644
index 0000000..a41eb57
Binary files /dev/null and b/ruby/ruby/icon/unit_face.png differ
diff --git a/ruby/ruby/icon/unit_face1.png b/ruby/ruby/icon/unit_face1.png
new file mode 100644
index 0000000..a9e0f9e
Binary files /dev/null and b/ruby/ruby/icon/unit_face1.png differ
diff --git a/ruby/ruby/icon/unit_point.png b/ruby/ruby/icon/unit_point.png
new file mode 100644
index 0000000..8ca8671
Binary files /dev/null and b/ruby/ruby/icon/unit_point.png differ
diff --git a/ruby/ruby/icon/unit_point1.png b/ruby/ruby/icon/unit_point1.png
new file mode 100644
index 0000000..e27450b
Binary files /dev/null and b/ruby/ruby/icon/unit_point1.png differ
diff --git a/ruby/ruby/icon/zone_div1.png b/ruby/ruby/icon/zone_div1.png
new file mode 100644
index 0000000..0519432
Binary files /dev/null and b/ruby/ruby/icon/zone_div1.png differ
diff --git a/ruby/ruby/icon/zone_div11.png b/ruby/ruby/icon/zone_div11.png
new file mode 100644
index 0000000..9141632
Binary files /dev/null and b/ruby/ruby/icon/zone_div11.png differ
diff --git a/server.py b/server.py
index e9f5e4d..5d2f2b2 100644
--- a/server.py
+++ b/server.py
@@ -2,8 +2,15 @@ import socket
import json
import threading
import os
+import sys
from datetime import datetime
+# 设置标准输出编码为 UTF-8(解决 Windows 控制台编码问题)
+if sys.platform.startswith('win'):
+ import codecs
+ sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
+ sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach())
+
class JSONSocketServer:
def __init__(self, host='localhost', port=8888):
self.host = host
diff --git a/server_safe.py b/server_safe.py
new file mode 100644
index 0000000..4a12871
--- /dev/null
+++ b/server_safe.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Socket JSON 服务器 - 编码安全版本
+解决 Windows 控制台编码问题
+"""
+
+import socket
+import json
+import threading
+import os
+import sys
+from datetime import datetime
+
+# 导入编码修复模块
+try:
+ from encoding_fix import safe_print, safe_format_message
+except ImportError:
+ # 如果导入失败,使用简单的备用函数
+ def safe_print(text, fallback=None):
+ try:
+ print(text)
+ except UnicodeEncodeError:
+ print(text.encode('ascii', errors='ignore').decode('ascii'))
+
+ def safe_format_message(text):
+ # 简单替换emoji为文本
+ replacements = {
+ "🚀": "[START]", "📍": "[ADDR]", "⏰": "[TIME]", "✅": "[OK]",
+ "🔗": "[CONN]", "📨": "[MSG]", "❌": "[ERROR]", "🔌": "[DISC]",
+ "📋": "[LIST]", "🖥️": "[SERVER]", "💻": "[CLIENT]", "📁": "[FILE]",
+ "🎯": "[RESULT]", "⚠️": "[WARN]", "📊": "[REPORT]", "⛔": "[STOP]"
+ }
+ for emoji, replacement in replacements.items():
+ text = text.replace(emoji, replacement)
+ return text
+
+class JSONSocketServerSafe:
+ def __init__(self, host='localhost', port=8888):
+ self.host = host
+ self.port = port
+ self.socket = None
+ self.clients = []
+
+ def start_server(self):
+ """启动服务器"""
+ try:
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.bind((self.host, self.port))
+ self.socket.listen(5)
+
+ safe_print("🚀 服务器启动成功!", "[START] 服务器启动成功!")
+ safe_print(f"📍 监听地址: {self.host}:{self.port}", f"[ADDR] 监听地址: {self.host}:{self.port}")
+ safe_print(f"⏰ 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
+ f"[TIME] 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ safe_print("✅ 等待客户端连接...", "[OK] 等待客户端连接...")
+
+ while True:
+ client_socket, address = self.socket.accept()
+ safe_print(f"🔗 客户端连接: {address}", f"[CONN] 客户端连接: {address}")
+
+ # 为每个客户端创建独立线程
+ client_thread = threading.Thread(
+ target=self.handle_client,
+ args=(client_socket, address)
+ )
+ client_thread.daemon = True
+ client_thread.start()
+
+ except Exception as e:
+ safe_print(f"❌ 服务器启动失败: {e}", f"[ERROR] 服务器启动失败: {e}")
+ finally:
+ if self.socket:
+ self.socket.close()
+
+ def handle_client(self, client_socket, address):
+ """处理客户端请求"""
+ try:
+ while True:
+ # 接收客户端消息
+ data = client_socket.recv(4096).decode('utf-8')
+ if not data:
+ break
+
+ safe_print(f"📨 收到来自 {address} 的消息: {data}",
+ f"[MSG] 收到来自 {address} 的消息: {data}")
+
+ # 解析客户端请求
+ try:
+ request = json.loads(data)
+ response = self.process_request(request, address)
+
+ # 发送响应
+ response_json = json.dumps(response, ensure_ascii=False, indent=2)
+ client_socket.send(response_json.encode('utf-8'))
+
+ except json.JSONDecodeError:
+ # 如果不是 JSON 格式,当作普通消息处理
+ response = {
+ "status": "success",
+ "message": f"服务器收到消息: {data}",
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+ response_json = json.dumps(response, ensure_ascii=False, indent=2)
+ client_socket.send(response_json.encode('utf-8'))
+
+ except Exception as e:
+ safe_print(f"❌ 处理客户端 {address} 时出错: {e}",
+ f"[ERROR] 处理客户端 {address} 时出错: {e}")
+ finally:
+ client_socket.close()
+ safe_print(f"🔌 客户端 {address} 已断开连接",
+ f"[DISC] 客户端 {address} 已断开连接")
+
+ def process_request(self, request, address):
+ """处理客户端的 JSON 请求"""
+ command = request.get('command', 'unknown')
+
+ if command == 'get_file':
+ # 发送文件内容
+ filename = request.get('filename', 'test_data.json')
+ return self.send_file(filename, address)
+
+ elif command == 'save_file':
+ # 保存文件
+ filename = request.get('filename', 'received_data.json')
+ content = request.get('content', {})
+ return self.save_file(filename, content, address)
+
+ elif command == 'list_files':
+ # 列出当前目录的 JSON 文件
+ json_files = [f for f in os.listdir('.') if f.endswith('.json')]
+ return {
+ "status": "success",
+ "command": "list_files",
+ "files": json_files,
+ "count": len(json_files),
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+
+ elif command == 'ping':
+ # 心跳测试
+ return {
+ "status": "success",
+ "command": "ping",
+ "message": "pong",
+ "server_time": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+
+ else:
+ # 未知命令
+ return {
+ "status": "error",
+ "message": f"未知命令: {command}",
+ "available_commands": ["get_file", "save_file", "list_files", "ping"],
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+
+ def send_file(self, filename, address):
+ """发送文件内容"""
+ try:
+ if os.path.exists(filename):
+ with open(filename, 'r', encoding='utf-8') as f:
+ content = json.load(f)
+
+ return {
+ "status": "success",
+ "command": "get_file",
+ "filename": filename,
+ "content": content,
+ "size": os.path.getsize(filename),
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+ else:
+ return {
+ "status": "error",
+ "command": "get_file",
+ "message": f"文件 {filename} 不存在",
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+ except Exception as e:
+ return {
+ "status": "error",
+ "command": "get_file",
+ "message": f"读取文件失败: {str(e)}",
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+
+ def save_file(self, filename, content, address):
+ """保存文件"""
+ try:
+ with open(filename, 'w', encoding='utf-8') as f:
+ json.dump(content, f, ensure_ascii=False, indent=2)
+
+ return {
+ "status": "success",
+ "command": "save_file",
+ "filename": filename,
+ "message": f"文件 {filename} 保存成功",
+ "size": os.path.getsize(filename),
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+ except Exception as e:
+ return {
+ "status": "error",
+ "command": "save_file",
+ "message": f"保存文件失败: {str(e)}",
+ "timestamp": datetime.now().isoformat(),
+ "client_address": f"{address[0]}:{address[1]}"
+ }
+
+if __name__ == "__main__":
+ server = JSONSocketServerSafe()
+ try:
+ server.start_server()
+ except KeyboardInterrupt:
+ safe_print("\n⛔ 服务器已停止", "\n[STOP] 服务器已停止")
\ No newline at end of file
diff --git a/test.bat b/test.bat
index cd69a98..fb6baae 100644
--- a/test.bat
+++ b/test.bat
@@ -1,5 +1,6 @@
@echo off
chcp 65001 >nul
+set PYTHONIOENCODING=utf-8
echo.
echo ===============================================
echo Socket JSON 传输系统 - Windows 测试工具
diff --git a/test_report.json b/test_report.json
new file mode 100644
index 0000000..48583ca
--- /dev/null
+++ b/test_report.json
@@ -0,0 +1,18 @@
+{
+ "test_time": "2025-07-01T14:03:13.108361",
+ "test_results": [
+ "[14:03:11] INFO: 🚀 开始 Socket JSON 传输系统完整测试",
+ "[14:03:11] INFO: ==================================================",
+ "[14:03:11] INFO: \n📋 测试 1/4: 验证文件存在性",
+ "[14:03:11] INFO: 验证测试文件...",
+ "[14:03:11] SUCCESS: ✅ 所有必需文件都存在",
+ "[14:03:11] INFO: \n🖥️ 测试 2/4: 启动服务器",
+ "[14:03:11] INFO: 正在启动服务器...",
+ "[14:03:13] ERROR: ❌ 服务器启动失败: Traceback (most recent call last):\n File \"C:\\Users\\20920\\Desktop\\blender\\server.py\", line 22, in start_server\n print(f\"\\U0001f680 服务器启动成功!\")\n ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^\nUnicodeEncodeError: 'gbk' codec can't encode character '\\U0001f680' in position 0: illegal multibyte sequence\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"C:\\Users\\20920\\Desktop\\blender\\server.py\", line 189, in \n server.start_server()\n ~~~~~~~~~~~~~~~~~~~^^\n File \"C:\\Users\\20920\\Desktop\\blender\\server.py\", line 40, in start_server\n print(f\"\\u274c 服务器启动失败: {e}\")\n ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^\nUnicodeEncodeError: 'gbk' codec can't encode character '\\u274c' in position 0: illegal multibyte sequence\n",
+ "[14:03:13] INFO: ⛔ 服务器已停止",
+ "[14:03:13] INFO: \n==================================================",
+ "[14:03:13] WARNING: 🎯 测试完成: 1/4 项通过",
+ "[14:03:13] WARNING: ⚠️ 部分测试失败,请检查上述错误信息"
+ ],
+ "summary": "测试完成,详细日志共 12 条"
+}
\ No newline at end of file
diff --git a/test_socket.py b/test_socket.py
index 49f029f..b3970ef 100644
--- a/test_socket.py
+++ b/test_socket.py
@@ -13,6 +13,12 @@ import sys
import json
from datetime import datetime
+# 设置标准输出编码为 UTF-8(解决 Windows 控制台编码问题)
+if sys.platform.startswith('win'):
+ import codecs
+ sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
+ sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach())
+
class SocketTester:
def __init__(self):
self.server_process = None
diff --git a/使用说明.md b/使用说明.md
index f9e2f22..f7595bc 100644
--- a/使用说明.md
+++ b/使用说明.md
@@ -256,6 +256,34 @@ server = JSONSocketServer(host='0.0.0.0', port=9999)
client = JSONSocketClient(host='192.168.1.100', port=9999)
```
+### 解决 Windows 编码问题
+
+如果在 Windows 控制台遇到 Unicode 编码错误:
+
+1. **使用编码安全版本**:
+ ```bash
+ # 使用编码安全的服务器
+ python server_safe.py
+ ```
+
+2. **设置控制台编码**:
+ ```cmd
+ # 在 CMD 中运行
+ chcp 65001
+ set PYTHONIOENCODING=utf-8
+ python server.py
+ ```
+
+3. **使用现代终端**:
+ - Windows Terminal(推荐)
+ - VS Code 集成终端
+ - PowerShell 7
+
+4. **文件包含**:
+ - `encoding_fix.py` - 编码修复模块
+ - `server_safe.py` - 编码安全的服务器版本
+ - `requirements.txt` - 依赖说明(无需额外包)
+
## 🛡️ 安全注意事项
- 此系统仅用于学习和测试,不适用于生产环境