====== 11 DWG Base64 与自定义插件/Hops 集成方案 ====== ===== 问题背景 ===== 业务系统有时会把 DWG 文件编码成 base64 字符串,通过 HTTP 请求传到服务端,然后希望在 Rhino.Compute 中完成导入、识别、参数化生成或二次处理。这个场景不要简单理解成“把 base64 字符串直接接到 Grasshopper 电池上”,因为 DWG 通常较大,而且涉及文件格式解析、插件授权、临时文件、单位、图层、块参照和安全校验。 推荐做成三层: 客户端 / 业务后端 -> 接收 base64,做认证、大小限制、审计和任务编号 -> Rhino.Compute 自定义插件端点 -> 解码 DWG,导入到无界面 RhinoDoc 或转换为中间文件/结构化结果 -> Grasshopper Hops 定义 / 自定义 GH 电池 -> 输出几何、识别结果、3dm 文件、JSON 摘要或后续业务结果 核心原则:base64 只适合作为传输格式,不适合作为 Grasshopper 画布长期流转的数据格式。Grasshopper/Hops 更适合接收 ''job_id''、''dwg_path''、''3dm_path''、''layer_filter''、''tolerance'' 这类短参数。 ===== 推荐方案:插件端点 + GH 电池共用核心服务 ===== 推荐把 DWG 处理能力封装在一个 Rhino 插件中,插件内部拆成三个部分: * ''DwgImportService'':核心服务,负责解码、校验、落盘、导入 DWG、提取几何和元数据。 * ''Compute Endpoint'':注册到 Rhino.Compute,对外提供稳定 HTTP API。 * ''Grasshopper Component'':自定义 GH 电池,给 Hops 定义或普通 GH 画布使用。 这样可以让同一套 DWG 处理逻辑同时服务两种入口: ^ 入口 ^ 用途 ^ | 自定义 Compute 端点 | 外部系统直接提交 DWG base64,返回结构化结果或中间文件引用。 | | 自定义 GH 电池 | 设计师在 Grasshopper 中继续编排识别、生成、过滤、标注等参数化逻辑。 | | Hops 定义 | 把含有自定义 GH 电池的 Grasshopper 定义封装成远程函数。 | ===== 数据流 ===== 推荐数据流如下: 1. 客户端上传: POST /api/cad/analyze Body: { fileName, dwgBase64, options, correlationId } 2. 业务后端校验: - 用户权限 - base64 长度 - 业务配额 - 文件名和扩展名 - correlation id 3. 后端调用 Rhino.Compute 自定义端点: POST /Rhino/CustomDwg/Prepare Body: { fileName, dwgBase64, options, correlationId } 4. Compute 插件执行: - Convert.FromBase64String - 写入服务端临时目录 - 创建无界面 RhinoDoc - 导入 DWG - 提取对象、图层、块、包围盒、单位等信息 - 可选:保存为 .3dm 或生成结构化 JSON 5. Grasshopper/Hops 继续处理: - 输入 job_id 或 3dm_path - 自定义 GH 电池读取准备好的几何或元数据 - 下游 GH 逻辑做识别、生成、优化或导出 6. 返回结果: - result_id - objects_count - layers - bounding_box - errors / warnings - 可下载的 3dm、json、图片或业务结果 ===== 自定义 Compute 插件端点设计 ===== 插件端点仍然按 Compute 扩展规则实现:在 Rhino 插件中定义静态类和 ''public static'' 方法,并在插件 ''OnLoad'' 中调用 ''Rhino.Runtime.HostUtils.RegisterComputeEndPoint''。 请求 DTO 建议保持清晰: public sealed class DwgPrepareRequest { public string FileName { get; set; } public string DwgBase64 { get; set; } public string CorrelationId { get; set; } public DwgPrepareOptions Options { get; set; } } public sealed class DwgPrepareOptions { public string UnitSystem { get; set; } public double AbsoluteTolerance { get; set; } public string[] LayerAllowList { get; set; } public bool SaveAs3dm { get; set; } } 端点方法示意: static class CustomDwgComputeFunctions { public static DwgPrepareResult Prepare(DwgPrepareRequest request) { // 1. 校验 fileName、base64 长度、扩展名、correlationId // 2. 解码 base64,写入受控临时目录 // 3. 使用 DwgImportService 导入和提取结果 // 4. 返回结构化摘要或 3dm/job 引用 return DwgImportService.Prepare(request); } } 注册示意: protected override LoadReturnCode OnLoad(ref string errorMessage) { Rhino.Runtime.HostUtils.RegisterComputeEndPoint( "Rhino.CustomDwg", typeof(CustomDwgComputeFunctions) ); return base.OnLoad(ref errorMessage); } ===== DWG 导入实现要点 ===== 在 Rhino 8 中,优先使用 code-driven file IO 和无界面 ''RhinoDoc''。它可以避免脚本命令、弹窗和活动文档依赖,更适合 Compute 服务端。 实现思路: using var doc = Rhino.RhinoDoc.CreateHeadless(null); var options = new Rhino.FileIO.FileDwgReadOptions { ImportUnreferencedBlocks = true, ImportUnreferencedLayers = true, ImportUnreferencedLinetypes = true }; bool ok = doc.Import(dwgPath, options.ToDictionary()); if (!ok) throw new InvalidOperationException("DWG import failed."); foreach (var obj in doc.Objects) { var geometry = obj.Geometry; var layerIndex = obj.Attributes.LayerIndex; // 提取曲线、块、文字、标注、包围盒或转成业务 DTO } 如果使用 Rhino 7,需要单独评估 DWG 导入路径。常见选择是: * 在 Compute 前置服务中先把 DWG 转为 DXF 或 3dm。 * 使用 ODA Drawings SDK、ODA Architecture SDK、Autodesk RealDWG 等受授权的 DWG 解析能力。 * 使用已有公司插件导入 DWG,但必须确认它能在无界面 Compute 子进程中运行,不能依赖弹窗、鼠标选择或命令行交互。 ===== GH 电池设计 ===== 自定义 GH 电池不要直接接收超长 base64 字符串作为常规输入。推荐输入: ^ 输入名 ^ 类型 ^ 说明 ^ | ''job_id'' | Text | 由后端或插件端点生成的任务编号。 | | ''dwg_path'' | Text | 服务端受控目录中的 DWG 文件路径。只用于可信内网/本机调试。 | | ''model_3dm_path'' | Text | 已由插件端点转换好的 3dm 文件路径。 | | ''layer_filter'' | Text/List | 需要处理的图层名或规则。 | | ''tolerance'' | Number | 几何识别容差。 | | ''run'' | Boolean | 控制是否执行,避免画布反复求解。 | 推荐输出: ^ 输出名 ^ 类型 ^ 说明 ^ | ''curves'' | Curve/List | 可直接进入后续 GH 逻辑的曲线。 | | ''breps'' | Brep/List | 可选的面或实体。 | | ''metadata'' | Text/JSON | 图层、块、单位、对象数量、警告信息。 | | ''result_id'' | Text | 后续下载或查询结果的编号。 | GH 电池内部调用同一个 ''DwgImportService'',或者读取端点已经生成的 3dm/JSON。不要让 GH 电池自己管理用户认证、HTTP 上传、base64 解码和全局临时目录清理。 ===== Hops 使用方式 ===== 在 Hops 中使用时,建议把含有自定义 GH 电池的定义保存为独立 ''.gh'' 文件,例如: dwg_prepare_and_analyze.gh 定义结构: Context Get: job_id / model_3dm_path / layer_filter / tolerance / run -> 自定义 GH 电池:ReadPreparedDwg -> 后续识别或参数化逻辑 Context Print: metadata / result_id / warnings Context Bake 或普通输出:curves / breps 调用侧 Hops 组件只需要把 Path 指向这个 ''.gh'' 文件或远程 REST URL。Hops 会根据 Context Get 和 Context Print/Bake 自动生成输入输出。自定义 GH 电池必须安装在所有 Compute 子进程能加载到的位置。 ===== 为什么不推荐直接把 base64 传进 Hops ===== * DWG base64 会比原始文件更大,容易触发请求体大小限制。 * Hops 的优势是函数化求解,不是文件上传和安全网关。 * 长字符串会影响缓存、日志、调试导出和错误定位。 * DWG 导入失败通常需要文件级日志、临时目录和授权诊断,放在插件端点更容易治理。 * 生产环境不能让浏览器前端直接持有 Compute API Key,也不应让前端决定服务端文件路径。 只有在内网调试、小文件、一次性验证时,才可以临时把 base64 作为 ''Text'' 输入传给 Hops;生产方案应改成预上传或插件端点接收。 ===== 安全与运维要求 ===== * 限制 base64 解码后的最大字节数。 * 只接受 ''.dwg'',不要信任客户端传来的文件名,服务端应重新生成安全文件名。 * 临时文件写入固定根目录,例如 ''C:\ProgramData\YourCompany\ComputeJobs'',禁止客户端传任意绝对路径。 * 每个请求使用独立任务目录,目录名使用 ''correlation_id'' 或服务端生成的 GUID。 * 记录导入耗时、对象数量、图层数量、失败原因和 Compute 子进程编号。 * 任务完成后按 TTL 清理 DWG、3dm、日志和中间 JSON。 * 插件依赖 DLL、授权文件、DWG 解析库必须部署到 Compute 服务器,并在 ''/sdk'' 和最小请求中验证。 ===== 验证清单 ===== - 在普通 Rhino 中安装插件并运行最小 DWG 导入测试。 - 启动本地 Rhino.Compute,访问 ''/sdk'',确认 ''Rhino.CustomDwg'' 端点出现。 - 用一个很小的 DWG base64 调用 ''Prepare'',确认能返回对象数量和图层摘要。 - 用损坏 base64、错误扩展名、超大文件、空文件分别测试错误处理。 - 在 Grasshopper 中放置自定义 GH 电池,使用 ''job_id'' 或 ''3dm_path'' 读取准备好的结果。 - 把该 GH 定义封装成 Hops 函数,检查 Context Get/Print 命名和远程 Compute 配置。 - 部署到 IIS/生产服务器后,重新验证插件授权、临时目录权限、请求体大小和超时设置。 ===== 落地顺序 ===== 推荐按以下顺序落地: - 先做插件核心服务:能在普通 Rhino 中把 DWG 导入并提取对象摘要。 - 再注册 Compute 自定义端点:能接收 base64 并返回结构化结果。 - 再做 GH 电池:读取 ''job_id'' 或 ''3dm_path'',输出曲线、图层和元数据。 - 最后做 Hops 定义:把 GH 电池和业务参数化逻辑封装成远程函数。 这样每一步都能单独测试,出错时也能判断是上传、插件导入、Compute 加载、Grasshopper 求解还是 Hops 调用的问题。 ===== 参考资料 ===== * [[https://developer.rhino3d.com/en/guides/compute/custom-endpoints/|Extending Compute with Custom Endpoints]] * [[https://developer.rhino3d.com/guides/compute/hops-component/|The Hops Component]] * [[https://developer.rhino3d.com/en/guides/compute/how-hops-works/|How Hops Works]] * [[https://developer.rhino3d.com/guides/rhinocommon/code-driven-file-io/|Code-Driven File IO]] * [[https://developer.rhino3d.com/api/rhinocommon/rhino.fileio.filedwgreadoptions|FileDwgReadOptions]] * [[https://developer.rhino3d.com/api/rhinocommon/rhino.fileio.filereadoptions|FileReadOptions]]