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 @@ + + + + cursor_move + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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` - 依赖说明(无需额外包) + ## 🛡️ 安全注意事项 - 此系统仅用于学习和测试,不适用于生产环境