这篇文章是属于SketchUp Ruby教程的第三篇,也是第二部分的第一篇。上一部分教程目的在于通过一个简短的例子大概地窥探SketchUp Ruby的用法,因此很多涉及到的细枝末节被一笔带过甚至隐藏。但从这一部分开始,教程将尽可能地展开内容。
这一部分的核心是介绍SketchUp的基本结构,为了更好的理解这些结构,需要一些例子来展示它们的功能。而理解这些例子需要一些Ruby的语法知识,因此在展开介绍基本结构之前需要对Ruby的数据类型和基础语法有所了解。于是这一部分将分为三篇文章,分别介绍Ruby的数据类型、基础语法和SketchUp Ruby的基本结构。
第一篇:Ruby的数据类型
前期工作:打开SketchUp,在“窗口”菜单中选择“Ruby控制台”。
(1)基础数据类型
在控制台中分别依次输入以下几行代码,每一次完成一行后回车确认,便可以看到控制台执行这行代码的结果(“#>>”之后展示返回值):
1.class
1.1.class
[1,2,3,4,5].class
"str".class
(1==2).class
(3>2).class
以上的代码展示几种基本的数据类型,分别是整型数(FixNum)、浮点数(Float)、数组(Array)、字符串(String)和布尔类型(TrueClass / FalseClass)。“.class”方法可以查看这些数据的类型。整型数和浮点数用于表示数的概念,而数组常常用于表示多维向量、复数和集合这些数学概念。字符串用于表示名义上的概念,如文件名、组件名称、文字标注等概念。最后两项的TrueClass和FalseClass是布尔类型,用于表示条件是否成立;这是它们的类型名称,而它们的值分别表示为“true”和“false”。false和FalseClass的关系类似于0和FixNum的关系,前者是数值,后者是类型;但是不同于整型数多对一的关系,两个布尔数值分别对应唯一的一个类型。以下代码可以查看布尔变量的值而非类型:
每一种类型都有对应的赋值、运算和比较的方法。在ruby中,变量没有强制的类型要求,一个变量存进整型数它就是整型变量,存进字符串它就是字符串变量。尝试一下赋值语句,将不同类型的数值赋值给不同的变量:
a=128
b=2.7182
c=[98,4,30]
d="Apiglio"
e=true
f=false
puts "#{a},#{b},#{c},#{d},#{e},#{f}"
注:以上代码中第8行表示依次输出a到f并以逗号隔开,这是Ruby的格式化输出方法(之一)。
赋值是将数值赋予变量,而运算是通过一个或多个数值计算得出一个某种类型的结果。就像向量的加减结果是向量,但点乘的结果是数,运算的结果可以不是参与计算的变量的类型,因此前文所说的“运算”和“比较”都是运算的概念,只不过后者返回的是布尔变量而已。以下直接用代码附上结果和说明来简单介绍不同数据类型的一些常用运算方式。
①数的算术运算
4+2
4-3
4*3
12/3
13/3
13.0/3
1.0/0
13%3
2**10
②字符串和数组的运算
"Ap"+"iglio"
"ASD-"*3
[1,2,3,4]+[3,4,5,6]
[1,2,3,4,5,6]-[2,5]
[1,2,3,4,5]|[3,4,5,6,7]
#>>[1,2,3,4,5,6,7] 并集运算
[1,2,3,4,5]&[3,4,5,6,7]
#>>[3,4,5] 交集运算
③比较运算
1>2
1==1
1<=1
"Apemiro"!="Apiglio"
1<=>2
④逻辑运算
true and true
true && true
true or false
true || false
not true
! true
false ^ false
以上出现的这些用来代表赋值、运算和比较的符号都被称之为运算符,它们可以分为赋值运算符、算术运算符、比较运算符和逻辑运算符。除此之外还有位运算符和条件(三元)运算符,赋值运算符也不止“=”一种。其他的运算符有一些仅仅是类似方言的存在,其功能可以被完全代替,在此处暂时不去理会,在有需要的时候会再介绍。
这些类型之间可以相互转换的,只需要在其后使用“.to_x”的方法:
⑤类型转换
1.to_f
1.0.to_i
12358.to_s
"1+4i".to_c
[1,2,3,4].to_a
通常情况下,包括SketchUp的数据结构在内的其他数据类型也会有类似的转化方法。类似的也有其他类似的类型转换方法,也都是形如“.to_xxx”的方法。
关于字符串和数组还可以在多说几句,是有关它们整体与局部之间的关系。字符串是一连串字母元素有序组成的整体,数组则更为广泛的可以表示任何类型数据元素的排列。在使用过程中,有的时候需要对其中的某一些元素进行操作,这需要了解它们的下标。以下是一些例子:
⑥字符串和数组的下标与范围
"string"
[0,10,20,30,40,50]
"string"[3]
[0,10,20,30,40,50][3]
"string"[1,4]
[0,10,20,30,40,50][1,4]
"string"[1..3]
[0,10,20,30,40,50][1..3]
"string"[1...3]
[0,10,20,30,40,50][1...3]
"string"[-3..-1]
[0,10,20,30,40,50][-3..-1]
"string"[-3,2]
[0,10,20,30,40,50][-3,2]
⑦数组元素的增加与去除
arr=[1,2,3,4]
puts arr.to_s
arr << 5
puts arr.to_s
arr.push 6
puts arr.to_s
puts arr.pop
puts arr.to_s
puts arr.shift
puts arr.to_s
arr.unshift(1)
puts arr.to_s
arr.insert(2,2.5)
puts arr.to_s
arr=[1,2,2,3,3,3]
arr.delete(2)
puts arr.to_s
⑧数组的排序、去重复和扁平化
[1,4,2,8,5,7].sort
[1,4,2,8,5,7].sort.reverse
[1,4,2,8,5,7].sort{|a,b|-(a<=>b)}
[1,4,2,8,5,7].sort{|a,b|a<=>b}
[1,2,2,3,3,3].uniq
[1,2,3,[1,2],[2,3]].flatten
arr=[1,3,3,2,4]
puts arr.sort.to_s
puts arr.uniq.to_s
puts arr.to_s
puts arr.sort!.to_s
puts arr.uniq!.to_s
puts arr.to_s
以上直接用代码展示了基本数据类型的一些常见用法,不过实际上基本数据类型并不存在与Ruby之中,包括这些类型也都是对象的概念,包括之前的运算符和之后的一些方法都是以对象的公有方法的身份来运行的。
123*342
123.*(342)
"Pineapple"+"Apple"
"Pineapple".+("Apple")
以上代码中的第一行和第三行是通常理解意义上的运算方式,但Ruby是分别将它们解析成第二行和第四行的样子,并调用字符串类的“.*”或者“.+”方法实现的,而每一个整型数、浮点数、字符串、数组和布尔类型其实都是“类”的概念,每一个数字,每一个字符串都是这些类的实例。为了更好地理解这个概念,将需要粗略的理解一下类与对象的概念,这也引出了下一个部分。
(2)类与对象
类与对象是一种抽象的方式,任何事物都可以被抽象为“对象”,而这个对象又一定属于某一些范畴,我们对具体一个事物的认识是通过将这个“对象”归于某个范畴,因而以这个范畴的特征来认识它。就像三段论一样,钠是碱金属,碱金属具有强还原性,所以钠具有强还原性。同样地,还可以描述成这样:object属于Class,所有Class都能够method,所以object也能够method。
所以object、Class和method的关系就很清楚了。其中object是对象,Class是类,method是类的方法。如果object属于Class,那么object就是Class的一个实例。这样一来“对象”、“类”、“方法”和“实例”这些概念就基本清晰了。
可能有强迫症患者会有因为上述例子中只有Class首字母大写而另两个没有表示不适,但是这是ruby的语法决定的。第一节课就有说过,ruby是大小写敏感的,“class”和“Class”是两个不一样的标识符。大写字母表示常量、类和模块名称,而变量、对象和方法以小写字母和下划线开头。
回到之前碱金属钠的例子,现在使用ruby构造一块属于碱金属(AlkaliMetal)的钠(sodium),并将它投入水(water)中就可以用以下的代码来实现:
class AlkaliMetal
def drop_into(targ)
end
end
sodium=AlkaliMetal.new
sodium.drop_into(water)
这里首先定义了一个“AlkaliMetal”类,并且定义有“.drop_into”的方法,表示扔进某种其他物质里头。“targ”是这个方法的参数,在第8行中即“water”,也是一个对象(在别处也有它自己的定义),它能够帮助“.drop_into”方法明确具体以何种方式执行。而执行过程中“sodium”对象会熔化并逐渐消失在水中,同时“water”对象也会有相应的数据修改,比如说内含的离子浓度和温度这样的属性。但如果“.drop_into”方法的参数不是“water”而是煤油,那么参数的修改会使得同样的方法会有不同的处理结果。再比如参数是实心木块,在这种情况下是没有办法完成“投入”这个动作的,所以“.drop_into”方法不会执行,并且会在报告参数错误的同时终止执行之后的代码。
上文中提到水作为对象的属性,而属性是对象的构成要素之一,因为同一个类对象之间也会存在差异,这些差异需要靠属性来区别。例如对象“water”有温度的区别,那么每一个对象都会有一个@temperature的属性。以下代码定义了温度属性以及温度属性对应的读写方法:
class Water
@temperature=0
def temperature=(x)
@temperature=x
end
def temperature
return @temperature
end
end
water1=Water.new
water2=Water.new
water1.temperature=100
water2.temperature=0
puts "water1's temperature is now #{water1.temperature} centigrade"
puts "water2's temperature is now #{water2.temperature} centigrade"
另外很有意思的是,“类”和“方法”本身也是对象,这就是说任何一个“类”也有所属的类(即类的类::Class),任何方法也有他的方法(即方法的方法::Method.methods)。这似乎很绕,但是请尝试以下代码:
ents = Sketchup.active_model.entites
ents.class
ents.class.class
mtd = ents.method :add_line
mtd.class
mtd.name
mtd.parameters
mtd.methods
以上代码中:第三行返回ents的类型(这里会返回Sketchup::Entities类),第四行返回Sketchup::Entities的类型(::Class类)。第六行的“ents.method”(注意,这里没有字母s)返回ents中的“:add_line”方法。之后可以查看这个方法所属的类型(::Method)、方法名、参数要求等等内容。
与类相似的还有一个模块的概念,像Sketchup、Geom这些就是模块,模块可以理解为不能实例化的类,它在Ruby中可以用作命名空间,一些类会定义在模块中,例如“Sketchup::Entity”(实体类)、“Sketchup::Edge”(边线类)和“UI::Toolbar”(工具栏类)。值得注意的是上述三个例子中Entity类和Edge类之间存在继承关系,或者更准确地说Entity是Edge的父类,因为抽象过程中所有的边线也都属于图元实体。这种继承关系表现为子类(Edge类)的实例可以使用父类(Entity类)的方法,同样的Edge的实例也可以出现在Entity类的容器Sketchup::Entities中。
ents = Sketchup.active_model.entites
ents[0].class
ents[0].instance_of? Sketchup::Edge
ents[0].kind_of? Sketchup::Edge
ents[0].instance_of? Sketchup::Entity
ents[0].kind_of? Sketchup::Entity
以上代码中“.instance_of?”和“.kind_of?”两个方法的作用是返回对象是否与某个类存在关系。当对象是给定类的实例时“.instance_of?”和“.kind_of?”都返回true(真);当对象是给定类的子类的实例时,“.instance_of?”返回false(假),而“.kind_of?”依然返回true(真);除此之外的其他情况两个方法均返回false(假)。
以上是有关类与对象的初步介绍,而核心内容在于理解类与对象的使用方法,知道类的继承关系,但是暂时并不要求去自己定义和构造类或者模块,因为更多时候,使用SU Ruby就像是搭积木,用提供的方法的组合来解决具体的需求。当然随着教程内容的深入,后续在合适的时机这些内容还是会出现的。
(3)关于类与对象的自学
因为Ruby中数据皆可对象,因此标题中的“数据类型”现在已经可以将它理解成是“类与对象”了。这最后一小节简单地介绍一种认识类与对象的方法。这种方法在第一篇中就已经提到过了,到现在依然值得重复介绍。那就是利用几个非常实用的方法来了解一个未知的对象。
以下方法可以确定一个object是哪一个类的实例:
以下方法可以打印出object可以使用的方法:
以下方法可以打印出object中名为method的方法所需要的参数列表:
(object.method :method).parameters
以下方法可以确定object的所属类的父类:
以下方法可以将object的所属类的完整继承关系打印出来:
通过使用上面这些方法,可以很有效地了解一个未知的对象,而在面临问题是,能够找到更多关键词用于检索,以便于快速在网络中找到相关的实现案例。
最后还是按照惯例放一些相关的代码。
arr=["123","146","223e-1","642e2","866","345e3","523","234e2"]
arr.sort
arr.sort{|a,b|a.to_f<=>b.to_f}
sel=Sketchup.active_model.selection.to_a
sel.sort!{|a,b|a.bounds.min.z<=>b.bounds.min.z}
这是Apiglio SU Ruby教程的第三篇,编号为“SU-R03”,本系列的其他文章可以在公众号后台输入“SU-R”+两位数编号查看,或者直接点击菜单中的“SU Ruby”选项获得全部有关SketchUp文章的目录。