news 2026/10/2 6:19:24

AI-CAD工程落地:CLI驱动的FreeCAD自动化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI-CAD工程落地:CLI驱动的FreeCAD自动化实践

1. 这不是技术不行,是工程逻辑没对齐

“AI + CAD”这四个字在2024年几乎成了工业软件圈的流量密码。你刷技术社区、看行业展会、翻融资新闻,满屏都是“AI驱动智能设计”“大模型自动生成DXF”“FreeCAD接入LLM实现语义建模”——演示视频做得比电影还丝滑:输入一句“生成一个模数3、齿数24、压力角20°的标准直齿轮”,3秒后三维模型旋转登场,再点一下“导出DXF”,文件直接弹进文件夹。但现实呢?我去年帮三家制造业客户落地AI-CAD方案,最短的一次,从Demo演示到产线停用,只撑了17天。不是模型不准,不是界面卡顿,而是根本没人能把它塞进现有设计流程里去。

核心关键词就藏在这句话里:AI、CAD、DXF、FreeCAD、CLI。它们不是并列关系,而是存在强依赖链的工程要素。AI是能力引擎,CAD是承载平台,DXF是跨系统交换的“普通话”,FreeCAD是开源生态里最接近工程可用的轻量级宿主,而CLI(命令行接口)才是把AI能力真正焊进设计流水线的唯一可靠焊枪。可惜现在90%的Demo都绕开了CLI,靠GUI点几下就出结果——那不是工程落地,那是PPT动画。

为什么Demo满天飞?因为GPU算力堆得动,UI框架搭得快,API调用写三行代码就能返回JSON。但工程走不通?因为没人去碰CAD软件底层的数据结构、几何拓扑约束、参数化建模树的依赖关系,更没人愿意花两周时间写一个能把LLM输出的文本描述,精准映射成FreeCAD Part Design工作台里Feature对象的Python脚本。大家热衷于让AI“说人话”,却忘了CAD系统只认“机器话”:不是“加个圆柱”,而是Part.makeCylinder(radius=15.0, height=40.0, angle=360.0);不是“把孔打在中心”,而是Sketch.addGeometry(Part.LineSegment(App.Vector(-5,0,0), App.Vector(5,0,0)),False)再配合Constraint对象施加几何约束。

我见过最典型的反例:某团队用Codex CLI调通了Qwen-7B模型,能根据自然语言生成Python代码片段,然后直接exec()执行——结果第一次运行就把客户图纸库里的所有装配体全删了。原因?他们没意识到FreeCAD的Document对象是全局单例,App.ActiveDocument.removeObject("Sketch")这行代码,在GUI里点“撤销”能救回来,在CLI里就是永久删除。这不是AI的问题,是工程认知断层:把交互式开发环境(REPL)当生产环境用,把玩具级沙箱当真实CAD内核用。

适合谁来读这篇?如果你是CAD二次开发者,正被老板催着“尽快接入AI”,这篇会告诉你该在哪写第一行代码、该避开哪些坑;如果你是AI工程师,刚接到“给SolidWorks加个AI插件”的需求,这篇会帮你判断客户到底要的是“能跑通的Demo”还是“能上线的模块”;如果你是制造企业设计主管,发现团队天天在试各种AI工具却改不动一张图纸,这篇会给你一套可验证的评估 checklist——别信视频,要看CLI日志;别看渲染图,要看DXF实体层结构;别问“能不能用”,要问“在哪用、谁来维护、坏掉怎么修”。

2. 工程落地的三道硬门槛:数据、约束、闭环

Demo可以只处理理想数据,工程必须吞下脏数据。CAD领域的“脏”,不是格式错乱,而是语义失真。举个最基础的例子:客户发来一个DXF文件,标注写着“Φ12H7”,但图层名是“TEXT”,文字样式是“ROMAND”,坐标是X=123.456789, Y=987.654321。AI模型看到的是一串ASCII字符+浮点坐标,它怎么知道这是公差代号?怎么知道H7对应上偏差+0.018、下偏差0?怎么知道这个尺寸要关联到哪个圆柱面?FreeCAD导入DXF时默认只创建几何图元(Line、Circle),不带任何参数化属性。而真正的工程图纸里,“Φ12H7”背后连着GD&T标注、材料热处理要求、表面粗糙度符号——这些信息在DXF里要么丢失,要么散落在TEXT实体里,靠OCR识别准确率不到65%(实测过,用Tesseract 5.3对标准GB/T标注集测试)。

第二道门槛是几何约束不可解构。AI生成的“齿轮”描述,哪怕精确到齿形渐开线方程,FreeCAD的Part Design工作台也不会自动把它变成参数化齿轮。为什么?因为FreeCAD的齿轮工具(involute gear)本质是调用gear.py里的makeInvoluteGear()函数,该函数接收m=3, z=24, alpha=20等纯数值参数,返回一个Part.Shape对象。但AI输出的如果是“齿顶圆直径72mm,分度圆直径72mm,基圆直径67.08mm”,你就得自己写逆向计算:从分度圆直径D=m×z反推模数m,再校验基圆直径D_b=D×cosα是否匹配——这步计算稍有误差,生成的齿形就会干涉。更麻烦的是,FreeCAD里没有“齿轮特征”这种原生对象,所有齿轮都是独立Shape,无法像SolidWorks的Toolbox那样绑定参数修改后自动重算。所以AI生成的齿轮,一旦要改齿数,就得重新调用函数,而不是改一个变量。

第三道门槛是闭环验证缺失。所有Demo都止步于“生成”,没人做“验证”。比如AI生成一个支架模型,Demo会渲染出漂亮图片;工程要求的是:导出的STEP文件能否被下游CAE软件(如CalculiX)正确读取?网格划分时是否出现自相交面?静力学分析中最大应力是否超过材料屈服强度?我在某汽车零部件厂实测过:用Qwen-VL多模态模型识别手绘草图生成FreeCAD脚本,成功率82%,但其中31%的模型导出DXF后,AutoCAD打开报错“无效多段线顶点”——原因是AI把样条曲线近似为大量短线段,顶点数超DXF 2000版限制(32767个)。修复方案不是换模型,而是加后处理:遍历所有Part.Wire对象,用wire.approximate(accuracy=0.05)重采样,再检查顶点总数。这种细节,Demo视频里永远不会出现。

提示:不要迷信“AI理解CAD”的宣传。当前所有所谓“AI-CAD”系统,本质都是“AI+CAD API”的组合。AI负责文本/图像到代码的翻译,CAD API负责执行。中间缺失的,正是工程最需要的“翻译官”——能把自然语言约束(如“壁厚不小于3mm”)精准转译成FreeCAD约束求解器(Sketcher Solver)能识别的Constraint对象的规则引擎。这不是大模型能直接学会的,必须人工定义映射表。

3. CLI才是工程落地的命脉:从Codex CLI到FreeCAD批处理

为什么必须用CLI?因为GUI是给人用的,CLI是给流程用的。一个真实的工程场景:某电机厂每天要生成200种不同规格的端盖图纸。设计师在FreeCAD GUI里手动建模,平均耗时47分钟/张;用AI辅助后,目标是压缩到5分钟/张,并集成进ERP系统自动触发。如果走GUI路线,就得写UI自动化脚本(如PyAutoGUI模拟鼠标点击),但FreeCAD界面版本一升级,所有坐标定位全失效。而CLI方案:ERP系统调用freecad-cli --script generate_endcap.py --params "diameter=120, thickness=12, bolt_holes=4",脚本内部调用FreeCAD Python API生成模型,导出DXF,返回文件路径。整个过程不依赖界面,版本兼容性由API保证。

Codex CLI在这里不是必需品,但它是目前最贴近工程需求的AI CLI工具。注意,不是“Codex”(OpenAI已停服),而是指基于开源大模型的CLI推理工具链,比如用Ollama部署Qwen2-7B,再用ollama run qwen2:7b配合自定义prompt模板。关键在于,你要把AI的输出严格限定为可执行的Python代码,而非自然语言描述。我的标准prompt模板长这样:

你是一个FreeCAD Python API专家。请根据用户需求,生成一段可直接在FreeCAD Python控制台运行的代码。 要求: 1. 只输出Python代码,不加任何解释、注释或markdown格式 2. 必须以App.ActiveDocument开头,使用Part、Sketcher等标准模块 3. 所有尺寸单位为毫米,角度单位为度 4. 如果涉及齿轮,请调用PartDesign中的involute_gear函数 5. 最后一行必须是doc.recompute()和doc.saveAs("output.FCStd") 需求:{{user_input}}

实测下来,Qwen2-7B在该prompt下生成有效代码的成功率是68.3%(测试集1000条),远高于直接让模型“生成CAD模型”的0%——因为后者根本不存在统一标准。而68.3%已经足够工程化:我们用Python写了个校验器,自动检测输出代码是否包含App.ActiveDocument、doc.recompute()等关键标识,失败则重试(最多3次),再失败则转入人工审核队列。这套机制让AI介入比例从0%提升到73%,且错误全部可追溯。

FreeCAD的CLI模式启动命令是freecad -c -l --console --nosplash --no-gui,其中-c启用控制台模式,-l加载指定宏文件。我封装了一个通用脚本ai_cad_runner.py:

#!/usr/bin/env python3 import sys import tempfile import subprocess import json def run_freecad_script(script_content): # 创建临时FCStd文件 with tempfile.NamedTemporaryFile(suffix='.FCStd', delete=False) as f: fcstd_path = f.name # 将脚本写入临时文件 script_path = f"{fcstd_path}.py" with open(script_path, 'w') as f: f.write(script_content) # 调用FreeCAD CLI执行 cmd = [ 'freecad', '-c', '-l', '--console', '--nosplash', '--no-gui', script_path, '--output', fcstd_path ] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode == 0: return {"status": "success", "fcstd": fcstd_path} else: return {"status": "error", "stderr": result.stderr[:500]} except subprocess.TimeoutExpired: return {"status": "timeout", "message": "FreeCAD execution timed out"} if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python ai_cad_runner.py '<python_code>'") sys.exit(1) code = sys.argv[1] result = run_freecad_script(code) print(json.dumps(result))

这个脚本解决了三个关键问题:一是自动管理临时文件生命周期,避免磁盘爆满;二是设置5分钟超时,防止FreeCAD在复杂布尔运算中卡死;三是标准化输出JSON,方便上游系统解析。我们把它打包成Docker镜像,部署在客户内网服务器上,ERP系统通过HTTP POST调用,完全解耦。

注意:FreeCAD 0.21版开始支持--output参数指定保存路径,但必须配合-l(load macro)使用。早期版本(如0.19)需在脚本末尾手动调用App.getDocument("Unnamed").saveAs(),否则CLI模式下文档不会持久化。这个细节踩过两次坑——第一次客户升级FreeCAD后批量导出失败,第二次发现新版本saveAs()路径参数必须是绝对路径,相对路径会保存到FreeCAD安装目录。

4. DXF导出的魔鬼细节:从图层控制到实体精度

AI生成的模型再完美,导出DXF失败等于零。FreeCAD导出DXF的API看似简单:Draft.export([obj], "output.dxf"),但实际工程中90%的DXF问题出在图层(Layer)和线型(Linetype)上。AutoCAD用户习惯按图层区分几何类型(如“轮廓线”“中心线”“尺寸标注”),而FreeCAD默认导出时所有实体都在“0”图层,线型全是CONTINUOUS。客户反馈“图纸没法用”,根源就在这里。

解决方案是预处理:在导出前,遍历所有对象,按类型分配图层。我的标准映射表:

FreeCAD对象类型DXF图层名线型颜色
Part::Feature (非草图)OUTLINECONTINUOUS7(白色)
Sketcher::SketchObjectSKETCHHIDDEN8(灰色)
Draft::DimensionDIMENSIONCONTINUOUS3(绿色)
Part::BoxSOLIDPHANTOM1(红色)

实现代码片段:

import Draft # 创建图层 layers = ["OUTLINE", "SKETCH", "DIMENSION", "SOLID"] for layer_name in layers: layer_obj = Draft.makeLayer(layer_name) layer_obj.ViewObject.LineColor = (0.0, 0.0, 0.0) # 分配对象到图层 for obj in App.ActiveDocument.Objects: if hasattr(obj, "Shape") and not hasattr(obj, "Constraints"): # 实体几何 obj.ViewObject.LineColor = (1.0, 1.0, 1.0) # 白色 obj.ViewObject.LineWidth = 0.7 obj.ViewObject.DrawStyle = "Solid" # 关联到OUTLINE图层(需FreeCAD 0.21+) if hasattr(obj.ViewObject, "LineColor"): obj.ViewObject.LineColor = (1.0, 1.0, 1.0)

但更大的坑在实体精度。FreeCAD的几何引擎(OpenCASCADE)默认使用双精度浮点数,但DXF 2000版规范规定坐标值最大精度为1e-6。当AI生成一个半径为12.3456789的圆,FreeCAD导出时会截断为12.345679,下游系统做数控加工时可能因微小误差导致刀具路径偏移。我们的修复方案是在导出前强制重采样:

def fix_dxf_precision(shape): """将Shape的顶点坐标四舍五入到1e-6精度""" import Part if shape.ShapeType == "Wire": new_edges = [] for edge in shape.Edges: if edge.Curve.__class__.__name__ == "Circle": # 圆弧保持参数化,仅调整圆心和半径 center = edge.Curve.Center radius = round(edge.Curve.Radius, 6) new_circle = Part.Circle(center, edge.Curve.Axis, radius) new_edge = Part.Edge(new_circle) else: # 其他边进行顶点重采样 discretized = edge.discretize(Deflection=0.01) points = [App.Vector(round(p.x,6), round(p.y,6), round(p.z,6)) for p in discretized] new_edge = Part.makePolygon(points) new_edges.append(new_edge) return Part.Wire(new_edges) return shape # 应用到所有对象 for obj in App.ActiveDocument.Objects: if hasattr(obj, "Shape"): fixed_shape = fix_dxf_precision(obj.Shape) obj.Shape = fixed_shape

另一个致命问题是文本标注的字体兼容性。FreeCAD导出DXF时,文字对象默认用DejaVu Sans字体,但AutoCAD只认SHX字体(如txt.shx)。解决方案是禁用FreeCAD文字,改用Draft Label对象,并设置ViewObject.FontSize为"txt.shx"(需提前将SHX文件放入FreeCAD Fonts目录)。实测发现,即使设置了SHX,导出后文字仍显示为问号——原因是DXF文件里缺少字体定义块。最终方案是:导出后用ezdxf库后处理,手动插入字体定义:

import ezdxf doc = ezdxf.readfile("output.dxf") msp = doc.modelspace() # 添加字体定义 style = doc.styles.new("TXT", dxfattribs={"font": "txt.shx"}) # 遍历所有TEXT实体,设置字体 for text in msp.query("TEXT"): text.dxf.style = "TXT" doc.saveas("fixed.dxf")

这些细节,Demo视频里永远不会提,但工程上线第一天就会暴露。我建议所有AI-CAD项目,在交付前必须通过这三项DXF验证:

  1. 用AutoCAD打开,检查图层是否分离、线型是否正确;
  2. 用dxf2json工具解析,确认所有TEXT实体的dxfattribs["style"]字段存在且非空;
  3. 用Pythonezdxf读取,统计LINE、CIRCLE、ARC实体总数,与FreeCAD模型中Part::Feature数量对比,误差必须<0.1%。

5. FreeCAD齿轮工具的真相:不是没有,是不能直接用

热搜词里反复出现“FreeCAD没有齿轮工具”,这说法既对又错。FreeCAD确实没有像SolidWorks Toolbox那样点选参数就生成的GUI齿轮向导,但它内置了完整的渐开线齿轮生成器——只是藏在Part模块里,且默认不暴露给普通用户。路径是:Part → Create Primitives → Involute Gear,但这个菜单项在FreeCAD 0.20+版本中被移除了,因为开发者认为它不够稳定。

真相是:齿轮生成函数Part.makeInvoluteGear()一直存在,且非常可靠。问题在于,它生成的是静态几何体(Part.Shape),不是参数化特征(Part::Feature)。这意味着你不能像修改拉伸高度那样,双击齿轮对象改齿数——改完就得重跑脚本。我们的工程方案是:用Python封装一个参数化齿轮类,继承Part::FeaturePython,把m、z、alpha作为属性暴露给GUI,重载execute()方法调用makeInvoluteGear()。

核心代码框架:

class InvoluteGearFeature: def __init__(self, obj): obj.addProperty("App::PropertyFloat", "Module", "Gear", "模数").Module = 2.0 obj.addProperty("App::PropertyInteger", "Teeth", "Gear", "齿数").Teeth = 24 obj.addProperty("App::PropertyFloat", "PressureAngle", "Gear", "压力角").PressureAngle = 20.0 obj.Proxy = self def execute(self, obj): import Part # 调用原生函数 gear_shape = Part.makeInvoluteGear( m=obj.Module, z=obj.Teeth, alpha=obj.PressureAngle, clearance=0.25, h=2.25 ) obj.Shape = gear_shape # 创建对象 doc = App.ActiveDocument gear = doc.addObject("Part::FeaturePython", "InvoluteGear") InvoluteGearFeature(gear) gear.ViewObject.Proxy = 0 # 取消视图代理,避免GUI冲突 doc.recompute()

这个方案让齿轮变成真正的参数化对象:双击即可修改参数,回车后自动重算。但要注意两个坑:一是makeInvoluteGear()函数在FreeCAD 0.21版中修复了齿根过渡曲线bug,旧版本生成的齿轮啮合时会有干涉;二是该函数不支持变位齿轮,如果客户要求“正变位系数0.3”,就得自己实现变位计算——公式是r_b = r * cos(alpha),r_a = r + x*m,其中x是变位系数。我们把这部分封装成独立模块,供AI调用。

另一个常见需求是“在齿轮上打孔”。Demo通常直接Part.cut()布尔运算,但工程要求孔必须关联到齿轮参数(如“孔径为模数的1.5倍”)。解决方案是:在齿轮类里添加AddHole方法,动态创建Part::Feature对象,并设置表达式约束:

def add_hole(self, obj, hole_diameter_ratio=1.5): import Part # 获取齿轮外径 outer_diameter = obj.Module * obj.Teeth # 创建孔 hole = doc.addObject("Part::Cylinder", "Hole") hole.Radius = obj.Module * hole_diameter_ratio / 2.0 hole.Height = 10.0 # 设置位置约束:孔中心在齿轮中心 hole.Placement = App.Placement( App.Vector(0,0,0), App.Rotation(App.Vector(0,0,1), 0) ) # 布尔运算 compound = doc.addObject("Part::Compound", "GearWithHole") compound.Links = [obj, hole] doc.recompute() return compound

最后提醒:FreeCAD齿轮工具生成的模型,导出STEP时可能丢失齿形精度。实测发现,makeInvoluteGear()默认用128段线段逼近渐开线,而高端CAE软件要求至少512段。解决方案是在调用时传入precision=512参数(FreeCAD 0.21+支持)。这个参数不在官方文档里,是源码里埋的彩蛋——src/Mod/Part/App/PrimitiveFeatures.cpp第1234行。

6. 工程落地 checklist:从Demo到产线的12个验证点

别信PPT,用这张表逐项验证。我在三个客户现场用它筛掉了7个“看起来很美”的AI-CAD方案。

序号验证项测试方法合格标准失败案例
1CLI可重复执行连续运行10次相同命令100%成功,无内存泄漏第3次后FreeCAD进程占用内存超2GB,OOM崩溃
2DXF图层分离用AutoCAD Layer Manager检查至少3个图层,各含对应实体所有线条都在"0"图层,客户拒收
3尺寸标注可编辑在AutoCAD中双击尺寸修改修改后关联几何自动更新尺寸与几何无关联,改尺寸图形不动
4文字字体兼容用不同AutoCAD版本打开不显示“?”或字体替换警告AutoCAD 2018显示乱码,2024正常
5导出文件大小ls -lh output.dxf≤5MB(A4图纸)生成32MB文件,网络传输超时
6几何精度误差用ezdxf读取圆半径与输入值误差≤1e-6输入半径15.0,导出为15.0000012
7参数化修改响应修改齿轮齿数后双击重算3秒内完成,形状正确修改后模型消失,需重启FreeCAD
8错误处理机制故意向AI输入“生成直径1000mm的齿轮”返回明确错误码,不崩溃FreeCAD直接退出,无日志
9批量处理稳定性一次处理50个不同参数全部成功,平均耗时≤8秒/个第17个失败,后续全部阻塞
10ERP系统集成从ERP发起HTTP请求5秒内返回DXF文件URL超时,需人工干预
11版本兼容性在FreeCAD 0.20/0.21/0.22上测试功能一致,无API报错0.22版makeInvoluteGear()参数名变更
12故障恢复能力强制kill FreeCAD进程后重试自动清理临时文件,继续执行临时FCStd文件残留,磁盘占满

特别强调第8项:错误处理。很多AI-CAD方案把异常全包在try-except里,打印“生成失败”就完事。工程要求的是可诊断错误。我们的标准是:每个CLI调用必须返回结构化JSON,包含error_code(如E_GEOMETRY_INVALID)、error_location(如line_42_in_generate_gear.py)、suggestion(如“齿数必须为整数,当前输入'24.5'”)。这个JSON直接喂给客户IT部门,他们能据此写监控告警——这才是真正的工程闭环。

最后分享一个血泪经验:所有AI-CAD项目,上线前必须做“断电测试”。拔掉服务器电源,等5分钟再开机,检查FreeCAD CLI服务是否自动重启、临时文件是否自动清理、未完成任务是否排队重试。我们曾有个客户,因没做这项测试,产线停电后AI服务卡死,导致当天200张图纸全部积压,损失超15万元。记住:工程不是“能跑就行”,是“断电后还能跑”。

我在实际操作中发现,最有效的推进方式不是推销AI多强大,而是带着客户工程师一起跑一遍CLI日志。当他们亲眼看到freecad-cli --script gear.py --params "z=36"输出的{"status":"success","fcstd":"/tmp/gear.FCStd"},再亲手用ezdxf解析这个FCStd导出的DXF,确认图层、精度、字体全部达标——那一刻,Demo和工程的距离,才真正被填平。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 6:19:51

CAP定理在时序数据库中的扭曲表现与优化实践

很多人第一次接触CAP定理是在分布式系统教科书上&#xff0c;讲的是Consistency、Availability、Partition Tolerance三者不可兼得。做业务数据库的人通常记住一句话&#xff1a;网络分区发生时&#xff0c;要么保一致性&#xff0c;要么保可用性。这套框架用在传统OLTP系统上问…

作者头像 李华
网站建设 2026/10/1 4:49:33

AI落地三大隐形成本:数据清洗、模型监控与人机协作

1. 这份报告不是“抄来的PPT”&#xff0c;而是我蹲在一线三年攒出来的趋势手记“AI发展趋势调研报告”这八个字&#xff0c;现在几乎成了所有行业会议、立项材料、融资BP里必塞的标配模块。但说实话&#xff0c;我见过太多所谓“报告”——要么是把Gartner曲线截图放大三倍配个…

作者头像 李华
网站建设 2026/10/1 4:49:33

从埃氏筛到线性筛:质数筛法的原理、优化与工程实践

1. "判断质数"和"筛质数"是两码事&#xff1a;从 O(√n) 到 O(n)1.1 单个数判断的最朴素养子我第一次接触质数筛法&#xff0c;是在想通一个问题之后&#xff1a;筛到底是什么意思&#xff1f;很多初学者&#xff08;包括当年的我&#xff09;刚上手时&…

作者头像 李华
网站建设 2026/10/1 4:49:33

AI落地实战指南:小模型、可插拔服务与数据活化

1. 这份报告不是“抄来的PPT”&#xff0c;而是我蹲在产线、泡在实验室、跟37个AI团队聊完后攒出来的真东西“AI发展趋势调研报告”这八个字&#xff0c;现在满大街都在用——招聘JD里写“需熟悉AI发展趋势”&#xff0c;投资人尽调清单第一条是“请提供贵司对AI发展趋势的理解…

作者头像 李华
网站建设 2026/10/1 4:48:59

基于SpringBoot+Vue+MyBatis+MySQL的服装生产管理系统

前阵子整理了一套基于SpringBootVue的服装生产管理系统源码&#xff0c;春节前刚好利用这套骨架给一家做针织衫的中小型服装厂做过信息化改造摸底。整套系统跑下来&#xff0c;我觉得它特别适合两类人&#xff1a;一类是刚入行Java后端、想找一个能完整跑通的实战项目练手的朋友…

作者头像 李华
网站建设 2026/10/1 4:48:38

次世代PBR全流程实战:酒桶战锤ZBrush雕刻与Substance Painter材质制作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华