SketchUp Ruby API 插件开发:参数化建模与 rbz 打包 📅 发布时间:2026/9/17 12:29:24 👁 浏览次数: 简介这份 SketchUp Ruby API 参考文档由 Sugar 整理自官方资料共 274 页面向使用 Ruby 开发 SketchUp 插件、扩展与自动化脚本的开发者也适合需要离线速查接口的中级使用者。资源包仅含 1 个 pdf 文件约 2.67MB体积轻便便于检索。内容按模块编排从应用程序级类讲起涵盖模型、实体、属性词典、坐标轴、动画、摄像机、颜色、扩展授权、导入器与语言处理等主题模型部分说明创建、属性设置与选取操作属性词典部分给出键值读写、遍历与检索方法坐标轴、动画与摄像机部分则对应参数设置与控制逻辑基本覆盖插件开发常用的类与方法。该文档已有 475 人学习。读者可把它当作常备的接口速查手册借助分模块目录快速定位类与方法之间的调用关系减少在英文文档中反复翻找的时间为编写工具栏、批量处理或导入导出类扩展提供参考。1. 一份 PDF 引出的问题SketchUp Ruby API 到底在解决什么甲方的地下室图纸要求当天出模型6 米柱网200 根柱子立面方案改了十几版每版都要把几百个幕墙分格重新排一遍。这类活靠鼠标一个一个推拉是干不完的但写成 Ruby 脚本重新跑一遍只要几十秒。SketchUp Ruby API 的价值就在这里它把 SketchUp 里「画线、封面、推拉、成组」的每一个动作翻译成一个可以循环、可以传参、可以复用的 api 接口。一份像《Sketch Up Ruby API by Sugar》这样的资料价值不在于罗列方法名而在于把三件事串起来SketchUp 的对象模型谁装谁、几何构造的坐标系规则点从哪来、插件的挂载方式代码怎么进到菜单里。多数人卡住的地方也不是 Ruby 语法而是不知道entities和definition谁持有谁、不知道10.mm和裸数字10差了 25.4 倍、不知道脚本为什么一按就崩。下面这套内容是按「控制台跑通 → 建几何 → 挂菜单 → 打包排错」的路径写的面向想把手头重复建模动作脚本化的设计师也面向准备写第一个 SketchUp 插件插件在中文界面里叫「扩展程序」的工程师。从一行puts开始到最后产出能分发的 rbz。2. SketchUp Ruby API 的运行环境与对象模型这门 API 是随 SketchUp 一起装进你机器里的不需要单独装 Ruby 解释器。装完 SketchUpRuby 环境、sketchup.rb、extensions.rb这些基础库就已经在了你写的代码跑在 SketchUp 自己内嵌的解释器里。2.1 打开 Ruby 控制台先确认 API 能跑起来Windows 菜单栏找「窗口 → Ruby 控制台」macOS 在「窗口 → Ruby 控制台」。控制台是一个交互式 REPL敲一行执行一行最适合验证方法是否存在、返回什么。先跑这几行# 确认版本与平台后面写兼容分支时要用 puts Sketchup.version # 例如 24.0.594字符串 puts Sketchup.platform # :platform_win / :platform_osx puts Sketchup.is_pro? # 是否为 Pro 版部分 API 仅 Pro 可用 model Sketchup.active_model # 拿到当前打开的模型对象 puts model.title # 未保存时是空串 puts model.path # 未保存时为空 puts model.entities.count # 当前模型根层级的图元数量Sketchup.active_model返回的是Sketchup::Model实例它是后面所有操作的入口。如果这几行里任意一行抛NameError说明你打开的不是 SketchUp 自带控制台而是在外部 IRB 里——外部 Ruby 没有Sketchup这个常量。新手最容易踩的坑是把控制台当编辑器用。控制台适合验证不适合写几十行的逻辑因为它没有撤销、没有保存、改错一行要重敲。2.2 从 model 到 entities三层对象关系决定了你往哪加东西SketchUp 的模型结构是树理解这三层就够了层级Ruby 对象作用常用方法模型层Sketchup::Model全局入口管选择集、材质、图层、组件库active_entities、start_operation、definitions容器层Sketchup::Entities收纳图元根容器或组内容器add_face、add_line、add_group、add_instance图元层Face/Edge/Group/ComponentInstance实际几何与实例pushpull、transformation、explode关键区分model.entities是模型的根容器model.active_entities是「当前编辑上下文」的容器。你在组里双击进去建模时active_entities指向那个组内部的Entities而model.entities始终指向根层。写脚本时如果希望几何落在用户正在编辑的组里用active_entities希望永远落在根层用entities。Group和ComponentInstance不直接存几何几何挂在它们的ComponentDefinition上。一个定义可以被多个实例引用改定义所有实例跟着变。这是批量建模能省内存的根本原因也是「为什么我改了组的形状别的组也变了」的来源。2.3 一个能被 SketchUp 识别的最小扩展骨架SketchUp 启动时会自动加载 Plugins 目录下所有.rb文件。找出这个目录# 在控制台执行直接输出你的 Plugins 目录绝对路径 puts Sketchup.find_support_file(Plugins)在这个目录下新建两个东西加载文件sugar_demo.rb和文件夹sugar_demo/。加载文件内容如下# Plugins/sugar_demo.rb —— SketchUp 启动时自动执行这个文件 require sketchup.rb require extensions.rb module Sugar # 扩展主文件夹与加载文件同级 PLUGIN_ROOT File.join(File.dirname(__FILE__), sugar_demo) # file_loaded? 防止重复注册热重载时尤其重要 unless file_loaded?(__FILE__) ext SketchupExtension.new(Sugar 参数化 Demo, sugar_demo/main.rb) ext.description 按柱网批量生成梁柱节点 ext.version 1.0.0 ext.creator Sugar ext.copyright 2025 # 第二个参数 true 加载 SketchUp 时一并启动 Sketchup.register_extension(ext, true) file_loaded(__FILE__) end endSketchupExtension.new(name, path)的第二个参数是相对于 Plugins 目录的路径写绝对路径在别人机器上会失效。register_extension的第二个参数控制是否随 SketchUp 启动加载调试阶段建议先设为true配合「扩展程序管理器」里的开关做启停出问题时能一键关掉而不是删文件。file_loaded?/file_loaded是 SketchUp 提供的去重机制。少了它反复load同一个文件会重复注册菜单和工具栏结果是菜单里出现两条一模一样的项。3. 用 Ruby API 生成几何点、面、拉伸与变换能建出一个面就说明几何链路通了。这一章从最小图元讲起到批量阵列结束。3.1 点、线、面的最小生成单元与单位换算SketchUp 内部长度单位是英寸。Geom::Point3d.new(1, 2, 3)里的 1 就是 1 英寸不是 1 毫米。SketchUp 给Numeric加了单位后缀方法务必用它们model Sketchup.active_model ents model.active_entities # 用 .m / .mm 明确单位避免裸数字带来的 25.4 倍误差 p1 Geom::Point3d.new(0, 0, 0) p2 Geom::Point3d.new(6000.mm, 0, 0) p3 Geom::Point3d.new(6000.mm, 4000.mm, 0) p4 Geom::Point3d.new(0, 4000.mm, 0) face ents.add_face(p1, p2, p3, p4) # 返回 Sketchup::Face puts face.area # 24.0平方英寸 # 画线用 add_line返回 Sketchup::Edge edge ents.add_line(p1, p2) puts edge.length # 6000mm 对应的英寸值add_face(points)接收 3 个及以上的点。顶点顺序决定法线方向遵循右手法则如果点共线、重合或自交返回-1或者抛ArgumentError。写生成脚本时要养成的习惯是把返回值和-1做比较if face -1 UI.messagebox(顶点共线无法封面请检查坐标) else face.pushpull(3000.mm) # 向上拉伸 3 米返回拉伸出的新顶面 endFace#pushpull(distance)的参数是沿面法线的距离正负号决定方向。它返回的是被推拉出来新的那个面有时是多个面组成的数组不是原来的面后续要在这个新面上做操作时记得接住返回值。3.2 用 Transformation 做阵列平移、旋转和参数含义阵列的本质是同一个几何体加不同的变换矩阵。做柱阵列通常先把几何塞进ComponentDefinition再用add_instance落位model.start_operation(批量生成柱, true) # true 关闭过程中刷新跑得更快 # 1) 建一个组件定义几何用局部坐标左下角对齐原点 defn model.definitions.add(柱-300x300) pts [ Geom::Point3d.new(0, 0, 0), Geom::Point3d.new(300.mm, 0, 0), Geom::Point3d.new(300.mm, 300.mm, 0), Geom::Point3d.new(0, 300.mm, 0) ] defn.entities.add_face(pts).pushpull(3000.mm) # 2) 按 6m 柱网阵列横 4 纵 6 ents model.active_entities (0...4).each do |i| (0...6).each do |j| # 平移变换从原点沿 X/Y 各偏移 i*6m、j*6m tr Geom::Transformation.translation( Geom::Vector3d.new(i * 6000.mm, j * 6000.mm, 0) ) ents.add_instance(defn, tr) # 返回 ComponentInstance end end model.commit_operation # 一次性压入撤销栈Geom::Transformation.translation(vector)只做平移。旋转用Geom::Transformation.rotation(origin, axis, angle)角度是弧度所以写45.degrees而不是45。缩放用Geom::Transformation.scaling(origin, factor)非等比缩放建议慎用组件被非等比缩放后内部几何的坐标系会变得不好推算。start_operation(name, disable_ui)的name会显示在「编辑 → 撤销」菜单里取个能看懂的动词短语disable_ui传true表示操作期间不刷新视图几百个实例的循环能快好几倍。配套的还有model.abort_operation捕获异常时用它回滚避免模型里留下半成品。对已有图元批量加变换用ents.transform_entities(transformation, entities_array)第二个参数必须是数组。3.3 组内局部坐标与变换顺序最常见的「位置对不上」组和组件的几何存在局部坐标系里group.transformation才是它相对父容器的位置。把几何加进组之前设变换、和加完之后设变换结果一样但读坐标时完全不一样group ents.add_group group.entities.add_face(pts) # 这里的坐标是组内局部坐标 group.transformation Geom::Transformation.translation([5000.mm, 0, 0]) # 想把一个世界坐标点换算成组内坐标必须用逆矩阵 world_pt Geom::Point3d.new(5500.mm, 1000.mm, 0) local_pt group.transformation.inverse * world_pt puts local_pt # (500mm, 1000mm, 0)transformation.inverse * point是这套 API 里用得最多的一行。凡是要在「用户点了一下鼠标拿到的世界坐标」和「往组里加几何」之间转换都绕不开它。另一个高频问题是组的嵌套。group.entities.add_group创建的是子组它的transformation相对于父组不是相对于世界。层级深的时候把各级transformation依次相乘才能得到世界矩阵。写生成脚本时我一般把层级控制在两层以内能少一半调试时间。命令跑完发现位置不对先打印group.transformation.to_a看矩阵再打印group.bounds看包围盒基本能定位是坐标算错了还是挂错容器了。4. 把脚本变成插件菜单、工具栏与 Tool 交互脚本能跑通不等于能给别人用。这一章解决挂载与交互。4.1 用 UI::Command 建命令再挂到菜单和工具栏UI::Command是「一个可复用的动作」菜单项、工具栏按钮、右键菜单都指向同一个 Command 实例module Sugar unless ui_loaded ui_loaded true cmd UI::Command.new(生成柱网) { Sugar.build_grid } # 点击时执行 cmd.small_icon File.join(PLUGIN_ROOT, icons/grid_16.png) cmd.large_icon File.join(PLUGIN_ROOT, icons/grid_24.png) cmd.tooltip 生成柱网 cmd.status_bar_text 按 6 米柱网在当前层级生成柱子 cmd.menu_text 生成柱网... toolbar UI::Toolbar.new(Sugar 工具) toolbar.add_item(cmd) toolbar.show # 不加这句工具栏默认隐藏 menu UI.menu(Plugins) # 中文界面显示为「扩展程序」 menu.add_item(cmd) end end图标路径必须是绝对路径用File.join(PLUGIN_ROOT, ...)拼。Windows 下工具栏小图标按 16×16、大图标按 24×24 准备尺寸不对会出现模糊或被裁切。UI.menu(Plugins)是约定俗成的入口菜单往UI.menu(File)里塞自己的功能会显得很突兀。toolbar.show只影响当前会话用户手动关掉后就一直隐藏。想要记住状态得自己在Sketchup.write_default里存一个标记位下次启动读回来再决定是否show。4.2 自定义 Tool用 InputPoint 抓鼠标位置Tool 是 SketchUp 里处理视口交互的机制。定义一个类实现生命周期方法再交给模型module Sugar class PlaceTool def activate Sketchup.status_text 点击放置柱子Esc 退出 end def deactivate(view) view.invalidate # 触发重绘清掉绘制残留 end def onLButtonDown(_flags, x, y, view) ip Sketchup::InputPoint.new ip.pick(view, x, y) # 把屏幕坐标解析成模型中的三维点 pt ip.position return if pt.nil? model Sketchup.active_model model.start_operation(放置柱, true) Sugar.place_column(model.active_entities, pt) model.commit_operation end end end Sketchup.active_model.select_tool(Sugar::PlaceTool.new)InputPoint#pick(view, x, y)会自动识别吸附目标——端点、边线中点、面上一点、坐标轴交点返回的position已经带上吸附结果。直接用view.screen_coords反算只能得到屏幕平面上的点落地位置是错的。onLButtonDown(flags, x, y, view)的四个参数flags是修饰键位掩码判断是否按了 Ctrl/Shift 用flags COPY_MODIFIER_KEYx/y是视口像素坐标view是当前视图对象。Tool 里要频繁重绘比如跟随鼠标画预览时在onMouseMove里更新状态后调view.invalidate不要直接view.draw。select_tool是切换当前工具的唯一入口。工具用完记得在deactivate里把Sketchup.status_text复位成空串否则状态栏会一直挂着你的提示。4.3 从菜单一路走到落图把三块拼起来module Sugar def self.build_grid model Sketchup.active_model model.start_operation(生成柱网, true) begin defn model.definitions[柱-300x300] || build_column_def(model) (0...4).each do |i| (0...6).each do |j| tr Geom::Transformation.translation([i * 6000.mm, j * 6000.mm, 0]) model.active_entities.add_instance(defn, tr) end end model.commit_operation rescue StandardError e model.abort_operation # 出错回滚不留下半张图 UI.messagebox(生成失败#{e.message}) end end end这里的begin/rescue/abort_operation是模板级写法一旦循环中途抛异常前面的实例会被一起撤掉用户不会得到一个「生成了一半」的模型。UI.messagebox只用来兜底提示正常的参数校验建议用UI.inputbox在操作前收集别让用户在出错后才看到对话框。5. 进阶验证观察器、撤销栈与 rbz 打包排错代码能跑和插件能发布之间还差三件事不给用户留烂摊子、能被正确加载、出问题能定位。5.1 用观察器兜底用操作名对齐撤销粒度观察器让你在模型变化时被回调。保存前的校验、自动改图层这类需求都靠它module Sugar class SaveWatcher Sketchup::ModelObserver def onPreSaveModel(model) loose model.entities.grep(Sketchup::Face).size puts [Sugar] 保存前检查根层裸露面 #{loose} 个 if loose 0 end end end # 必须把实例存到常量和变量里否则会被 GC 回收回调再也不触发 Sugar::WATCHER Sugar::SaveWatcher.new Sketchup.active_model.add_observer(Sugar::WATCHER)观察器实例被回收是这类「代码明明没错但回调不执行」的头号原因。凡是add_observer后面没有再引用这个对象的地方都要检查一遍。撤销栈的粒度则由start_operation/commit_operation的配对决定。一个操作名对应一次 CtrlZ。把整个阵列放进一个操作里用户撤销一次就回到干净状态每根柱子各开一次操作用户要按 24 次撤销体验很差。5.2 打包成 rbz 与加载失败的排查顺序.rbz就是改了扩展名的 zip。命令行打包最容易理解结构# 在 Plugins 目录的上一级执行 # 压缩包里应该同时包含加载文件 sugar_demo.rb 和文件夹 sugar_demo/ zip -r sugar_demo.rbz sugar_demo.rb sugar_demo/ -x *.DS_Store -x *__MACOSX*注意-x排除掉 macOS 的隐藏文件也注意不要在压缩包里多套一层目录。如果解压后是sugar_demo/sugar_demo.rbSketchUp 就扫描不到加载文件装完毫无反应。安装后在「扩展程序管理器」里检查是否出现在列表中然后按这个顺序排查现象首先检查常见原因列表里没有压缩包目录结构多套了一层文件夹列表里有但没菜单SketchupExtension.new路径路径带了绝对前缀或拼写错误菜单出现两条file_loaded?守护忘了写去重判断报LoadErrorrequire的相对路径文件移动后路径没同步改只在部分版本崩Sketchup.version分支用了新版本才有的方法运行时错误定位靠 Ruby 控制台。在代码里临时插puts是最快的控制台会实时输出。更系统的做法是在加载文件顶部加一句Sketchup.set_status_text之类的打点意义不大真正有效的是把可疑段落用begin/rescue包起来在rescue里打印e.backtrace.first(5)这样能直接看到是哪一行炸的而不是只看到一句异常消息。调试阶段还可以用load手动重载整个插件——这也是file_loaded?那块守护逻辑要写对的原因重载时它必须能正确跳过重复注册否则菜单每次重载都会多一条。改完代码执行load File.join(Sketchup.find_support_file(Plugins), sugar_demo.rb)再点菜单验证整个循环比反复重启 SketchUp 快得多。本文还有配套的精品资源点击获取