星期三, 8月 31, 2011

批次輸出 cache

(
fn outputCache obj folderPath recordStart recordEnd sampleRate mode:1 =
(
try( deletemodifier obj obj.modifiers[#Point_Cache_Binding] ) catch()
try( deletemodifier obj obj.modifiers[#Point_Cache] ) catch()
local pcMod = undefined
if mode == 1 then pcMod = copy(pointCache()) else pcMod = copy(PointCacheWSM())
if pcMod == undefined do ( print "unknow mode" ; return false )
addmodifier obj pcMod
pcMod.fileCount = 0
pcMod.recordStart = recordStart
pcMod.recordEnd = recordEnd
pcMod.sampleRate = sampleRate
pcMod.filename = (folderPath +"\\"+obj.name+".xml")
if getCommandPanelTaskMode() != #modify do setCommandPanelTaskMode #modify
modpanel.setCurrentObject obj.modifiers[1]
classof obj
cacheOps.RecordCache obj.modifiers[1]
true
)
-- 用法 --
selObjs = selection as array
for obj in selObjs do
(
outputCache obj "c:\\Temp\\Out" 0.0 161.0 1 mode:2
)
)
最近寫的小型工具,有須要請自己取用。
又很久沒寫blog了,都在玩ssf4啊…

星期二, 7月 12, 2011

Classof 原來可以這樣用…

今天看到Bobo講了才知道,原來 classof 還有這等妙用。

我之前寫某個工具時剛好也為了 modifier 的狀態不更新而煩腦,當時是用其他的方法解決了,等等再拿這個方式去試試看可以不可以用這個方式解。

 

This is because when you put the code in a function, the code becomes the body and is run as a multiline expression within a single undo record. During this, scene updates, viewport redraws etc. are suspended until the whole body has finished executing. As result, the modifier stack is not updated until the function has finished executing, and the modifier is in the wrong state.

In contrast, when you run the code within the global scope, each line is executed individually and causes the modifier stack to be updated too.

You can force a modifier stack update by adding the line 'classof theShape' right after the addModifier() call. When you ask for the class of an object, MAXScript checks to see if the stack needs updating because it needs to figure out what the class is ON TOP of the stack. (You could have a shape turned into a mesh turned into EPoly turned into EPatch and back to EMesh within the stack, so without updating the whole stack Max cannot know what the result would be). So classof() is a nice workaround in almost all cases where a modifier was added but has not been updated yet.

You could also call 'select theShape', but that would force a scene update, too, which can be slower than just updating the stack.

 

->出處<-


大意是,把code包起來之後,code 就會以一個 undo 的單位下去執行,沒包之前則是一行就會可以做一次undo。
而在一個undo單位下執行中,場景更新,視窗重繪等等的會全部暫停更新。而 modifier stack也是等到執行完再一次更新 ,
這時要是去找modifier stack拿資訊就會拿到未更新狀態的資訊。

而 classof 這個函式會強迫 modifier stack 更新狀態,因為有可以該物體是mesh後轉成editpoy後又轉成 edit patch最後再轉回mesh,為了查得該個物體的 class,所以一定要先更 modifier stack,不更新就查不到。 

還有另一個選擇是用 select ,不過 select 會有場景更新,相對而言的就比較慢。

 

星期六, 5月 14, 2011

i9000 再次刷rom

唉,好死不死就手賤去摔到手機。
平常也摔了不少次,連我兒都幫我摔過,沒想到就這一次摔出了毛病…

送修原廠就代表重刷rom…
就又代表著要備份…重灌軟體。

不過最煩的還是重刷rom。只好再爬一下文,找回怎麼解鎖三鍵,怎麼用odin,刷哪個rom好…,不小心就又看到提升 gps 準度的好物

弄了半天,真的難免會覺得…
到底是我在用智慧性手機還是它在用我…?

星期三, 4月 27, 2011

struct, struct, rollot

最近試著 mxs 中 的 strcut 裡再包一個 struct。
想要達成像是…

frontWheel = car.wheel()

這樣子虛華的語法…

當然,很快的就找到方法了。不過雖然在直譯時會過,但是struct裡的變數卻怎麼也吃不到,失敗…

再接再厲的…
我又想把 rollout也包在 struct 裡,當然,之是很快的就發現這樣做是沒問題了,而且可以用

car.showControlWindow()

這樣虛華的語法來建立視窗。
只是再一次的…在某些部份會掛掉,在特地的某些點裡會指名要求明確的指出要用的是那個struct裡的那個變數…

好像又白走了一些路…(嘆)

===================================================================

結果後來發現其實有人早就這樣子寫了,這個寫法有個好處就是可以把程式裡的所要用到的東西都共享,然後只有用到一個全區變數。還蠻好用的喔~

星期四, 3月 17, 2011

filters struct

寫script時常常會須要用到判斷這東西是不是一個某某物件或是某某類別
通常就是自己用 classof或是iskindof
不過其實max有寫了一個 filters 的結構體,寫的完整許多,可以直接拿來用。

用法大致上是這樣

b1 = box()
select b1
filters.Is_EditMesh()

就可以了
要判斷的物體是要選取的而不是用傳參數的,這點跟我自己的用法有點不同。不過這種用法執行速度好像比較快…

其他的還有像

filters.Is_EditSpline()
filters.Is_EditPatch()
filters.Is_EditPoly()
filters.is_MeshSelect()
filters.is_SplineSelect()
filters.is_PatchSelect()
filters.is_PolySelect()

等等的,有用到的人可以去
C:\Program Files\Autodesk\3ds Max 2011\stdplugs\stdscripts\filterfunctions.ms
查看原始碼~

星期三, 3月 16, 2011

FBX 在script中可以用的指令,備忘一下

help裡給的指令實在太舊了點。
查了一下資料。怕也後忘記,在這裡備忘一下

可以用
FBXImporterGetParam “FBXProperties”
或是
FBXExporterGetParam “FBXProperties”
得到全部的 fbx 指令

一列出來…有這麼多呢…
PATH: Import|PlugInGrp|PlugInUIWidth    ( TYPE: Integer ) ( VALUE: 600 )
PATH: Import|PlugInGrp|PlugInUIHeight    ( TYPE: Integer ) ( VALUE: 600 )
PATH: Import|PlugInGrp|PlugInUIXpos    ( TYPE: Integer ) ( VALUE: 660 )
PATH: Import|PlugInGrp|PlugInUIYpos    ( TYPE: Integer ) ( VALUE: 285 )
PATH: Import|PlugInGrp|UILIndex    ( TYPE: Enum )  ( VALUE: ENU  )  (POSSIBLE VALUES: < ENU > < DEU > < FRA > < JPN > < KOR > < CHS >  )
PATH: Import|IncludeGrp|MergeMode    ( TYPE: Enum )  ( VALUE: Add to scene  )  (POSSIBLE VALUES: < Add and Update scene elements > < Update scene elements > < Add to scene >  )
PATH: Import|IncludeGrp|Geometry|SmoothingGroups    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|ExtraGrp|Take    ( TYPE: Enum )  ( VALUE: IN_cut07_TDMS_001  )  (POSSIBLE VALUES: < No animation > < IN_cut07_TDMS_001 >  )
PATH: Import|IncludeGrp|Animation|ExtraGrp|TimeLine    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation|ExtraGrp|BakeAnimationLayers    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|ExtraGrp|Markers    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation|ExtraGrp|PointCache    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|Deformation    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|Deformation|Skins    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|Deformation|Shape    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|CurveFilter    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed|CurveFilterCstKeyRedTPrec    ( TYPE: Number ) ( VALUE: 0.000090 )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed|CurveFilterCstKeyRedRPrec    ( TYPE: Number ) ( VALUE: 0.009000 )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed|CurveFilterCstKeyRedSPrec    ( TYPE: Number ) ( VALUE: 0.004000 )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed|CurveFilterCstKeyRedOPrec    ( TYPE: Number ) ( VALUE: 0.009000 )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyCstKeyRed|AutoTangentsOnly    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyKeyReduce    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyKeyReduce|CurveFilterKeyReducePrec    ( TYPE: Number ) ( VALUE: 1.000000 )
PATH: Import|IncludeGrp|Animation|CurveFilter|CurveFilterApplyKeyReduce|CurveFilterApplyKeySync    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|IncludeGrp|Animation|SamplingPanel|SamplingRateSelector    ( TYPE: Enum )  ( VALUE: Scene  )  (POSSIBLE VALUES: < Scene > < File > < Custom >  )
PATH: Import|IncludeGrp|Animation|SamplingPanel|CurveFilterSamplingRate    ( TYPE: Number ) ( VALUE: 30.000000 )
PATH: Import|IncludeGrp|Animation|Bone|BoneWidthHeightLock    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|Animation|Bone|BoneAsDummy    ( TYPE: Enum )  ( VALUE: Leave as bones  )  (POSSIBLE VALUES: < Leave as bones > < Convert as dummy >  )
PATH: Import|IncludeGrp|Animation|Bone|Max4BoneWidth    ( TYPE: Number ) ( VALUE: 1.000000 )
PATH: Import|IncludeGrp|Animation|Bone|Max4BoneHeight    ( TYPE: Number ) ( VALUE: 1.000000 )
PATH: Import|IncludeGrp|Animation|Bone|Max4BoneTaper    ( TYPE: Number ) ( VALUE: 0.900000 )
PATH: Import|IncludeGrp|CameraGrp|Camera    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|LightGrp|Light    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|IncludeGrp|LightGrp|Environment    ( TYPE: Bool ) ( VALUE: false )
PATH: Import|AdvOptGrp|UnitsGrp|DynamicScaleConversion    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|UnitsGrp|UnitsSelector    ( TYPE: Enum )  ( VALUE: Centimeters  )  (POSSIBLE VALUES: < Millimeters > < Centimeters > < Decimeters > < Meters > < Kilometers > < Inches > < Feet > < Yards > < Miles >  )
PATH: Import|AdvOptGrp|AxisConvGrp|AxisConversion    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|AxisConvGrp|ZUProtation_max    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|AxisConvGrp|UpAxisMax    ( TYPE: Number ) ( VALUE: 1.000000 )
PATH: Import|AdvOptGrp|UI|ShowWarningsManager    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|UI|GenerateLogData    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Obj|ReferenceNode    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|ReferenceNode    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Texture    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Material    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Animation    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Mesh    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Light    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Camera    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|AmbientLight    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Rescaling    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Filter    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|FileFormat|Max_3ds|Smoothgroup    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|Dxf|WeldVertices    ( TYPE: Bool ) ( VALUE: true )
PATH: Import|AdvOptGrp|Dxf|ObjectDerivation    ( TYPE: Enum )  ( VALUE: By layer  )  (POSSIBLE VALUES: < By layer > < By entity > < By block >  )
PATH: Import|AdvOptGrp|Dxf|ReferenceNode    ( TYPE: Bool ) ( VALUE: true

指定值時要給fullpath,像是
FBXImporterSetParam “Import|IncludeGrp|Animation|Markers” true





星期五, 2月 25, 2011

黑客與畫家 - 好文共享

嗯…我想我沒辦法講的像 Paul Graham 這麼精闢,不過我有感受程式實作中對於"美"的要求絕不亞於畫家對對於畫面上的經營,不然我不會這麼喜歡程式這東西。

我…也要再去讀計算機科學嗎…(還真想…)

以下為譯文,出自 好好学习,天天向上
直接用google翻繁中,請見諒。

=======================================================
本文是Paul Graham 寫的一篇關於黑客與畫家共同之處的文章,深入探討了黑客工作的藝術性與創造性。雖然大部分的程序員都覺得藝術是一件很遙遠的事情,但對於那些願意仔細打磨代碼追求精益求精的優秀黑客來說,在創造的過程中總是能感受到藝術的真實存在(儘管可能只是 隱約感受到,而且羞於把自己和藝術聯繫起來)。藝術之所以會讓人覺得高高在上遠離生活,是因為大部分人都是在衣著光鮮地談論著藝術,而不知道什麼是創造。要成為一個創造者,你所要做的不是誇誇其談,而是投入全部熱情去不斷實踐。

原文 | 譯文地址

黑客與畫家

我讀完計算機本科以後,去藝術學校學習繪畫。許多人感到奇怪,喜歡計算機的人也會喜歡美術嗎?他們大概認為編程序和畫畫是兩種完全不同的工作,編程需要冷靜,精密,和正確的方法,而畫畫是表達某種狂熱的情感。

這種印像是不對的,編程和畫畫有很多共同之處,實際上,在我認識的不同類型的人中間,畫家和黑客是最相似的。

畫家和黑客的相似之處在於:他們都是創造者,就好像作曲家,建築師,以及作家一樣。黑客和畫家類似,他們的目的是創造某種美好的事物。儘管在創造的過程中,也許會發現新技術,但他們的根本目的並不是研究技術。

我從來都不喜歡”計算機科學”這個詞,因為這種東西根本就不存在。
這門學科的內容,不過是由於歷史原因偶然湊合到一起的大雜燴,就好像南斯拉夫國的形成一樣。
一頭是數學家們,他們擺弄計算機是為了得到國防部的資金贊助,中間部分,一夥人在研究彷彿是計算機自然史之類的東西--比如網絡上數據流算法的行為特徵等等。在另一個極端上,是黑客們,他們編寫有趣的軟件。對他們來說,計算機是表達的工具,如同水泥之於建築師,顏料之於畫家。這三種人湊在一塊的群體,就好像是數學家,物理學家和建築師被分到一個專業裡。

有時候黑客們幹的事被稱為”軟件工程”,這個詞也是一種誤會。比起建築師來,軟件設計師離工程師的距離更遠。建築師和工程師的分界並不十分精確,但卻是實實在在存在的。其分界在於做什麼和如何做:建築師決定做什麼,工程師考慮如何做出來。

這兩件事情也不能分得太開,如果你不懂得如何做,那麼你設計的時候就會陷入難局。 但是編程當然不是僅僅決定如何實現某種特性那麼簡單,在最好的情況下,編程實際上就是設計軟件的特性--往往最好的設計方式就是實現它。

說不定哪一天,”計算機科學”會分裂成幾個專業,就好像南斯拉夫最終分裂成幾個國家那樣。 這也許是件好事。 尤其是這意味著我所擅長的編程,會變成獨立的專業。

這些不同類型的工作綁到一個專業裡,當然有利於行政管理,但是卻會引起智力上的困惑。 這也是我不喜歡這個名詞的另一個原因。 處於中間部分的那伙人所干的,和經驗科學差不多,但是另外兩頭的人,數學家和黑客,可不太像是在幹真正的科學。

數學家好像並不為這個問題發愁,他們就像數學系的同行一樣,很高興地做著理論研究,不久就忘了辦公大樓的牌子寫的是”計算機科學系”。 但是對黑客們來說,這個牌子就很成問題。 既然他們幹的事被稱作科學,他們就會感到好歹要像那麼回事,於是大學和研究所的黑客們覺得應該寫論文,而不是寫優美的程序。 但是不幸得很, 後者才是他們真正應該干的。

論文充其量不過是一個手續。 黑客寫出很棒的程序,然後再做一篇論文,論文表示軟件上的成績。 但是兩者之間的不協調引起了問題:好的軟件比起糟糕的軟件來,更加不適合做論文的題材。

好的軟件不適合作論文的題材。 首先,論文要有獨創性的,寫過博士論文的都知道,要想保證你開墾的那片地是處女地,就等於說是你劃出一片別人都不想要的地來。 第二,論文必須言之有物。 糟糕的軟件使論文材料充足,你有很多事實可以描述你是如何克服那些困難的。 糟糕的假設總是會產生大量問題。 大部分AI 研究就是好例子。 比如,你假定,以抽象概念為參量的邏輯表達式列表可以用來表示知識,那你要論證的內容可就多了。 就像Ricky Ricardo說的,Lucy,這下可夠你解釋了。

創造美好事物的過程,常常是對已有事物的細微調整,或者是把已有概念用新方式組合起來。 這種事情,恐怕不太好做研究論文吧。

那麼為什麼大學和研究所還要用論文來衡量黑客呢? 同樣的, 為什麼要用標準化考試來衡量學術才能呢? 為什麼要用代碼行數來衡量程序員的工作量呢? 這些考試的好處是容易實施,而且有一點效果, 因此才會引誘我們繼續採用這些措施。

真正的黑客能夠寫出優雅的代碼, 但是識別這種黑客的方法,真的很不容易找到。 要有好的嗅覺才可能識別出真正優秀的設計。 是否真的有這種嗅覺,和是否自信有這種嗅覺,這兩者之間沒什麼關聯,即使有,也是負面的。

真正的考驗是時間。 經過時間的考驗,好的東西會發展壯大,壞的東西會丟棄。 不幸的是,需要的時間往往太長, 以至超過人的壽命。 Samuel Johnson說,需要一百年的時間,才能形成一個作家的真正聲譽。 你得等到這個作家有影響的朋友都死了,他的追隨者也都死了才行。

我想黑客不得不接受名聲上的不確定性,這一點上, 他們和其他創造者沒什麼不同。 實際上比較起來還要幸運一些。 在編程領域,一時的流行風氣雖然也有影響,但沒有繪畫領域那麼大。

還有比別人的誤解更糟的事情。 更糟的危險是你可能自己誤解自己。 你通常在相關領域尋找靈感。 如果你在計算機系,很自然地會以為,編程的本質就是實現計算機理論。 我讀本科的時候有一種令我很不舒服的感覺,我覺得自己應當多學一點計算機理論,可是期末考試完了不到三個禮拜,我就把那些東西全忘光了。 這讓我覺得自己不夠盡責。

現在我認識到我那時的想法都是錯誤的。 黑客對計算機理論的了解程度,只要達到畫家對顏料化學所了解的程度就夠了。 你應當知道怎樣計算時間和空間複雜度,知道圖靈機模型。 也許應當知道狀態機,至少知道這個概念,如果要寫語法解析或者正則表達式庫的時候會用得到。 畫家對顏料的學問上,要記的東西比這還要多一些呢。

對我來說,靈感的源泉不是來自於那些掛著計算機招牌的地方,而是那些聚集著創造者的地方。 我從繪畫方面得到的靈感比我從計算機理論上得到的,要多得多。

打個比方。 我上學的時候,學生在上機之前,要把整個程序先用紙筆寫出來。 可是我覺得這不是我寫程序的方式。 我喜歡坐在計算機前面寫程序,根本不用紙筆。 我並不先在紙上寫出程序並檢驗其正確性,我喜歡先敲一段代碼,當然好多毛病,然後慢慢敲打成型。 我受到的教育告訴我,調試應當是檢查輸入錯誤的最後一關,而按照我的方式,程序基本上就是調試出來的。

好長一段時間我都感到很沮喪,念小學的時候,我捉鉛筆的方式和老師教的不一樣,那時我也感到同此刻一樣的沮喪。 如果我那會知道別的創造者-比如畫家和建築師-的做法的話,我就早該知道這種方法的名字,那就是:打草稿。 我可以告訴你,他們在大學時教我的方法是錯的。 你應當是一邊寫程序一邊來確定程序的走向, 這和畫家, 作家以及建築師的做法完全一樣。

這裡蘊涵著軟件設計的真義, 認識到這一點, 就意味著程序語言應當首先要具有延展性。 語言要有助於在編程中思考, 而不是僅僅表達思考的結果。 它應該象鉛筆, 而不是像鋼筆。 如果程序員真的像大學裡教的那樣寫程序, 那麼靜態類型語言就是不錯的選擇。 但是我所知道的黑客都不是那樣子編程序的。 我們需要這樣一種語言, 我們用它來隨意塗抹。 而使用靜態類型語言編程序的感覺, 就好像手放在膝蓋上, 小心翼翼握著茶杯, 正襟危坐著和一個嚴肅的老太太談話。

談論靜態類型, 以及創造者這種話題, 我們除去了另外一個困擾的科學的問題: 數學嫉妒。 科學界的每個人暗地裡都認為數學家比自己聰明。 我想數學家們自己大概也這麼認為。 反正科學家們總是把自己的作品弄得像數學論文一樣。 這對物理學倒還沒什麼大害, 但是你要是在自然科學上走得越遠, 就越發現這個問題的嚴重性。

印上一整頁的公式, 看上去很讓人敬畏的樣子, 用上希臘字母就更加不得了。 這種傾向可能誘惑你去研究那些可以公式化的問題, 結果是忽略了真正重要的東西。

如果黑客認同創作者的身份, 像是畫家和作家一樣, 他們就不會受此誘惑。 作家和畫家才不理會數學呢, 根本就是不相干的事情。 我認為, 黑客也應當這樣看。

如果大學和研究所不讓黑客做自己想做的事情, 他們還可以去公司, 可惜, 公司和大學的做法是一丘之貉。 大學和研究所要求黑客當科學家, 而公司要求黑客當工程師。

我也是最近才發現這問題的。 Yahoo買了Viaweb之後, 他們問我的意向, 我一向就不喜歡商業公司, 我就說我還是想編程序。 進了Yahoo以後, 我發現在他們那裡, 編程序的意思就是代碼實現, 和設計沒關係。 程序員就是代碼工人, 他們把產品經理的願望, 以代碼形式記錄下來。

看起來這是大公司的一貫的做法。 這樣做的目的是減低工作的偏差。 只有少數程序員真正懂得設計軟件, 而且這些有才能的人很不容易一下子識別出來。 所以與其把軟件的未來寄託在少數聰明人身上, 不如把軟件設計讓一個委員會來作, 程序員只管編碼實現。

如果你想賺錢, 那麼記住我的話, 因為我講的, 正是小公司取勝的機會。 大公司採取保險的做法, 意圖規避風險。 但是試圖限制這種工作效果上的震蕩的時候, 固然避免了最壞的可能,但也失去了最好的。 這對大公司當然不是問題, 大公司取勝的原因不是因為發明了偉大的產品, 而是因為犯的錯誤比其他大公司少而已。

如果你有辦法和一個大公司競爭某種產品, 這個公司的產品是產品經理們設計的, 那麼, 他們永遠趕不上你。 不過這樣的機會很不容易找到。 你很難和大公司捲入軟件競爭, 就好比你很難和對手在城堡裡徒手搏鬥一樣。 寫一個比微軟的word還要好的字處理器是可能的, 但是在操作系統這個微軟獨占的堡壘裡, 他們對你根本就不屑一顧。

軟件競爭只能在全新的市場中展開, 因為在那裡還沒有誰建立起防禦工事。 你有可能採取大膽的策略, 集合那些既做設計又做編碼的人, 來贏得競爭。 微軟最初就是這樣做的, 蘋果,HP也莫不如此。 我想任何成功的創業公司都是走的這條路。

所以, 創造偉大軟件的一個辦法就是創業開公司。 不過這裡面還有兩個問題。 第一, 開公司以後, 除了編程序, 你需要做好多其他事情。 在Viaweb的時候, 我真的希望自己能擠出四分之一的時間編程就好了。 實際上我四分之三的時間都是在做很討厭甚至很麻煩的事情。 對此我深有體會, 有一次當我開完董事會去補牙, 坐在診所的椅子上, 我覺得簡直抵得上度假了。

還有另一個問題。 寫有趣的軟件, 和寫賺錢的軟件, 經常是沒多少共同之處。 設計語言是很有趣的工作, 微軟的第一個產品就是。 但是現在沒人會花錢買語言。 要想賺錢就得寫那種很麻煩的, 沒人會免費幹的軟件。

所有的創造者都會面臨這個問題。 價格是供求關係決定的, 對有趣軟件的需求總是比較少,而解決一般用戶的平凡問題的需求, 總是多一些。 在高速公路邊上演出, 觀眾一定少, 在廟會搭個台子演出, 觀眾一定多。 寫長篇小說的收入, 比不上寫廣告詞的收入, 雖然那些廣告最後的歸宿是垃圾箱。 設計一種語言的回報一定不多, 而搞定某些公司的老掉牙的數據庫和web server的連接問題, 回報會豐厚得多。

我認為這個難題的答案, 是創造者們應當找一個養家糊口的”日常工作”。 這個名詞最初是慣於晚上演出的音樂家們使用的。 它的意思是: 你做一個工作是為了賺錢, 另一個工作是因為你喜歡。

幾乎所有的創造者在他們職業生涯的早期, 都有日常工作。 其中最為人所知的就是畫家和作家。 如果能賺錢的日常工作剛好是你所喜愛的工作, 那你就太幸運了。 音樂家就經常在唱片店工作。 正在用某種語言或者操作系統的黑客, 也應當找個相近的系統管理或維護的工作。 [1]

黑客應當找個日常工作糊口, 業餘時間做自己喜愛的程序。 我的這個說法並不是獨出心裁。 所有的開源社區的黑客都是這樣做的。 我要說的是, 開源社區的模型也許是正確的模型, 因為這種模型被其他創造者分別獨立地驗證過。

一般的雇主都不太願意僱員參與開源項目, 這讓我有一點驚奇。 在Viaweb則相反, 我們不願意僱傭沒有做過開源項目的人。 面試程序員的時候, 我們考慮的一個首要問題就是, 他們業餘時間寫什麼軟件。 你要不是真的熱愛這個工作, 就不可能幹的出色。 如果你熱愛編程, 就必然會有自己熱愛的業餘項目。 [2]

黑客是創造者, 不太像是科學家。 黑客尋找靈感的地方, 不應當是科學領域, 而是其他創造者工作的領域。 那麼, 我們從繪畫上, 能夠得到什麼啟示呢?

第一件可以從繪畫領域學習的, 或者說可以驗證的, 就是怎樣學習編程。 繪畫都是在實踐中學會的, 編程亦然。 大部分黑客都不是因為念大學計算機課才走上編程之路的。 他們13歲年紀就開始學著寫程序。 即使是上了大學計算機課, 你真正學會編程, 大多也是通過自己實際寫程序。 [3]

畫家通常會留下一系列作品, 你可以從中觀察到他們在實踐中學習的過程。 如果你按年代順序觀察一個畫家的作品, 你會發現後一個作品在前一個作品基礎上的提高。 如果一幅畫中的某樣東西特別出色, 你多半會在更早的作品中發現其發展成熟的軌跡。

我認為大多數創造者都是這樣工作的。 作家和建築設計師也不例外。 對於黑客而言, 我覺得這樣的做法大概比較好: 從一個大概的草稿開始起步, 不斷嘗試採納新的想法, 做修訂版,而不是連續幾年埋頭做一個題目。

這種工作模式是區別黑客和科學家的另一個顯著標誌。 科學家並不通過乾活來學習科學, 他們通過做實驗和解題來學習科學。 科學家總是從完美的東西開始, 也就是說他們重複前人已經做過的工作, 最後達到某種高度, 才開始做自己創造性的工作。 而黑客呢, 一開始就是做創造性的工作–當然這時候作品還不成樣子。 黑客從創造開始, 最終達到完美。 而科學家從完美開始, 最終達到創造。

創造者學習的另一種方法是觀摩傑作。 對畫家來說, 美術館是技巧的寶庫。 幾百年來, 美術館都是畫家學習和借鑒大師作品的地方, 它成為傳統教育方式的一個部分。 觀摩傑作強迫畫家仔細觀察那幅畫是如何畫成的。

作家也是如此。 本傑明-富蘭克林曾經總結Addison和Steel的散文的特點, 並加以模仿。 Raymond Chandler也是這樣學寫偵探小說的。

同樣, 黑客也是通過看優秀的程序來學習編程–不僅看它的外在表現, 而且要看源碼。 開源軟件有一個少人提及的優點就是: 你很容易從中學習編程。 我學編程的時候, 不得不依賴書裡的例子。 其中有一大堆代碼是屬於Unix的, 但Unix也不開源。 大部分人是讀John Lions的書裡的源代碼, 而這些內容是不合法的。 這本寫於1977年的書, 直到1996年都還被禁止出版。

繪畫的過程就是不斷改進的過程, 這是值得我們學習的另一個地方。 繪畫通常從草圖開始,逐漸地添上細節, 但又不僅僅是添上細節那麼簡單。 有時候會發現最初的想法是錯的。 無數的人像作品, 在x光照射之下, 會發現面部輪廓修改過, 嘴的位置也移動過, 諸如此類。

這就是我們應當學習的榜樣, 編程也應當遵循同樣的做法。 想要假設軟件的規格設計完美無缺, 這顯然是不切實際的。 預先接受這種現實對你有好處, 寫程序的時候就會有所準備,隨時應對可能發生的設計規格上的改變。

(大公司很難做到這一點, 這又是一個小公司可以發揮優勢的地方。)

現在差不多每個人都知道過早優化的危險。 我認為我們也同樣應當顧慮另外一個問題, 就是過遲確定軟件的設計規格。

好的工具可以幫助我們避免這個危險。 好的語言也可以幫助你較容易地改變主意。 動態類型語言就有這個優點, 你用不著預先就指定數據的表現形式。 不過, 我認為彈性的關鍵之處在於, 它使語言具有較高的抽象度, 如果一個程序比較短, 那它就比較容易修改。

這似乎聽起來讓人迷惑。 但是偉大的作品總是精益求精。 例如, 達芬奇在國家美術館畫Genevra de Benci像的時候, 頭像後面是檜柏樹叢, 他仔細地描繪每一片葉子。 許多畫家也許認為,這些東西是襯托頭像的, 沒人會仔細看。

達芬奇並不這樣認為。 他繪畫的認真程度, 並不取決於看畫的人的認真程度如何。 達芬奇和米開朗琪羅一樣, 都是一絲不苟。 從總體看去, 那些似乎看不見的細節也會變得顯著。 這是一絲不苟的重要之處。 觀眾經過這幅畫的時候, 注意力一下子就被吸引過去, 那些原本不易覺察的細節, 綜合在一起產生了驚人的效果, 就好像一千個細微的聲音唱出的和聲一樣。

偉大的軟件對於美的追求, 也需要超人的投入。 當你仔細查看好軟件的時候, 會發現那些不為人注意的部分同樣優美。 我不是說我自己寫的軟件是偉大的, 但我知道, 寫代碼的時候,要盡量寫得清晰易讀。 有的程序變量名取得醜陋極了, 有的程序行縮進亂七八糟, 讀這樣的代碼真能讓我發瘋。

如果把黑客僅僅當作代碼工人的話, 那他會像工人挖水溝一樣從一頭乾到另一頭。 但是如果把黑客當作創造者的話, 我們就必須考慮靈感的因素。

編程序的過程和繪畫的過程類似, 也會有起有落。 上新項目的時候, 一天干16個小時不知道累, 也有時候, 無論如何都提不起興致。

這種狀況也必須考慮在內, 你應對的方法不同, 效果也會不一樣。 當你開著手動檔汽車過山的時候, 有時候為了防止拋錨, 不得不鬆開離合器。 鬆開離合器可以防止拋錨。 在繪畫和編程之中, 有一些是關鍵的東西, 另外一些是常規的工作, 留下一些容易作的工作, 等你厭倦的時候, 就做這些較輕鬆的工作。

比如說, 在編程時可以故意留一些bug, 我比較喜歡找bug。 這時候, 黑客這個詞的含義可以說恰當極了。 你面臨的問題總體上是有限制的, 你要做的就是解決掉它。 假定你的程序應該做x, 結果卻做了y, 哪裡出了問題? 你可以斷定最終一定是可以解決的。 這個活跟刷牆一樣, 是不錯的調劑。

繪畫不僅可以教我們如何處理自己的工作, 還教我們如何協同工作。 過去很多偉大的作品都是由一群人共同完成的, 儘管在美術館的標籤上可能只寫著一個人的名字。 達芬奇在Verrocchio 的工作室當學徒時, 就參與繪製<<基督受洗>>中的天使。 這樣的事情當時很常見。 當米開朗其羅堅持要自己一人繪製西斯廷教堂屋頂的人像時, 就被認為是很不得體的事情。

就我所知, 畫家們一起作畫時, 他們並不是一起畫一個共同的部分, 而是一個主要畫家畫主題人物, 他的副手畫背景和其他部分, 絕對不會有人摻和別人正在畫的東西 。

我認為這種模式也適用於軟件開發, 不過別走得太遠。 如果一段代碼有三四個程序員分別寫過, 那麼沒人真正對它負責。 結果就會變成公用房間一樣沒人收拾, 又冷清又灰暗。 正確的做法是把程序分成嚴格定義的模塊, 每個模塊有專人負責, 仔細設計模塊之間的接口, 使之盡可能像程序語言本身那樣, 精確地表達出來。

軟件和繪畫一樣, 都是為人而做的。 黑客也應當像畫家一樣, 努力創作出偉大的作品。 你必須為用戶的立場著想。

我小時候, 就听人講要學會從別人的立場來設想。 意思就是做別人想要你做的事情, 而不是做你自己想做的事情。 這當然給” 換位思考”這個詞帶來了壞名聲。 因此我一直不願意這樣做。

可是, 我錯了。 換位思考確實是成功的秘密, 這並不意味著放棄自我。 理解別人的觀點, 並不是說你要按別人的興趣辦事。 在某種情況下剛好相反, 舉個例子, 打仗的時候, 理解敵人觀點, 其目的恰好是要反其道而行之。 [4]

大多數創作是為人的, 你得理解人的需要。 差不多所有偉大的作品主題都是人, 因為人最感興趣的, 就是人類自身。

好程序員和偉大的程序員之間的唯一的差別, 就是體察別人的能力。 有些程序員很聰明, 但論到”換位思考”, 則是完全的自我主義者。 這樣的人不可能設計出偉大的軟件[5], 他們從來不懂得理解別人的觀點。

判斷一個人換位思考的能力如何, 最好的辦法是看他怎樣向那些不懂技術的人講解技術問題。 我們大概都見過那樣一些人, 不管多麼聰明, 這件事情上卻是糟得很。 如果有人問, 什麼是編程語言, 他們會說, 呃, 就是一種高級語言, 能經過編譯器處理產生目標碼。 高級語言?編譯器? 目標碼? 不知道編程語言的人, 難道會知道這些東西?

軟件的目標之一, 就是解釋自己。 你要寫出好程序, 就應當知道用戶對軟件了解甚少。 他們用軟件時, 全無思想準備。 如果軟件的行為剛好合乎他們的設想, 那就最好了。 別指望用戶會去讀操作手冊。 這方面, 我見過的最好系統是早期的蘋果, 那時候還是1985年。 蘋果乾了所有軟件都做不了的事情, 那就是能正常運行。 [6]

源碼同樣也應當解釋自己。 如果讓人回憶關於編程的名言, 經常提到的是結構化和解釋語言初期的一句話:

程序寫出來是給人看的, 碰巧機器也能運行。

你不但要為用戶設身處地地著想, 對讀者也是一樣, 因為讀者可能就是你自己。 好多程序員寫了程序, 過半年再看, 簡直看不懂究竟是怎麼回事。 我就見過有幾個人因為這原因放棄了perl。 [7]

缺乏換位思考的能力彷彿是高智商的特徵, 尤其在某些地方, 這都成了一種風尚。 但我不覺得真的有什麼關聯。 數學和自然科學和人類感情無關, 這些領域的人顯然都很聰明, 於是乎高智商就和”不通世故人情”掛起構來。 事實上好多平常智商的人在這方面也不行。 看看脫口秀節目裡那些站起來發問的人, 那些問題問的, 真叫拐彎抹角, 主持人得重新梳理一遍, 才能搞得清是啥意思。

如果編程和繪畫寫作一樣的話, 它也一樣酷嗎? 畢竟, 人只有一次生命, 最好是做有意義的事情。

這問題真難回答。 在贏得名氣上總是有很大的滯後。 這就好像遙遠的星星發出的亮光, 要經過好多年才能到達我們眼裡。 繪畫行業光芒四射是因為500年前就產生的傑作。 那時候,沒人會像我們現在這樣看重這些作品。 我們現在所知的Urbino 公爵Federico daMontafeltro先生的形象, 是從Piero della Francesca的作品裡的高鼻子男人哪裡得來的。 這在當時的人眼裡看來, 一定是非常奇特的。

所以當我說編程沒有繪畫那麼酷的時候, 我們應當記住繪畫在它古老的光輝年代, 同樣也不見得那麼酷。

我們可以自信地說, 現在正是黑客事業的光輝年代, 在大部分領域, 偉大的作品誕生很早。 1430-1500年代的繪畫現在仍難以超越, 莎士比亞彷彿生來就是戲劇家, 把這門藝術推進到如此之高的程度, 以致於後來的劇作家都生活在他的陰影裡。 Albrecht Durer之於雕刻, 奧斯丁之於小說, 也是如此。

一次又一次, 我們看到同樣的模式。 新的媒體誕生了, 人們熱情高漲, 短短幾代人就把它的能量發揮到極至。 黑客事業似乎也正處於這樣的時期。

達芬奇時代的繪畫行業並不酷, 是他的傑作造就了繪畫行業的酷。 黑客事業之未來, 全依賴我們今日之創造。

作者註:

[1] 照相術的出現, 毀掉了畫家的日常工作。 歷史上很多畫家靠替人畫像維持生計。

[2] 我聽說微軟不鼓勵員工從事開源項目, 業餘搞也不行。 不過現在有那麼多黑客都在做開源項目, 這種政策也許會令他們難以招募到很多一流程序員。

[3] 大學所能學到的編程技術, 其狀況相當於你學到的關於讀書, 打扮或者約會的知識: 你上高中那時候的品味多差啊。

[4] 這裡有一個”換位思考”的例子。 在Viaweb的時候, 如果在兩個選擇之間下不了決心, 我們就會問: 我們的對手最恨什麼? 當一個對手在軟件裡加了個沒用的特性, 這個特性我們沒有, 他們就在媒體 上大作文章。 我們當然也可以解釋說這個特性根本是廢物, 但是我們還是決定也實現它, 因為這樣的話, 對手會更生氣。 於是當天下午我們就加上了這個特性。

[5] 不包括文本編輯器和編譯器。 因為這兩樣東西黑客自己也天天用, 自己就是典型用戶,所以用不著了解別人的觀點。

[6] 差不多如此。 他們在內存使用上弄巧成拙, 產生好多很麻煩的磁盤交換。 幾個月後, 我買了個新驅動器加上, 這問題就解決了。

[7] 給程序加註釋, 並不是增加易讀性的好辦法。 我把Abelson和Sussman的話再發揮一下:程序語言是用來表達算法的, 碰巧也能在機器上運行。 好的編程語言, 表達軟件的能力比英語更好。 只有在代碼含義復雜難解的地方, 才有必要加註釋, 就好像高速公路上急轉彎的地方才會有警告標誌。

感謝Trevor Blackwell, Robert Morris, Dan Giffin, 和Lisa Randall閱讀本文的草稿, 感謝Henry Leitner和Larry Finkelstei邀請我講話。

星期三, 2月 16, 2011

maxscript 裡對物件 transform 的控制

max裡中代表物件在3d坐標中的位置的transform是經由object transform跟物件本身的transform推算出來的。


transform 是一組由四個向量組成的陣列值
在MAX中可用
matrix3 1

的到如下的東西(就是 matrix3 預設值啦)
(matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0])

而transfrom這東西在 get 和 SET 時給你的都是以世界軸為主,
所以在設定時就相當的方便,

就算你要要設定父物件是a物件的b物件的位置,想要他對齊一個c物件,你也只須要寫一行

b.transform = c.transform

就搞定了,而不用去管c物件有沒有父物件。


但如果你遇上了想讓物件跟pivot二個不一樣時,那就還要借助上object offset 這個 transform

在設定 rigging control 時
很常會遇到一個情況就是…例如:
spine的控制器對齊spine骨頭之外,外觀要對齊世界坐標軸向,
我們可以用
spineControl.transform = spineBone.transform
之外再加上
spineControl.objectoffsetrot = (((matrix3 1) * inverse spineControl.transform) as quat)
就可以讓控制器在"外觀上"是和世界標對齊的~


ps: 最後的那個 as quat 可以不用寫,max會自已幫你轉換。