====== 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]]