вторник, 18 октября 2016 г.

Расстановка книг.

Возникла задача расставить большое количество книг в книжном магазине. Как обычно, руками оказалось слишком долго. Разбираться в готовых решениях тоже долго. Быстрее оказалось написать простой скрипт для расстановки по сплайнам, что называется без изысков.

Ниже сам скрипт с небольшим описанием:
Заготовка из книг
Расположение пивота(точки опоры) книги
Сплайны по которым будет расстановка книг
При выделенном сплайне выполняем этот код:
-----------------------------
main_spl = $
arrSplineData = #()
for numS = 1 to (numSplines  main_spl) do
    (     arS = #()
        for k =0.0 to 1.0 by  0.0001 do append arS (pathInterp main_spl numS k)
        append arrSplineData arS
    )
-----------------------------
Происходит считывание позиций со сплайна с шагом 0.0001 от длины элемента сплайна. Запись данных в массив происходит также поэлементно.
После записи arrSplineData = #(#(pos1, pos2, pos3),#(pos1, pos2, pos3),#(pos1, pos2, pos3).....)
Затем выделяем заготовку книг и выполняем запись книг в массив:

--------------------------------
arrBooks = selection as array
--------------------------------
Далее с помощью функции newBook из массива книг arrBooks выбираем произвольную книгу и определяем ее габариты, в моем случае, по оси Y! 
------------------------------
fn newBook arB =  #(theB = arB[random 1 arB.count], theB.max.y - theB.min.y)
NB = newBook arrBooks    
------------------------------

С помощью следующей части кода определяем массив newBooks - куда будут записаны все созданные книги и обращаясь к уже созданному массиву arrSplineData производим расстановку книг:
------------------------------
 newBooks = #()
for k = 1 to arrSplineData.count do(
    s = 0
    thePos = arrSplineData[k][1]
    for i = 2 to arrSplineData[k].count do(
        if s == 0 do thePos = arrSplineData[k][i-1]
        s = s + (distance arrSplineData[k][i-1] arrSplineData[k][i])
        if s >= NB[2] do (
            nbi = instance NB[1]
            nbi.wirecolor = NB[1].wirecolor
            theY = normalize (arrSplineData[k][i-1] - arrSplineData[k][i])
            theZ = [0,0,1]
            theX = cross theY theZ
            nbi.transform = matrix3 theX theY theZ thePos
          
            append newBooks nbi
            NB = newBook arrBooks  
            s = 0
            )
        )
    )
------------------------------

После первой расстановки возможно понадобится сделать реверс некоторым элементам сплайна.

Для этого удалим массив newBooks:
--------------------------------
delete newBooks
--------------------------------

Получается сырой, полуавтоматический способ расстановки для больших библиотек без изысков.
Ниже целиком весь код:
---------------------------------
 main_spl = $
arrSplineData = #()
for numS = 1 to (numSplines  main_spl) do
    (     arS = #()
        for k =0.0 to 1.0 by  0.0001 do append arS (pathInterp main_spl numS k)
        append arrSplineData arS
    ) -- END for numS = 1 to (numSplines  theMainShape) do
      
arrBooks = selection as array

fn newBook arB =  #(theB = arB[random 1 arB.count], theB.max.y - theB.min.y)
NB = newBook arrBooks  

newBooks = #()
for k = 1 to arrSplineData.count do(
    s = 0
    thePos = arrSplineData[k][1]
    for i = 2 to arrSplineData[k].count do(
        if s == 0 do thePos = arrSplineData[k][i-1]
        s = s + (distance arrSplineData[k][i-1] arrSplineData[k][i])
        if s >= NB[2] do (
            nbi = instance NB[1]
            nbi.wirecolor = NB[1].wirecolor
            theY = normalize (arrSplineData[k][i-1] - arrSplineData[k][i])
            theZ = [0,0,1]
            theX = cross theY theZ
            nbi.transform = matrix3 theX theY theZ thePos
          
            append newBooks nbi
            NB = newBook arrBooks  
            s = 0
            )
        )
    )

delete newBooks
---------------------------------

Это финальный результат:

Похожие скрипты:
bookmanager на scriptspot
bookscatter на scriptspot



пятница, 7 октября 2016 г.

Сложное копирование.

--------------------------------------------
arZamenit = selection as array
arNaEto = selection as array

for i in arZamenit do(
    maxops.clonenodes arNaEto[random 1 arNaEto.count] cloneType:#instance expandHierarchy:true newNodes:&nnl
    nnl[1].transform = i.transform
     )
delete arZamenit
--------------------------------------------

Функция  maxops.clonenodes позволяет копировать объекты с прилинкованными объектами.
Например, если надо боксы-заготовки поменять на машины с людьми. При этом машины  в виде группы, то данный способ подойдет как нельзя кстати.

среда, 14 сентября 2016 г.

Выделение инстансных объектов.

 ------------------------------------------------------
arInst = #()
arSel = selection as array
if arSel.count != 0 do(
    for i in arSel do(
        InstanceMgr.GetInstances i &rptInstances
        join arInst rptInstances
        )
    select arInst
    )
 ------------------------------------------------------
Источники:
ersindemir
forums.cgsociety
forums.cgsociety
docs.autodesk.com

Декодирование mse скриптов.

1. Запустить quickbms.exe  и  выбрать Mse_Decrypter.txt

2. Выбрать mse

3. Указать путь для сохранения декодированного скрипта.

Источник:
http://www.scriptsays.com/2016/04/3dmax-mse-script-source-code-decryption-tool/

Правда этот способ подходит не для всех mse файлов. 
 

вторник, 13 сентября 2016 г.

Работа с именами текстур.

Бывает так, что, к примеру, в папке wood - куча текстур с непонятными названиями, вроде - 01, asdfasdf, и т.п. .Скрипт ниже переименовывает текстуры по названию папки + произвольное шестизначное число. Сразу обновляет путь битмапы. На примере папки wood текстуры будут иметь следующие имена  - wood_126523 и т.п.
-----------------------------------------------------------
 (
local albm = getclassinstances bitmapTexture, arPath = #()
  
fn randName num = (
local rN = ""
for i = 1 to num do rN+= (random 1 num) as string
rN
)

fn getFileFolderName pf = (
    fsp = filterString pf "\\"
    fsp[fsp.count-1]
    )

for n in albm do
(
    if n.filename != undefined do(
        if doesFileExist n.filename then(
                appendIfUnique arPath n.filename
                nn =(getFilenamePath  n.filename)+(getFileFolderName  n.filename)+"_"+(randName 6)+(getFilenameType n.filename)
                renameFile n.filename nn
                n.filename = nn
                appendIfUnique arPath n.filename
            ) else(
                if (findItem  arPath n.filename) != 0 do n.filename = arPath[(findItem  arPath n.filename)+1]
                )
        )
    )
)   
-----------------------------------------------------------

среда, 7 сентября 2016 г.

Поиск и замена одинаковых текстур с разными именами.

Иногда бывает так, что в сложных сценах могут появится битмапы с одинаковыми текстурами, но с разными путями к ним, соответственно 3ds max может при просчете несколько раз загружать одну и ту же текстуру, в пустую расходуя память. Чтобы этого избежать и был написан следующий скрипт.

  ---------------------------------------------------
(
arBMT = getclassinstances bitmapTexture
   
fn unicImageData w_bmp = (
            arData = #()
            unicAr = #()
            work_bmp = openbitmap w_bmp.filename
            bmp_w = work_bmp.width
            bmp_h = work_bmp.height
            for i = 0.1 to 0.9 by 0.1 do(
                for j = 0.1 to 0.9 by 0.1 do(
                    p = (getpixels work_bmp [(i*bmp_w) as  integer,(j*bmp_h) as  integer] 1)[1]
                    append unicAr p
                    )
                )
            close work_bmp       
            join arData  #(bmp_w, bmp_h, GetFileSize w_bmp.filename, unicAr, getFilenameType w_bmp.filename)
            arData
    )
   
fn CompareArrays a b = if a.count == b.count AND (for i = 1 to a.count where a[i]!=b[i] collect i).count == 0 then return true else return false
   
fn CompareImages bm1 bm2 =(
            local a = unicImageData bm1
            local b = unicImageData bm2
            if a[1] ==b[1] and a[2] == b[2] and a[3] == b[3] and CompareArrays a[4] b[4] == true and a[5] == b[5] then return true else return false
    )

progressstart "Search and replacement..."
num = 1
nc = arBMT.count
    for i in arBMT do(
        progressupdate (num*100.0/nc)
        num+=1
       
        for j in arBMT do(
            if i.filename != j.filename do(
                if i.filename != undefined and j.filename != undefined do(
                    if doesFileExist i.filename and doesFileExist j.filename do(
                        if CompareImages i j do (print (j.filename+"    --->    "+i.filename);j.filename = i.filename;)
                        )
                    )
                )
            ) -- for j in arBMT do
           
        )--for i in arBMT do
progressend ()    

)
---------------------------------------------------

Функция unicImageData получает информацию о текстуре в виде массива #(ширина, высота, размер текстуры, RGB 100 точек с шагом 10% , разрешение текстуры)

Функция CompareArrays просто сравнивает два массива. Источник.

Функция CompareImages сравнивает две битмапы.

Далее, собственно, идет двойной перебор и сравнение битмапов в проекте с выводом результата в листенер.
На последнем проекте было сэкономлено не много, конечно, но около 10 мегабайт.

Скрипт можно усовершенствовать и убрать жесткое сравнение по параметрам картинки, а ввести некую погрешность для поиска одинаковых картинок разного разрешения.

Полуавтоматический способ импорта и подготовки чертежей

Это что-то вроде заготовки для импорта и подготовки чертежей. В теле скрипта необходимо указать путь к dwg файлам,  также можно удалить указанные слои.
Функция ObjLayerName выполняет роль фильтра - не рассматриваются сплайны из указанных в функции слоев.
 --------------------------------------------------------
--mP = "23"

importFile ("path.dwg") #noPrompt

delete (for o in objects where superclassOf o == shape and (o.layer.name == "name layer 01" or o.layer.name == "name layer 02") collect o)

fn ObjLayerName obj = (
    temp = true
    if obj.layer.name == "acad-K2-fas" then temp = false
    if obj.layer.name == "acad-K1-pl" then temp = false
    if obj.layer.name == "acad-K2-pl" then temp = false
    if obj.layer.name == "acad-K3-pl" then temp = false
    if obj.layer.name == "acad-K4-pl" then temp = false
    if obj.layer.name == "acad-K5-pl" then temp = false
    if obj.layer.name == "acad-K6-pl" then temp = false
    temp
    )

arrShape = for o in objects where superclassOf o == shape and ObjLayerName o collect o
arrShapeC = arrShape.count

arByName = #()
for i = 1 to arrShapeC do if findItem arbyName arrShape[i].name ==  0 do append arByName arrShape[i].name
   
arSplineByName = #()
for n in arByName do(
    arNT = #()
    for j in arrShape do if j.name == n do append arNT j
    append arSplineByName arNT
    )
   
for s in arSplineByName do(
        convertToSplineShape s[1]
        for i=2 to s.count do(
            convertToSplineShape s[i]
            addAndWeld s[1] s[i] -1
            )
        updateShape s[1]
    )
   
arrShape = for o in objects where superclassOf o == shape and ObjLayerName o collect o
arrShapeC = arrShape.count
ms = arrShape[1]

convertToSplineShape ms
for i = 2 to arrShapeC do(
        convertToSplineShape arrShape[i]
        addAndWeld ms arrShape[i] -1
            )
updateShape ms

--delete blocks           
delete (for o in objects where superclassOf o != shape collect o)

--delete empty layers
--set 0-layer to be current
(layermanager.getlayer 0).current = true
for i = Layermanager.count-1 to 1 by-1 do(
(layermanager.getLayer i).nodes &theNodes
if thenodes.count== 0 do LayerManager.deleteLayerbyname (layermanager.getLayer i).name
)

select ms
ms.name = "K6_floor_"+mP

------add newObj to layer
theLayer = layermanager.getLayerFromName $K6_floor_01.layer.name
theLayer.addNode ms
 --------------------------------------------------------

Wirecolor by Z


----------------------------------------------------------------
arPos = #()
arObj = selection as array

for i in arObj do append arPos i.pos.z
sort arPos

arData = #()
for i = 1 to arPos.count do (append arData ((arPos[i] - arPos[1])/(arPos[arPos.count] - arPos[1]));  append arData arPos[i])

for i in arObj do (
    fi = findItem arData i.pos.z
    if fi != 0 do i.wirecolor = (color 255 (arData[if fi != 1 then fi-1 else fi]*255) 0)
    )
-----------------------------------------------------------------

Варьируя параметрами "(color 255 (arData[fi-1]*255) 0)"  можно менять расцветку.
Этим параметром можно менять оси "i.pos.z"

среда, 31 августа 2016 г.

Аттачит сплайны по именам.

Если в сцене много сплайнов с одинаковыми именами и появилась необходимость их объединить в один, то этот скрипт как раз для этого:

-------------------------------------------------------
(
arrShape = for o in objects where superclassOf o == shape collect o
arrShapeC = arrShape.count

arByName = #()
for i = 1 to arrShapeC do if findItem arbyName arrShape[i].name ==  0 do append arByName arrShape[i].name
   
arSplineByName = #()
for n in arByName do(
    arNT = #()
    for j in arrShape do if j.name == n do append arNT j
    append arSplineByName arNT
    )
   
for s in arSplineByName do(
        convertToSplineShape s[1]
        for i=2 to s.count do(
            convertToSplineShape s[i]
            addAndWeld s[1] s[i] -1
            )
        updateShape s[1]
    )
)
-------------------------------------------------------

вторник, 30 августа 2016 г.

Один из способов найти неинстансные короновские прокси и сделать их инстансными.


 ----------------------------------------------
arCP = for i in objects where classOf i == CProxy collect i
arCPc = arCP.count
for i = 1 to arCPc do(
    for j = 1 to arCPc do(
        if i != j do (
            n1 = getFilenameFile (arCP[i].filename)
            n2 = getFilenameFile (arCP[j].filename)
            if arCP[i].filename != arCP[j].filename and n1 == n2 do instanceReplace arCP[i] arCP[j]
            )
        )
    )
 ----------------------------------------------

По идее, если  "CProxy " заменить на "VrayProxy", то должно сработать и для vray прокси.

вторник, 16 августа 2016 г.

Если необходимо переименовать сгруппированные объекты по названию группы.

Есть сгруппированные объекты с именем группы "Group_01". Необходимо каждому объекту присвоить следующее имя - "Group_01_######", где ###### - произвольное шестизначное число.
Скрипт работает только с выделенными группами.
------------------------------------------------------------------
 (
fn randName num = (
local arS = #("123456", "123456", "123456"), rN = ""
for i = 1 to num do rN+= arS[r = random 1 arS.count][random 1 arS[r].count]
rN
)
  
for i in selection do(
    if not (isgrouphead i) do(
        if i.parent != undefined do i.name = (i.parent).name + "_" + (randName 6)
        )
    )  
)
------------------------------------------------------------------

пятница, 10 июня 2016 г.

Вращение колес посредством maxscript.

Данный скрипт предназначен для анимации сразу большого массива колес!

Если Вам надо заригить колеса в одном авто, лучше пройти по ссылкам ниже.  Данный случай постараюсь рассмотреть позже.

Для корректной работы колесам необходимо применить ResetXForm.

Также, в зависимости от способа копирования колес (mirror или instance с разворотом), возможно понадобится отдельно настраивать вращение для левых и правых колес (по часовой стрелке или против).

Сразу про недостатки:
- покадровая анимация
- морока с настройкой колес
- наверняка можно оптимизировать, но такая задача не ставилась. Главное, что более менее корректно работал. 

---------------------------------------------------------------
-- tested in 3DMax 2014
try destroyDialog WheelAnimator catch()

global arrFrontWheel, arrBackWheel, arAllData = #()

--function
fn sumArData ar = (
    sum = 0
    for i in ar do  sum+=i
    sum
    )
   
fn wheelRotationFlipZ_180 arW fb = if arW.count != 0 do for i in arW do if fb then rotate i (angleaxis 180 [0,0,1]) else rotate i (angleaxis -180 [0,0,1])

    -- arWheel - array wheels, wheelRadius - value, theAxis - axis rotation(1 - "X" or 2-"Y"), AngFlip - clockwise(counterclockwise) (true or false)   
fn wheelWriteDataAnimation arW wheelRadius theAxis AngFlip FB = (
                if arWeel.count != 0 do(
                    arAllData = #()
                    for w in arW do(
                        case FB of(
                            ("FrontWheel"): (
                                            local arTransform = #(), arAng = #()
                                            --weelRadius
                                            if wheelRadius == 0 do wheelRadius =  (w.max.z - w.min.z)/2.0
                                            --delete old keys
                                            sliderTime =  animationRange.start
                                            deleteKeys w #allKeys
                                            --write data
                                            for t = animationRange.start to animationRange.end do (
                                                    at time t (
                                                            p0 = at time t w.position; p1 = at time (t+1) w.position;
                                                            dif = p1-p0; len = Length(dif); vec = normalize dif; v2 = normalize (cross vec [0,0,1]);
                                                           
                                                            theAng = (if AngFlip then 1 else -1)*(180.0*len)/(wheelRadius*pi) -- AngFlip +1 or -1
                                                            append arAng theAng; ang = sumArData arAng;
                                                           
                                                            cm = if theAxis == 1 then matrix3 v2 vec (cross v2 vec) p0 else matrix3 vec v2 (cross vec v2) p0
                                                            append arTransform ((rotateXmatrix ang)*cm)
                                                        )  -- end at time j
                                                    ) -- end for j = AnimStart to AnimEnd do
                                            append arAllData #(w, arTransform)
                                )
                            ("BackWheel"):(
                                            local arTransform = #(), arAng = #()
                                            --weelRadius
                                            if wheelRadius == 0 do wheelRadius =  (w.max.z - w.min.z)/2.0
                                            --delete old keys
                                            sliderTime =  animationRange.start
                                            deleteKeys w #allKeys
                                            --write data
                                            for t = animationRange.start to animationRange.end do (
                                                    at time t (
                                                            p0 = at time t w.position; p1 = at time (t+1) w.position;
                                                            dif = p1-p0; len = Length(dif); vec = normalize dif; v2 = normalize (cross vec [0,0,1]);
                                                           
                                                            theAng = (if AngFlip then 1 else -1)*(180.0*len)/(wheelRadius*pi) -- AngFlip +1 or -1
                                                            append arAng theAng; ang = sumArData arAng;
                                                           
                                                            cm = w.transform
                                                            append arTransform ((rotateXmatrix ang)*cm)
                                                        )  -- end at time j
                                                    ) -- end for j = AnimStart to AnimEnd do
                                            append arAllData #(w, arTransform)
                                )
                            )
                        )
                    )--end if arWeel.count != 0
    )
   
-- animation wheel by array data
fn wheelAnimation =(
        if arAllData.count != 0 do(
            for w in arAllData do(
                local num = 0
                with animate on(
                    for t = animationRange.start to animationRange.end do at time t w[1].transform = w[2][num+=1]
                    )
            --deleting unnecessary keys       
            selectKeys w[1].pos.controller animationRange.start animationRange.end
            deleteKeys w[1].pos.controller #selection   
            selectKeys w[1].scale.controller animationRange.start animationRange.end
            deleteKeys w[1].scale.controller #selection   

            )--for i in arWeel do
        )--if arWeel.count != 0 do(
    )--fn weelAnimation w arW =(

--Interface
rollout WheelAnimator "WheelAnimator"
(   
group "Selection:"
(
    listbox ListFrontWheel ""  height:5
    button addToListFrontWheel "Add front wheels" width:175
    listbox ListBackWheel ""  height:5
    button addToListBackWheel "Add back wheels" width:175
    )
group "Parameters:"
    (
    spinner radW "Wheel radius:" range:[0,100000, 0] type:#float width:100 offset:[30,0] enabled:false across:2
    checkbox theAuto "Auto" checked:true align:#center offset:[10,0]
    label lbl1 "Orientation: " align:#left across:2
    radiobuttons rb1  "" labels:#("X", "Y") default:1 align:#left  offset:[-15,0] --across:2
    checkbox theFlip "Flip (turn 180 deg around Z-ax)" checked:true align:#left
    checkbox theCW "Clockwise (or not)" checked:true align:#left
        )

button theGo "Animate" width:175 height:30  align:#left  --across:2

on theAuto changed theState do(
    radW.enabled = not theAuto.state
    if radW.enabled == false do radW.value = 0
    ) 
   
on addToListFrontWheel pressed do(
    local SelFW = selection as array, ListName = #()
    arrFrontWheel = #()
    if SelFW.count != 0 then(
            for i in SelFW do (append ListName i.name; append arrFrontWheel i;)
            ListFrontWheel.items = ListName
            ) else ListFrontWheel.items = #()
     )
   
 on addToListBackWheel pressed do(
    local SelFW = selection as array, ListName = #()
    arrBackWheel = #()
    if SelFW.count != 0 then(
            for i in SelFW do(append ListName i.name; append arrBackWheel i;)
            ListBackWheel.items = ListName
            ) else ListBackWheel.items = #()
     )

on theFlip changed theState do (wheelRotationFlipZ_180 arrFrontWheel theFlip.state; wheelRotationFlipZ_180 arrBackWheel theFlip.state;)   
   
on rb1 changed state do (
    wheelWriteDataAnimation arrFrontWheel radW.value rb1.state theCW.state "FrontWheel"
    wheelAnimation()
    wheelWriteDataAnimation arrBackWheel radW.value rb1.state theCW.state "BackWheel"
    wheelAnimation()
    )
on theGo pressed do (
    wheelWriteDataAnimation arrFrontWheel radW.value rb1.state theCW.state "FrontWheel"
    wheelAnimation()
    wheelWriteDataAnimation arrBackWheel radW.value rb1.state theCW.state "BackWheel"
    wheelAnimation()
    )
on theCW changed theState do    (
    wheelWriteDataAnimation arrFrontWheel radW.value rb1.state theCW.state "FrontWheel"
    wheelAnimation()
    wheelWriteDataAnimation arrBackWheel radW.value rb1.state theCW.state "BackWheel"
    wheelAnimation()
    )
) --END rollout
createDialog WheelAnimator width:200
---------------------------------------------------------------
 

Ссылки по теме:
MAXScript Tutorials: Quaternions 1/2
Rolling Ball
111_carWheel_v0.3
3DS MAX - CG Academy - The Matrix Continued
CGTalk - Wheel rig script!
Rotating the Wheels


Если необходимо просто провращать колеса без подруливания, то это можно сделать этим скриптом:

------------------------------------------------------------
 -- tested in 3DMax 2014
try destroyDialog SimpleWheelAnimator catch()
--fn
fn swa arW wr st cw rb  = ( -- arW: array wheels [#()], wr: wheel radius[float], st: step [integer], cw: Clockwise[false-true], rb: radiobuttons [integer]
    if arW.count != 0 do(
        for w in arW do(
            deleteKeys w #allKeys
                for t = animationRange.start to animationRange.end by st do(
                    p0 = at time (t-st) w.position; p1 = at time t w.position; d = length (p1-p0); u += (if cw then 1 else -1)*180.0*d/(wr*pi);
                    case rb of(
                        (1):(addnewkey w.rotation.controller.X_Rotation.controller t).value = u
                        (2):(addnewkey w.rotation.controller.Y_Rotation.controller t).value = u
                        (3):(addnewkey w.rotation.controller.Z_Rotation.controller t).value = u
                        )
                    )
            )--for w in ws do(
        )--if ws.count != 0 do(
    )-- end fn
--Interface
rollout SimpleWheelAnimator "SimpleWheelAnimator"(  
group "Parameters:"(
    label lbl1 "Rotation axis   - " align:#left across:2; radiobuttons rb1  "" labels:#("X", "Y", "Z") default:1 align:#left    columns:3;
    checkbox theCW "Clockwise" checked:true align:#left across:2; spinner sf "Step:" range:[1,100000, 20] type:#integer width:60  align:#left;  
    spinner radW "Wheel radius:" range:[0,100000, 40] type:#float width:100 align:#left  --enabled:false across:2; checkbox theAuto "Auto" checked:true align:#center offset:[10,0]
        )
button theGo "Animate selection!" width:175 height:30  align:#left  --across:2
on theAuto changed theState do(
    radW.enabled = not theAuto.state
    if radW.enabled == false do radW.value = 0
    )
on theGo pressed do swa (selection as array) radW.value sf.value theCW.state rb1.state
on theCW changed theState do    swa (selection as array) radW.value sf.value theCW.state rb1.state
on rb1 changed state do swa (selection as array) radW.value sf.value theCW.state rb1.state
) --END rollout
createDialog SimpleWheelAnimator width:200
------------------------------------------------------------

Clear undefined Bitmaps

----------------------------------------------------

fn clearUndefinedBitMaps mm = (
  numSTM = getNumSubTexmaps  mm
  if numSTM != undefined and numSTM != 0 do  (
   for i = 1 to numSTM do  (
    BMAP = getSubTexmap  mm i
       if BMAP != undefined do(
                                        bm =  getclassinstances Bitmaptexture target:BMAP
                                        getUnd = false
                                        if bm.count != 0 do for t in bm do if t.filename == "" or t.filename == undefined  do getUnd = true
                                          
                                        if getUnd do(
                                            format "%, % - clear\n" mm.name BMAP.name
                                            setSubTexmap mm i undefined
                                            )
                                    )
                                )  
                            )
 )


fn theAllMat = (
  local allMat = #()
  join allMat (getclassinstances CoronaMtl)
  join allMat (getclassinstances VRayMtl)
  join allMat (getclassinstances Standardmaterial)
   allMat
 )

for m in theAllMat() do clearUndefinedBitMaps m
for m in sceneMaterials do clearUndefinedBitMaps m
for i = 1 to meditmaterials.count do if classOf meditmaterials[i] == BitmapTexture do if meditmaterials[i].filename == "" or meditmaterials[i].filename == undefined do (mn = meditmaterials[i].name; format "% - replase Standardmaterial in meditmaterials\n" mn; meditmaterials[i] = Standardmaterial name:mn;)
----------------------------------------------------

четверг, 9 июня 2016 г.

Выделить задублированные объекты в сцене.

Если вдруг выяснилось, что по ошибке произошло задвоение объектов в сцене и объекты абсолютно идентичные, с одним материалом  и т.п.. В общем, единственный способ их убрать - это вручную "протыкивать" , то  на помощь может прийти этот скрипт:

------------------------------------------------------------
 fn round_to val n =
(
    local mult = 10.0 ^ n
        (floor ((val * mult) + 0.5)) / mult
)
arPos = #()
arObj = #()

ar = selection as array

for i in ar do(
    rp = [round_to i.pos.x 1,round_to i.pos.y 1, round_to i.pos.z 1]
  
    if findItem arPos rp == 0 do(
        append arPos rp
        append arObj i
        )
    )
select arObj
------------------------------------------------------------


Функция округления взята отсюда: http://forums.cgsociety.org - Rounding a number, How?
 Похожие скрипты: http://www.scriptspot.com/ - Duplicate object finder/selecter


вторник, 7 июня 2016 г.

Расстановка объектов разной детализации в зависимости от расстояния до камеры.

Есть массив штор (быстро расставлена базовая заготовка).

Теперь надо поменять эту заготовку на высоко и низко детализированные шторы, в зависимости от расстояния до камеры:
Если расстояние от шторы до камеры меньше 2000 см, то - Hi, если больше - Mi шторы.

---------------------------------------------------------
 arBaseCur = selection as array

arHi = selection as array
arMi = selection as array

fn theDistAllFrames obj c = (
    b = false
    for j= animationRange.start to animationRange.end do(
       at time j (
           p1 = obj.pos
           pc = c.pos
           dist = length (p1 - pc)
           if dist <= 2000 do b = true
           )
    )
b
)

delAr = #()
nAr = #()
for i in arBaseCur do (
    _cam = for i in objects where classOf i == Targetcamera collect i
    if theDistAllFrames i _cam[1] then(
        RselFromAr = arHi[random 1 arHi.count]
        nobj = instance RselFromAr
        nobj.transform = i.transform
        nObj.wirecolor = RselFromAr.wirecolor
        append delAr i
        append nAr nobj
        )else (
                RselFromAr = arMi[random 1 arMi.count]
                nobj = instance RselFromAr
                nobj.transform = i.transform
                nObj.wirecolor = RselFromAr.wirecolor
                append delAr i
                append nAr nobj
        )
    )
delete delAr
select nAr
---------------------------------------------------------














понедельник, 6 июня 2016 г.

Скрипт убирает лишние материалы из Multi/Sub-Object.

Вот пример. В объекте используется всего два материала, остальные идут в виде бороды от старых сцен:
Есть несколько способов сделать это ручками стандартными средствами макса, но скриптом проще и быстрее:



-------------------------------------------------------------

-- work olny selection geometry
-- collapse modifier stack
fn GetOriginObj ar = (
    arrAL = #()
    arComp = #()
    for i in ar do(
        if findItem arComp i == 0 do(
            if classOf i.baseobject == Editable_Poly or classOf i.baseobject == Editable_mesh do(
                arPart = #()
                InstanceMgr.GetInstances i &allObjInst
                join arPart allObjInst
                join arComp arPart
                append arrAL arPart
                )
            )
    )
    arFin = #()
    aC = arrAL.count
    for i = 1 to aC do append arFin arrAL[i][1]
    arFin
    )

fn getArID obj =(
    arID = #()
    maxOps.CollapseNodeTo obj 1 true
    case classOf obj of (
                (Editable_Poly):(
                    nf = obj.numfaces
                    arID = makeUniqueArray(for f=1 to nf collect  polyop.getFaceMatId obj f)
                    )
                (Editable_Mesh):(
                    nf = obj.numfaces
                    arID = makeUniqueArray(for f=1 to nf collect  getFaceMatID obj f)
                    )
                default: (false)
            )
    )

fn setID obj arMat =(
    case classOf obj of (
                (Editable_Poly):(
                    nf = obj.numfaces
                    for f=1 to nf do(
                        _id = polyop.getFaceMatId obj f
                        for m in arMat do if _id == m[3]  do polyOp.setFaceMatID obj f m[4]
                        )
                    )
                (Editable_Mesh):(
                    nf = obj.numfaces
                    for f=1 to nf do(
                        _id = getFaceMatId obj f
                        for m in arMat do if _id == m[3]  do setFaceMatID obj f m[4]
                        )
                    )
                default: (false)
            )
    )  

arrObj = GetOriginObj (selection as array)  
select arrObj
  
progressstart "Optimization ID-materials..."  
arNum = arrObj.count
num = 1
for o in arrObj do(
    m = o.material
    arMat = #()
    if m != undefined and classOf m == Multimaterial do(
        arID = getArID o
        ac = arID.count
        if m.numsubs > ac do(
                if arID != false and ac > 1 do for i = 1 to ac do append arMat #(m[arID[i]], m.names[arID[i]], m.materialIDList[arID[i]], i)
                setID o arMat
                if setID != false do(
                            nMat = Multimaterial  numsubs:arMat.count name:("m-" + o.name)
                            for i = 1 to arMat.count do(
                                nMat.materialList[i] = arMat[i][1]
                                nMat.names[i] = arMat[i][2]
                                nMat.materialIDList[i] = arMat[i][4]
                                )
                              
                            update o  
                            --get instances obj
                            InstanceMgr.GetInstances o &allObjInst
                            for i in allObjInst    do i.material = nMat  

                            format "%, %" o.name allObjInst.count
                        )
                    progressupdate (num*100.0/arNum)
                    num +=1  
            ) -- end if m.numsubs > ac
        ) -- end if m != undefined and classOf m == Multimaterial
    ) -- end for o in arrObj
  
progressend ()
-------------------------------------------------------------

четверг, 2 июня 2016 г.

Удаление лишних вершин на ребрах в Editable Poly.


Понадобилось почистить кирпичную кладку от "лишних вершин" после булевых операций:




Ссылки:
Vertex Cleaner
How do I remove mid-edge EPoly Vertices?
VGSelectBadVertices
111_selectBadVertices

   После исследования скриптов по ссылкам, стало понятно, что они немного не подходят для поставленной задачи.


   В скрипте ниже, по сути, тоже самое, только сбоку.
   Добавил возможность работать с группой объектов, чтобы не перещелкивать каждый в больших сценах и постарался немного ускорить. Скрипт работает только с Editable Poly объектами!:
tr - threshold = 0.7 подбирается опытным путем.


---------------------------------------------------------------------
try destroyDialog RemoveMiVertex catch()
--function
fn getMiVertexPoly obj tr = (   
    arrVertInd = #()
    for v in obj.verts  do (
        arEdge=(PolyOp.getEdgesUsingVert obj #(v.index)) as array
        if arEdge.count==2 do (
                edgeVerts = (polyop.getVertsUsingEdge obj arEdge) as array
                p1 = polyop.getVert obj edgeVerts[1]
                p2 = polyop.getVert obj edgeVerts[2]
                p3 = polyop.getVert obj edgeVerts[3]
                v1 = p1 - p2
                v2 = p2 - p3
                v3 = length (cross (normalize v1) (normalize v2))
                if v3 <= tr do append arrVertInd v.index
        )

    )
format "%; % - removed vertices\n" obj.name arrVertInd.count
arrVertInd
)   
--Interface
rollout RemoveMiVertex "RemoveMiVertex"
(   
spinner tresh "Threshold:" range:[0,100,0.7] type:#float width:120 align:#left --offset:[10,0] enabled:false --across:2
checkbox onlySel "Only select (don't remove)" checked:false align:#left --offset:[10,0]
button theGo "Remove" width:175 height:30  align:#left  --across:2
    on theGo pressed do(
    arSel = for i in selection where classOf i.baseobject == Editable_Poly collect i
   
    max modify mode--switch to modify panel  --set the base object as current level:   
        for obj in arSel do(
            select obj--select it
            modPanel.setCurrentObject obj.baseobject
            subObjectLevel = 1--set sub-*object level to vertex level
            polyOp.setVertSelection obj (getMiVertexPoly obj (tresh.value/100.0))
            update obj   
            --pick button "Remove"   
            if not onlySel.state do (
                    h = windows.getChildHWND #max "Remove"
                    hc=windows.getChildrenHwnd h[2]
                    UIAccessor.PressButton hc[1][1]  --button  "Fit"  
                )
            -----------------------
            subobjectLevel = 0
            update obj   
            )
    ) -- end on theGo pressed do
) --END rollout
createDialog RemoveMiVertex width:200
---------------------------------------------------------------------

среда, 25 мая 2016 г.

Выравнивание вершин сплайнов по плоскостям.





-------------------------------
(
arrSpline = for i in selection where classOf i == SplineShape collect i

-- get all knots
global arKnots = #(), mX = 0, mY = 0, mZ = 0
for i in arrSpline do(
       
        for s = 1 to numSplines i do(
            nk = numKnots i s
            for k = 1 to nk do append arKnots #(i, s, k, getKnotPoint i s k)
          )--end s loop
    )
----------------
   
-- get mi value
arCount = arKnots.count
for i=1 to arCount do (
    mX+= arKnots[i][4].x
    mY+= arKnots[i][4].y
    mZ+= arKnots[i][4].z
    )
mX = mX/arCount   
mY = mY/arCount
mZ = mZ/arCount
------------------   

--set all knots   
for i = 1 to arCount do(
    knt = getKnotPoint arKnots[i][1] arKnots[i][2] arKnots[i][3]

--!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   
    --knt.x = mX
    --knt.y = mY  
    knt.z = mZ
--!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   
    setKnotPoint arKnots[i][1] arKnots[i][2] arKnots[i][3] knt
    updateshape arKnots[i][1]
    )   
   
  )
----------------------------------
Управлять плоскостями можно этими строками:
    --knt.x = mX
    --knt.y = mY  
    knt.z = mZ

 Удобен скрипт тем, что выравнивает вершины всех выделенных сплайнов в одной плоскости.

понедельник, 23 мая 2016 г.

Работа со строковыми переменными.


Есть текстуры с, примерно, такими именами:

"vm_v3_019_wood_chestnut.jpg"
....................................................
"vm_v3_###_wood_chestnut.jpg"

Все текстуры одинаковые, но с разными именами, что перегружает сцену. 
Надо убрать значения "###_" во всех похожих текстурах:

"vm_v3_wood_chestnut.jpg"


Вот скрипт, который это делает с небольшой инструкцией:

--------------------------------------------------------------
(
 --disableSceneRedraw()

albm = getclassinstances bitmapTexture
  
progressstart "Changing fileNames..."
local num = 1, numAll = albm.count
  
with redraw off (
        for n in albm do (
                 progressupdate (num*100.0/numAll)
                 if n.filename != undefined do(
                  a = filenameFromPath n.filename -- get "vm_v3_019_wood_chestnut.jpg"
                  b = filterString  a "_" -- get #("vm", "v3", "019", "wood", "chestnut.jpg")
                      
                  nb = b.count
        
                    try(if b[1] == "vm" and (classOf (execute b[3])) == integer do(
                            ns = b[1]
                            for i = 2 to nb do if i != 3 do ns = ns + "_" + b[i]
                            np = (getFilenamePath n.filename) + ns
                            n.filename = np
                        ))catch()
                  )
                 num+=1
                 )
    )  

progressend () 
--enableSceneRedraw()
)--------------------------------------------------------------

Полезные ссылки:
File Name Parsing
String Values


пятница, 20 мая 2016 г.

Создание Pin Art стены.




-----------------------------------------------------
p=pickobject prompt:"Pick surface to move to."

ar = selection as array

for i in ar do(
    theRay=ray i.pos i.dir
    isr =(intersectray p theRay)
    if isr != undefined do i.pos = isr.pos - i.dir*i.height
    )
-----------------------------------------------------

В данном случае, стержни - это цилиндры длиной 100см, радиусом - 0.5 см, с шагом 3 см.
Чтобы корректно определились перечения луча с поверхностью, заготовке (справа на картинке) надо вывернуть нормали.












четверг, 12 мая 2016 г.

Возможности Boolean в 3ds Max 2017.


Похоже булевы операции в 2017 максе стали работать гораздо лучше, чем в предыдущих версиях.
Появилась возможность изменять параметры исходных объектов, менять тип операций и многое другое.

Скачать сцену можно по этой ссылке.

среда, 27 апреля 2016 г.

Callback функции.

Пример на illusioncatalyst.com

Хороший урок от Bobo


General Event Callback Mechanism

Ниже простой пример вызова функции при выделении нужного модификатора.


----------------------------
fn checkModName theName =(
    r = false
    try(if (modPanel.getCurrentObject()).name == theName then r = true) catch()
    r
)

txt = "try(if checkModName \"UVW Map\" then print \"you have selected UVW Map modifier\" \n"
txt += "else print \"you have selected a different modifier\")catch()"

callbacks.addscript #modPanelObjPostChange txt id:#abrakadabra

------------------------------------------------------------
--callbacks.removeScripts id:#abrakadabra
эта строка для выгрузки функции из памяти.
----------------------------

четверг, 21 апреля 2016 г.

Просто и быстро заменить одни объекты другими (например hi польные на low польные, включая инстансные...). Или про функцию instancereplace.

Есть сцена, в которой понадобилось заменить высокополигональных людей на те же модели, но оптимизированные.
Делается этим скриптом, но в определенной последовательности!
-----------------------------------------start
  arLow = selection as array
 arHi = selection as array

 for i in arLow do(
    for j in arHi do(
         if i.name == j.name and i.material.name == j.material.name do(
                print i.name
                instancereplace j i
             )
        )
     )
-----------------------------------------end

Выделяем первую группу, например высокополигональных людей и выполняем строку (shift+enter):

arHi = selection as array
 создаем ,тем самым, массив hipoly
 Тоже для lowpoly группы:
arLow = selection as array


 Только теперь можно выполнить код:
--
 for i in arLow do(
    for j in arHi do(
         if i.name == j.name and i.material.name == j.material.name do(
                print i.name
                instancereplace j i
             )
        )
     )
 --
Благодаря использованию функции instancereplace в сцене должны поменяться все инстансные (зависимые) высокополигональные люди.