Python开发--踩雷历程排雷指引【小白向】

写在前面
本内容是我作为入坑ghpython开发半年以后,回顾这段时间产生过的一些犹疑,与对应的心得做一个总结,以期许对有类似经历的人有些许帮助。
主要面向对象是觉得gh中的插件并不能满足需求,想进行初步的自定义脚本功能,但是看了一圈教程查了很多资料仍然一头雾水的人。如果大神看到本文中粗略的地方予以指正,我非常感激。

开始编程之前

学什么语言好

这个问题属于老生常谈,对于完全新人,我个人强烈建议是从C#入门。
然后下面的文字完全就不用再看了。

但是我听说python更容易上手

如果担忧上手难度,那就学python。

从哪里开始

官方列了一个指引,地址在这里https://developer.rhino3d.com/guides/。
你说英语不好怎么办?建议装一个网页上的浏览器翻译插件,勉强磕巴也能看。

如何学习python

官方给出了一个python的入门指引,地址在上边网址的一个小节中(https://developer.rhino3d.com/guides/rhinopython/)。
如果看不懂英语,python中文学习资源现在是网上一抓一大把了,可以找第三方的教程学习,需要注意python版本,必须学习python2的教程(虽然实际上rhino用的是ironpython2.7 ),然后再去看官方指引(非常必要)。
python基础知识学习以后就可以开始初步的使用rhinopython进行一些练习,不必要求全盘掌握python才可以进入实战,平白给自己上压力,自己劝退自己。
遇到问题建议使用必应搜索,有条件使用谷歌搜索。

开始编程后的难题

不知道怎么实现自己想要的功能

首先要理解一件事,python不是灵丹妙药,是基于对rhino的理解上深入到代码层来进行功能实现。如果对想要实现的功能,在rhino中就不知道怎么做,那使用python基本上更是完成不了。

在明白了如何手动实现以后,把rhino的操作界面语言切换到英文,记录关键操作中的英文指令。

然后打开官方为rhino.python开发的api列表(Rhino - RhinoScriptSyntax ),查询对应的api,这个api中还提供了对应功能的范例,(如果顺利的查询到了的话)抄着用即可。

举个例子

想创建一个点,记录rhino中的对应命令是’_Point’,查找api列表搜索到了很多结果,不过望文生义,其中应该只有两个是需要的,分别是"AddPoint""AddPoints"
就拿 “AddPoint” 举例,在"AddPoint"中可以看到一段代码:

AddPoint(point, y=None, z=None)

和一段对应的说明,以及最后的范例:

import rhinoscriptsyntax as rs
rs.AddPoint( (1,2,3) )

如果看了官方的python指引的话就明白如果要使用RhinoScriptSyntax,这一行 ”import rhinoscriptsyntax as rs“ 就必不可少。
如果没看也暂时没关系(有空还是建议去看完),”import rhinoscriptsyntax “指的是从RhinoScriptSyntax导入这些功能以备使用,”…… as rs“则是为了用的时候少打点字,与 ”rs.AddPoint( (1,2,3) )“ 中开头的 "rs"相对应,如果前面是 ”import rhinoscriptsyntax as abc“,那后面就应该是 “abc.AddPoint( (1,2,3) )” 。

RhinoScriptSyntax没有自己需要的功能

对于新手而言,那就惨了。
非常非常非常惨。

这时候有两条路:

  1. 直接调用 RhinoCommon,办法是 import Rhino
  2. 使用 rhinoscriptsyntax.Command 复现命令,就像手动在rhino的命令行中做的那样

如果使用 RhinoCommon ,那从python入门就很尴尬,因为大部分的范例只有C#和VB,虽然python节约了入门的时间,同样的函数也可以在python中调用,但是这个时候得开始还债,必须学习C#的基础知识,至少明白其意味着什么才能明白怎么去使用它。

我就在频繁的面对这个困难,这就是我在开头强烈强调从C#入门的原因。
也很抱歉,关于这个部分我也没办法分享我的经验。

不过好在官方对于rhinoscriptsyntax更新的很快,如果C#实在啃不下去,只好等官方更新。

如果使用 rhinoscriptsyntax.Command ,则需要注意性能上的问题,对于多行命令运行的 非常慢 ,不利于大量的使用。在非用不可的情况下建议想办法尽可能地对多个对象同时操作,而不是对一个对象操作完再进行多次重复。

python编译出错了怎么办

新手遇到的大部分情况下是python上的错误,特征是下方报错的 “Traceback” 只有一行。这时候直接在搜索引擎中依靠关键字找寻答案即可。

如果"Traceback" 有多行情况下,则是对调用的函数调用方法出了错,显著特征是代码只有几十行,但是 “Traceback” 中显示的出错行数(line )远超当前文本。真正的错误位置则是最后一行,格式是

line 数字 in script

这个数字就是实际错误代码所在行数。错误内容就具体情况具体分析。

个人经历的奇怪问题与我的野蛮解决办法

***** does not exist in ObjectTable

这根源由于python中自动转换信息格式。

虽然理论上rhinoscriptsyntax返回的是guid:

f6ca4eb2-5ef6-4c8c-a8d5-5861dac8c45e

但是有时候返回的信息长这样:

System.Guid object at 0x00000000000056DF [f6ca4eb2-5ef6-4c8c-a8d5-5861dac8c45e]

这就造成在gh中使用的python,拾取了rhino中的物件后,实际上存储了两个物件信息:

  • 一个是在rhino中物件的guid(就是rhino中对象-Geometry的一个唯一标识符);
  • 一个是在gh的文档空间中同时对这个物体创建了一个参照(这个描述不是很准确),典型特征是 "0x00000000000056DF"这一串字符在每一次运行时候会变化,而后面的GUID则应该是不变的。

如果把上一个ghpython中输出的物体信息输入到下一个ghpython中时,(在不对电池输入端的Type hint做修改时)输入的实际上是gh空间的参照物体guid,检查guid会发现与上一个电池中输出的信息不同,这时候如果按照guid去查找rhino中的物体,自然就会出现这个错误。

解决办法是对输出结果做一个限制,输出结果单独存储一份guid的字符串信息,避免python中自动转换信息格式。

也就是

str( GUID )

待续

5 个赞

发现主贴编辑有时效性,尬住了,在后面追加内容把。

追加一:

怎么在rhinopython中使用库和第三方python库

虽然使用的是ironpython,但是仍然已经集成了很多库,直接使用 import 导入即可。

如果内置的不满足需求,这需要把第三方python库放到对应的目录内,目录查看方法在rhino的工具栏中 Tools → Python Script → edit,会打开一个python的简易编译器。编译器中继续工具栏 Tools → Options ,弹出一个面板,面板的第一个标签页(也就是默认的页面)中有三个文件目录,我拿第一个举例(也就是程序的安装目录,实际上三个目录任意一个效果都一样):

C:\Program Files\Rhino 7\Plug-ins\IronPython\Lib

打开后会发现其中已经有了很多文件夹,把下载下来的第三方库的 文件夹 放到这个目录下,第三方库中有些不单单含有脚本,还有些做了实例等等附属文件,这些文件并不必须。甄别的方法是在真正的脚本文件夹的第一层目录下会有一个 init.py" 文件。

注意:
第三方库大多数时候会有依赖的其它库,但是在rhinopython中并不利于调试。
建议在独立的python环境中通过pip安装测试后,直接把库文件拷入到rhinopython的目录中更方便。

追加二:

怎么理解 Rhino、scriptcontext、 rhinoscriptsyntax, 什么时候该用什么

后来我发现这个问题属于年更问题,不止我一个人问,我就释然了

Rhino:

一切都是以Rhino为基础(也就是官方说的RhinoCommon,注意与Command做区分,后者是命令行,也就是指令巨集)。调用方法为

import Rhino

由于它对学习C#的更友好,所以我还得再次单方面强调C#在这个应用场景中秒杀python。纵然官方使用了IronPython就是为了方便调用RhinoCommon降低了上手成本,然而就以我的体验而言,降低的相当有限,不如新手直接学C#(忽略python中庞大的第三方库数量而言)。

rhinoscriptsyntax:

官方对于常用的一些功能把RhinoCommon直接封装了一下,易于理解,说明文档更全面,也更容易使用。

但不是封装了所有的RhinoCommon。

萌新的福音,进阶的拦路虎,给rhinopython披上了易于使用的伪装。

scriptcontext:

不是很好理解的东西,个人建议忽略这个东西。
有且只有在ghpython中想和rhino中的物件做互动时候,使用一次即可。
这是我在论坛提问时候官方的一个说明:请问关于scriptcontext的帮助文档哪里可以找到? - #11,来自 Jorin
如果你能看明白这里就不用看下面我个人的理解翻译出来的废话。

之所以存在这个是因为gh的特殊性与rhinoscriptsyntax运行的特性。

首先我们回到单纯的使用gh的场景,很多电池创建了物件,但是在rhino中并不存在(只可以显示),无法选取也无法操作,只有通过 bake 指令才可以在rhino中实际存在。

这是因为rhino有一个doc(文档?)的概念存在(不要问我更深层次的东西,我也不明白意义何在),我们平时在rhino中创建的物件,从程序的角度看信息保存在 Rhino.RhinoDoc.ActiveDoc

官方相关文档:
Rhino - Rhino objects in Python #简单
Rhino - 8 Geometry #极难且必要

而GH中也参考了这种设计,存在一个它自己的doc,名字就是 ghdoc ,与Rhino.RhinoDoc.ActiveDoc在程序的概念上看大部分时候都仅仅只有名字不同。
在gh中创建的物体,虽然并没有在rhino中存在,但是实际上存储在ghdoc中。而 gh 中的 bake 指令做的事也就可以理解为把对应的物件信息从ghdoc中复制到 Rhino.RhinoDoc.ActiveDoc 中一份。

ghpython中的bake官方范例:Rhino - Custom GhPython Baking Component

而需要注意的是,ghdoc 和 Rhino.RhinoDoc.ActiveDoc 是相互独立的。

那么由于这种相互独立,也就造成了如果在 gh 的 python 电池中运行,默认情况应该是对 ghdoc 中的物体进行处理,如果要对 rhino 中的物件进行处理,就必须把程序所操作的 doc 切换为 Rhino.RhinoDoc.ActiveDoc 。

scriptcontext就是为了做这件事。(当然实际上能做得更多)
在忽略掉ghdoc和Rhino.RhinoDoc.ActiveDoc时候,scriptcontext.doc 存储的的就是 rhinoscriptsyntax 操作的对象所在的doc,当需要对rhino中的物件信息进行操作时候,就把 scriptcontext.doc 指向 Rhino.RhinoDoc.ActiveDoc。

scriptcontext.doc = Rhino.RhinoDoc.ActiveDoc

加入上一行代码以后,就可以在 gh 的 python 中对rhino中物体信息进行互动(创建、拾取、读取信息、修改等)。

如果要对ghdoc中的物体信息进行互动,就得切换为 ghdoc :

scriptcontext.doc = ghdoc
# 普通情况下,gh中的python默认运行就是在ghdoc中,不需要手动加这一行代码。

扩展一:

由于前面强调过的 ghdoc 和 Rhino.RhinoDoc.ActiveDoc 相互独立"的特性,也就意味着切换doc时候需要注意 其中保存的物体信息不通用

记得吗,前面的 “易用的RhinoScriptSyntax” 中有关物体的函数中,主要的输入物件信息是guid,主要的返回信息也是guid。而guid只是物体的一个属性,只是相当于物体对于程序的名字,并没有保存物体的实际信息。

也就是在程序中的信息层级可以(不准确的)表述为:

—doc
------物体1…
-----------guid(自动生成且唯一)
-----------属性
-----------几何信息
-----------……
------物体2…
------物体3…

也就意味着,如果使用的是 “易用的RhinoScriptSyntax” ,保存到变量中了一个guid,在切换了文档(doc)以后,再使用“易用的RhinoScriptSyntax” ,就会发现查无此物,因为按照一个doc中的guid是不可能查得到另一个doc中的信息的,它没这个物件。

也就是说如果要保障切换doc时候的顺利进行,就得把要传递的具体信息保存到变量中,而不能只是仅仅记录guid。需要几何信息就把几何信息(geometry)记录下来,需要其他信息就把其他信息记录下来。

上面是说的理论上

然而不同于理论上,ghpython做了一个优化——为了保障RhinoScriptSyntax的易用性,在ghpython中的guid并不仅仅是一串字符串(string),是一个(Guid),是的,这就是一个格式。

可以用python内置的 type() 来校验,输出结果为:

36228887-86a2-4c6f-a60a-c11f4d7518b4
<type 'Guid'>

虽然官方的说明是:“RhinoScriptSyntax 以字符串的形式返回对象标识符”。

但是这个 Guid 的格式在实测中是包含了物体信息的(也就会造成在一些特殊情况下会产生奇怪的问题)。

换而言之,即使是在ghpython中用RhinoScriptSyntax返回的Guid作为输出,gh也会自动把物体信息(Object信息含有attributes、geometry等)从 Rhino.RhinoDoc.ActiveDoc 复制到了 ghdoc 中去,那么其中的几何信息(geometry)也可以作为后续电池的输入。

但这样产生了几个需要注意的问题:

  1. Guid格式中似乎没有保存所有的物体信息,只包含了几何信息。
  2. 输出的Guid如果接入到 gh 的 panel 中,有的时候传递的是 Rhino.RhinoDoc.ActiveDoc 中字符串格式的guid,有的时候是ghdoc中物件的几何信息。
  3. 输出的Guid如果接入到 gh 的 id 中,有时候可以成功转为 id,有时候则会报错:“Data conversion failed from geometry to Guid ”。
  4. 输出的 Guid 如果接入到下一个ghpython的输入端,如果输入端属性为默认(ghdoc Object when geometry ),则输入信息为ghdoc中的物件 Guid,也就意味着明明上一个电池输出的是Rhino.RhinoDoc.ActiveDoc ,但是到了下一个电池中信息自动变成了ghdoc。如果输入端属性限定为 str 或者 Guid,那么接第二条,panel显示为字符串的guid时候运行正常,如果panel显示为几何信息(point、surface、brep……),那么则会报错:“Data conversion failed from geometry to Guid ”。

总之,在需要切换doc时候,不要信任 “易用的RhinoScriptSyntax” 与它使用的 Guid 机制。

3 个赞

如果有可能,期待后续的排雷指引;另外,我想问一个之前你发过的一个话题“ 在gh与rhino之间应该如何传递物体并保留物体信息”关于这个问题我不知道你这边是否还有后续的内容,但我刚好也遇到了相关的问题,希望能您能给予解惑 ;问题如下:一个物件在rhino中被拾取进入到GH中,直接接入Guid电池是可以看到相应的ID的,但这个物件在进行任何操作,按照你说的,这个时候的物件应该是ghdoc内的物件,我想问的是,被拾取的物件被赋予了KV值,如何能让这个物件在进行了一系列的GH内的处理后,依然能将这个物件对应的KV值再次提取出来,我能想到的就是,要么让ID一直跟随物件,要么就是ghdoc内有个类似ID的信息能与RhinoDoc的ID对应;不知道你是否了解这个原理,这也是eleFront里的一个功能,KV值能跟随物件,物件移动后或这变换位置后,哪怕多次变动,也能让KV值跟随。