做手作和写代码这两件事在我这儿一直不太分家。前几年折腾过不少生成艺术的小项目每次想从屏幕里的图案变成现实里能挂上墙、能拼装、能送人的实物都要在绘图软件、脚本、激光切割参数、纸张克重之间来回倒腾烦得不行。后来我干脆把常用的那套流程抽出来攒了一个叫artcraft的个人工具库专门解决程序生成图案 → 变成可制作的设计文件这条路线上的一系列问题。这篇文章就是把 artcraft 里我自己觉得最值钱的那部分设计思路和踩坑经验整理出来如果你也喜欢写点小脚本做设计、想把自己的数字图案做成实体物件应该能直接拿去用。我一开始没打算把它做成一个框架或者平台它就是一套 Python 模块加一堆约定俗成的规则核心解决三件事让每次生成的图案可以稳定复现、让图案能直接对接激光切割和印刷流程、让不懂代码的人也能通过改参数玩出不同效果。听起来不算难但真正做起来里面的细节比我想象的多得多。下面按我实际开发的顺序来聊。1. 为什么会有 artcraft程序生成图案和实体制作之间的断层1.1 从一次挂画翻车说起事情起因很简单。我想给工作室的墙面做一组几何装饰盘打算用程序生成几十种不同的同心圆和波尔卡圆点组合然后切成亚克力板或者密度板挂起来。听起来很顺利对吧结果实际走了一遍流程之后我发现程序里预览完美的图案到了激光切割软件里全部乱套圆环线宽不对、切割路径重叠、螺旋图案被切成了断断续续的线段、最离谱的是图案边缘留白不够成品根本没法装裱。那会儿我才意识到程序生成图案这件事做到好看只是第一步。真正难的是从屏幕像素到机器可执行路径之间那一大段没人爱讲的脏活坐标系怎么统一、线宽怎么表达、什么样的路径是切割机能理解的、倒角要不要做、容差留多少。市面上的绘图代码比如各种 Python 绘图库擅长画给人看的图但它们的输出往往是像素位图或者带了一堆复杂样式属性的矢量文件直接丢给激光切割软件机器会把这些当成绘图指令而不是切割路径结果就是线条该切的地方没切不该烧的地方烧糊了。1.2 现有方案为什么都不顺手我当时试过几条路各有各的别扭。用通用画图库直接画图案再导出简单场景还行但要做参数化批量生成、要精确控制路径的物理尺寸就非常吃力。直接用 CAD 脚本又太工程写几何图案费劲做随机生成更麻烦。还有一条路是用纯 SVG 手写代码这个确实靠谱但手写复杂图案很痛苦而且 SVG 里坑特别多——渐变、滤镜这些花哨属性机器根本不认路径语法稍微写错一点在切割软件里就是各种莫名其妙的结果。后来我翻了很久发现很多做生成艺术的人都有类似的最后一公里问题图案生成得漂漂亮亮一到加工环节就拉垮。与其等着工具进化不如自己动手把这条链路打通。artcraft 最初的雏形其实就是一个能把圆环、螺旋、多边形这类几何元素以毫米为单位的精度输出成切割机能直接用的 SVG 的小脚本。后来用得多了慢慢长出植物纹样生成、纸模展开、织物图案网格这些模块就变成了今天的样子。1.3 artcraft 给自己定的三条规矩这个项目从一开始就给自己定了三条规矩一直到现在都没破过。第一一切坐标以毫米为真实单位。我受够了像素、点、英寸这些乱七八糟的单位换算在 artcraft 里建画布就指定物理尺寸所有几何元素的位置和大小都是毫米导出就是同一个坐标系这样切割软件那边完全不用重新缩放。第二输出优先考虑可制造性。每个生成的元素我会要求自己回答一个问题如果这个东西要上切割机它的每一条边是不是一条机器能走出来的连续路径画很多装饰线没问题但我会分图层装饰线和切割线永远分开。第三一切随机必须可复现。同一个种子值任何时候生成都应该得到一模一样的图案。这听起来像废话但实现起来远比想象中麻烦后面专门讲。这三条规矩帮我省了无数麻烦也决定了整个项目的架构方向。2. 架构设计把做东西拆成可组合的积木2.1 三个核心层级的划分artcraft 的架构没有玄机就是很朴素的三层划分每层只干自己该干的事。生成层负责产生几何对象。这一层不关心最终是切纸还是刻亚克力只管根据参数算出形状。比如一个圆环对象就是圆心坐标、外半径、内半径这三个数的组合。一个 L-system 分支对象就是一堆带层级结构的线段的集合。生成层的所有输出都是几何图元的列表坐标统一用毫米类型统一用我自定义的几个简单结构。这一步很关键——它把画什么和怎么画彻底分开。表达层负责把几何对象翻译成具体的矢量指令。目前主要输出是 SVG。这一层干的事情包括把连续曲线转成折线段、给路径做倒角、给封闭图形标记内部还是外部、把不同用途的线条分到不同图层。表达层是 artcraft 里最笨重但最值得做的部分因为机器看不懂漂亮图形它只认路径和指令。物化层负责处理从平面到实物的专属逻辑纸模的展开折痕、拼接口的容差、激光切割的引入线、印刷的出血边、织物图案的重复单元边界。这一层是传统绘图库完全不管的部分也是 artcraft 区别于一个画图脚本的地方。2.2 为什么统一用 SVG 作为中间格式可能有人会问为什么中间件不选 JSON、不选某种二进制格式偏偏选 SVGSVG 是文本心智负担低出了问题打开文本文件就能定位SVG 本身就是矢量描述几何精度不会损失缩放不糊更关键的是几乎所有激光切割软件、印刷排版工具、纸模设计软件都认 SVG 这个格式兼容性极佳。但 SVG 也不是没有代价。最大的坑在于SVG 规范给了很多方便的机制这些机制恰恰是机器的敌人。比如各种变换 transform机器通常能解析但遇到嵌套变换和特定字体渲染就容易出幺蛾子。所以在 artcraft 里我给自己定了一条铁律导出前所有路径必须平铺展开。所有 transform 全部拍平所有图形元素都转成独立的 aa bb cc dd 坐标路径不依赖任何外部状态。这样可以换来一个极其重要的能力任何一步出错我都能在一个纯粹的路径文件里快速定位。对比试验下来激光切割软件拿到这种裸 SVG 的容错率和加载速度明显优于带一堆变换和样式的版本。2.3 参数注入让不懂代码的人也能玩artcraft 的第三个重要设计是参数是公开接口。我写了很多生成函数但每个函数的入参我都尽量收敛成一个配置字典而不是几十个散落的参数。比如生成一个像素化的圆点图案配置可能是config { seed: 20250412, canvas_size_mm: (300, 300), dot_radius_range_mm: (2, 8), density: 0.2, avoid_overlap: True, layer_origin: score, }这套配置字典直接支持 JSON 读写。这意味着我可以把配置存成文件发给朋友改几个数字就能生成全新的图案。对于纯手工玩家来说不需要懂代码改数字和看效果之间的反馈链路极短这是 artcraft 能被身边不少人实际用起来的重要原因。3. 核心算法实现可复现随机性才是程序化创作的定海神针3.1 随机种子最容易翻车的地方说真的我在做 artcraft 之前完全没想过随机数这件事能坑我这么多次。刚开始我只是简单地用random.seed(seed)控制随机后来发现不同机器、不同 Python 版本同一段代码生成的图案居然不一样。这不是玄学原因是某些随机数算法的实现细节在不同版本间会变。对艺术创作来说这简直是灾难——你今天生成一张满意的图半年后想重新生成结果出来个完全不同的东西。解决方案其实也不高端就是自己实现一个确定性的伪随机数生成器跟环境无关。我用的是一个很经典的小型线性同余生成器自己管理状态不依赖系统随机源。这样带来的好处很多同一份代码在 Windows、Linux、不同 Python 版本下验算结果完全一致而且因为随机数序列是确定的我可以回放生成过程——调试的时候在任意一步插入断点随机数状态完全可控这是调试随机生成代码的利器。class ArtRng: 一个极简但足够用的确定性随机数生成器。 def __init__(self, seed: int): self.state seed 0x7FFFFFFF def next_int(self, bound: int) - int: # 线性同余参数是经典组合保证周期足够长 self.state (self.state * 1103515245 12345) 0x7FFFFFFF return self.state % bound def next_float(self) - float: return self.next_int(1 30) / float(1 30)有了它所有生成逻辑都走ArtRng种子一固定任何一次生成都是可回放的。这个看起来不起眼的设计是 artcraft 可靠性的地基。谁要是做生成艺术我建议第一步先搞定自己可控的随机源。3.2 用 L-system 生成植物纹样artcraft 里我最喜欢、也是被朋友问得最多的模块是植物纹样生成器。这个模块的核心是一种叫 L-system林氏系统的字符串重写算法。思路非常简单先给一个公理字符串比如F然后规定几条替换规则比如F 替换为 FF--FF。反复套用若干次规则后得到一个很长的字符串再按字符串里的每个字符来解释绘图指令F 表示向前画一段线 表示左转- 表示右转。听起来像小孩子玩具但就是这几条规则能生成长得极像真实植物的分支结构。我调参的时候试过蕨叶、绣球枝条、甚至银杏叶脉的样子效果比我预想中好很多。实际实现里我的规则文法略作扩展增加了一个生态位参数不同深度的分支采用不同的长度衰减系数和转角随机抖动范围让成品更有手工感。比如分支深度每增加一层枝干长度乘以 0.72角度偏移加上一个受ArtRng控制的 0 到 8 度抖动。这样既保留了 L-system 的规律性又避免了每一棵都一样的死板感。3.3 构图层从元素堆砌到完成构图几何元素能生成了以后紧接着的问题是怎么组合成一张完整的、有视觉重心的图。artcraft 的构图层做了一个很务实的机制把画布划分成一个虚拟的权重网格每个网格单元有不同的吸引度。生成元素放置时会优先落在权重高的区域权重低的地方作为呼吸空间。比如做装饰盘的时候我把中心区域的权重设得很高边缘设成中低随机生成的一堆圆点就会自然形成中心聚集、四周渐疏的构图而不是均匀地撒在画布上。这里有一个我特别想分享的经验随机不等于均匀。很多人以为随机撒点就是构图了其实纯随机撒下去往往在视觉上是一团糟或者过分均匀。真正需要的是一种受控随机——它看起来自由实际上是遵循了某些分布规则。权重网格只是其中最朴素的一种更高级的还能用泊松盘采样保证点与点之间有最小间距。artcraft 里我两种都做了并且把最小间距暴露成参数让使用者自己决定疏可走马还是密不透风。4. 从屏幕到实体切割打印拼装的适配细节4.1 分图层切割线和装饰线永远不能混我在 1.1 里提到过首次切亚克力翻车的事根源就是线型和图层混乱。现在 artcraft 的 SVG 输出强制分图层而且每个图层的命名有严格后缀约定切割软件和下游工序一眼就能识别。我实际用的图层约定大致是这样图层名用途典型线宽备注cut真正要切穿的轮廓0.01mm越细越好减少切缝损耗score半切压痕线纸模折痕0.01mm同样要细保证折线精确engrave雕刻或打标0.2mm粗细主要是视觉不是刀缝guide辅助定位线0.1mm加工时设为不输出bleed印刷出血区域1.0mm印刷场景用这个约定当初是跟加工商反复确认过几轮才定下来的。有朋友问过我既然都是线为什么不能全用一个图层因为切割软件通常只认红色图层代表切割、黑色图层代表雕刻这一类的语义混在一起机器不知道哪条线该用力切穿哪条线轻轻划一道就行。分层就是给机器传递加工语义的最小成本方式这条经验无论你用哪种工具导出都值得直接抄走。4.2 纸模展开从 3D 造型到 2D 展开面artcraft 另一个我没想到会这么受欢迎的功能是纸模展开。起因是我想做一个多面体的纸模型手工把 3D 模型摊开成 2D 图纸太痛苦了于是写了段代码做这件事。原理是图形学里很经典的网格展平把三维模型的每个三角面当作刚体通过迭代把三角形逐步摊到平面上同时尽量减少面与面之间的拉伸变形。这个算法的效果取决于模型拓扑和迭代次数。我跑了几个模型对比过最直观的感受是球状的模型展开会非常难看因为高斯曲率不为零无论怎么摊都会有大变形而棱柱、金字塔这类多边形物体展平效果极好。所以 artcraft 的纸模模块我把适用场景写得很清楚推荐做低多边形风格或者棱柱风格的造型球形造型请换用其他思路。展开之后还有两个细节很容易被忽略一是折痕线的方向要区分山折和谷折否则拼的时候会懵二是粘贴耳的尺寸补偿厚度 0.5mm 的卡纸和 0.2mm 的薄纸粘耳的宽度需求完全不同我在模块里默认留了 6mm 到 8mm 的可调区间。4.3 容差与材料补偿毫米级的较真做实体切割最容易被忽视的是材料损耗带来的尺寸偏移。激光切割的切缝通常有 0.1mm 到 0.3mm 宽纸张的裁切误差又是另一个数量级。如果你设计两个零件要完美拼插图纸上画得严丝合缝实际切出来一定会松或者紧。artcraft 的解决方式是把补偿值做成一个全局参数自动应用到所有需要配合的边缘上。比如拼插结构我会让凸起的榫头宽度减去两倍切缝宽度让预留的插槽宽度加上一点点松量。做过几次拼装之后你会对补偿值变得特别敏感像刻在骨头里一样——我现在看到任何严丝合缝的设计图纸第一反应都是实际加工后还合得上吗5. 用 artcraft 实际做出来的三件东西5.1 墙面装饰几何盘组回到文章开头那面墙。后来我用 artcraft 重新生成了一组装饰盘设计38 厘米直径的圆盘中心是受控随机撒点形成的渐变圆点外圈用 L-system 长出一圈枝叶状的镂空花纹。输出分两层cut层走切割engrave层做浅雕。上机切割后一次成功尺寸精度肉眼可见地好。那组盘子挂在工作室几年了每次有朋友来都会被问是不是买的——这就是程序化设计最爽的时刻。5.2 低多边形纸模摆件第二件是给朋友生日做的纪念日纸模用低多边形风做了一个抽象造型然后走纸模展开模块摊平打印到 200g 卡纸上手动裁切拼装。这里我很想强调一个体验上的细节因为展开图是我自己代码生成的我知道每个编号对应的粘贴位置所以整个过程完全不需要看说明书。后来我把这个能力顺手做成了自动贴编号功能——每片图纸面上印一个小序号拼装的时候按序号顺序粘连想都不用想。5.3 可编程织物图案第三件东西稍微跨了点界。我开始做拼布时想用程序生成重复性图案artcraft 的织物图案模块就是那时候写的。它做的事情很简单定义一个基础图案单元然后设定行距列距在画布上平铺但每个单元的转向、镜像、配色可以做受控随机。这样印出来的布既有重复纹样的秩序感又不会有纯平铺的单调。印布的时候我没走激光是走数码印花厂输出格式要求略有不同artcraft 里单独切了一个印刷模式出血边、色彩模式、分辨率都有对应的预设。这件事给我的启发是工具要做得好用必须贴近真实工序去设计接口而不是只在图纸世界里自洽。6. 避坑清单替大家踩过的那些坑写到这里把 artcraft 开发过程中踩过最深的几个坑集中列一下。这些经验没有写在任何官方文档里都是拿真金白银的废料换来的。6.1 单位和缩放问题的元凶DPI 混乱SVG 里的单位换算是我开坑第一天就遇到的第一个大坑。很多绘图库输出 SVG 时按 72 DPI 算而激光切割软件内部可能是按 96 DPI 或者直接按像素解释。结果同样一个10cm的图形在不同软件之间打开尺寸时大时小。artcraft 的解法是从源头锁死生成阶段所有数据只认毫米导出时在 SVG 根节点显式声明width、height和viewBox三个值都按毫米数值写并且我自己做一遍导出再解析的自动化测试每次改完代码立刻验证导出文件的尺寸是否仍然正确。这种自测一下的习惯比任何约定都靠谱。6.2 字体的坑你的文字在别人电脑上可能是一堆豆腐块程序生成的图案里经常要写字比如编号、标题、装饰性文字。最初我直接输出text标签本地预览完全正常结果文件发给朋友他的电脑上没装我用的那个字体整个图案的排版全崩了。现在 artcraft 里所有文字默认在导出前先转成路径——也就是说文字直接变成封闭的矢量轮廓不再依赖任何外部字体文件。这个功能的实现也不复杂就是调用底层字体渲染把每个字形变成轮廓点集然后合并进现有路径数据里。代价是文件体积变大、文字不再可编辑但对于加工场景来说这点代价完全值得。6.3 随机数漂移跨版本生成不一致的坑前面 3.1 已经详细讲过随机数的问题这里只补充一个印象特别深的案例有次我升级了自己的 Python 环境跑同一个种子生成一批旧图案结果细节差异巨大当时差点以为程序有 bug。后来查明就是随机算法实现变了。这提醒我做生成艺术可复现性不是一个锦上添花的特性而是基本要求。建议任何做程序化创作的朋友第一件事就是把随机源和系统环境隔离自己管好自己的随机数。6.4 过度优化是假需求能用才是硬道理最后这条可能有点反直觉。artcraft 最开始我特别想优化各种算法的性能比如 L-system 字符串重写、网格展平的迭代收敛速度。后来发现做图案生成和做游戏引擎完全是两码事——我们一次生成用时一到两秒完全可接受根本不需要毫秒级优化。反过来真正值得花时间的是那些软件工程之外的事情图层约定、文件组织、参数注释、输出文件的目录结构。一个生成脚本的目录里文件怎么命名、配置怎么归档、匹配什么材料参数这些才是决定你用起来顺不顺手的关键。我后来花在工程化整理上的时间远远超过了花在算法调优上的时间效果反而好得多。artcraft 到现在仍然是我手边最常用的工具之一但我不太打算把它包装成一个产品去开源发布它更像我的数字工作台顺手的地方太多牵一发动全身。如果你也想搭一个类似的工具链我的建议是从最小的闭环开始先解决图案生成 导出切割文件这一条线跑通一次真实制作再慢慢长模块。工具是长出来的不是设计出来的。