PDA

View Full Version : Problems with Scripted simpleobject plugin + mapbutton + animated maptexture


seismograph
07-13-2008, 08:02 PM
Hi all!

I have a problem with a scripted "Simpleobject" plugin which generates Blinds for a complete fassade and which should control the grade of the opening with a maptexture.

I want to use a noise or gradient map instead a randomseed like in the script i wrote some years ago, which was basicaly based on jon seagull's blind object:
http://www.cgtechniques.com/goodies/blinds/

Here The Problems:
1. There is no drag'n'drop with the mapbutton possible, so i have to load it in the ME with "get material"
2. When i animate the map i don't get any feedback on the mesh, which is basicaly the reason why i want to invest time in rewriting this script. I found a workaround which only works in the viewport and not the renderer: just animate one pluginparam and you will get the animation when using the timeslider. ..dunno why.

I hope someone can help me fixing this errors. ..oh forgot to say it: it would be nice if the script is working also with max4, which i prefer over newer versions ;)

here the code ( i tried to clean it a bit, but it's a script which grows with the time and maybe becomes not easy to read for others ..sorry)


plugin simpleobject Blinds_test
name:"Blinds_test"
classID:#(0x6cd7715b, 0x44d977bf)
version:1
category:"AEC Extended"
(

parameters main rollout:params
(
open_map type:#TextureMap ui:ui_open_map
open_treshold type:#percent ui:ui_open_treshold default:0

blinds type:#integer ui:ui_blinds default:24
width type:#worldunits ui:ui_width

floors type:#integer ui:ui_floors default:6
roomheight type:#worldunits ui:ui_roomheight default:3.2
)

rollout params "Blinds Opening" width:162 height:309
(
mapbutton ui_open_map "<<none>>" width:150 tooltip:"Select Opening Map"
spinner ui_open_treshold "treshold: " type:#float scale:1 range:[0,99,0]

spinner ui_blinds "# of Blinds: " fieldwidth:45 type:#integer range:[2,60,24]
spinner ui_width "Fassade Width:" fieldwidth:45 type:#worldunits range:[0,99999,width]

spinner ui_floors "# of floors: " fieldwidth:45 type:#integer range:[2,20,10]
spinner ui_roomheight "Height betw. floors:" fieldwidth:45 type:#worldunits range:[0,9999,roomheight]
)




on buildmesh do
(

local element_width = width/blinds -- this is the width of ONE Element
verts=#()
faces=#()

if open_map != undefined then
(
global open_bitmap = renderMap open_map size:[blinds,floors] scale:10.0 --display:true --here i generate a bitmap to read the pixelvalues for each blind
)
else
(
open_map = gradient()
global open_bitmap = renderMap open_map size:[blinds,floors] scale:10.0 --display:true
)


-- vertical
for e in 1 to floors do
(
-- horizontal
for i in 1 to blinds do
(
rnd = 100-((getpixels open_bitmap [(i-1),(floors)-e] 1)[1].v/255)*100 -- *offset_blind
if rnd >= (99-open_treshold) then rnd = 99

ctr = (roomheight*rnd)/100
if ctr <= 0 then ctr = 0

local blindVerts = #(
[element_width*(i-1) , 0 , roomheight*(e-1)+ctr], --right bottom xyz
[element_width*i , 0 , roomheight*(e-1)+ctr], --left bottom
[element_width*(i-1) , 0 , roomheight*e], --rechts top
[element_width*i , 0 , roomheight*e]) --left top

local fi = verts.count+1
for v in 1 to blindVerts.count do append verts blindVerts[v]

local blindfaces = #([fi,(fi+1),(fi+2)],[(fi+3),(fi+2),(fi+1)])
for f in 1 to blindfaces.count do append faces blindfaces[f]

)--end for e
)--end for i

setMesh mesh verts:verts faces:faces
meshop.autoedge mesh mesh.edges 3 --autoedge

)--end buildmesh


tool create
(
-- Need to declare ST as local
local ST
local my_euler = eulerangles 0 0 0
local gdx
local gdy

on mousePoint click do case click of
(
-- Initialize ST variable to record the initial point clicked
1:
(
nodeTM.translation = st = gridPoint
nodeTM.rotation = quat 0 0 1 0
)
3:#stop

) -- end MousePoint

on mouseMove click do case click of
(

2:
(

-- width -------------------------

width = (abs(griddist.x * cos(gridangle.z))) + (abs(griddist.y * sin(gridangle.z)))

--rotation -------------------------
my_euler = eulerangles 0 0 gridangle.z

if shiftKey then --constrain to cardinal directions
(
local grid_z = gridangle.z
case of
(
(grid_z<=45 and -45<grid_z): my_euler = eulerangles 0 0 0
(45<grid_z and grid_z<=135): my_euler = eulerangles 0 0 90
(135<grid_z or grid_z<=-135): my_euler = eulerangles 0 0 180
(-135<grid_z and grid_z<=-45): my_euler = eulerangles 0 0 270
)--end case
)--end if

if ctrlKey then --constrains to 15 degree increments
(
local index, sign, the_angle
local angle_array = #(0,15,30,45,60,75,90,105,120,135,150,165,180)
if gridangle.z >= 0 then sign=1 else sign=2
index = floor(((abs(gridangle.z)+15)/15)+.5)
the_angle = angle_array[index]
case sign of
(
1: my_euler = eulerangles 0 0 the_angle
2: my_euler = eulerangles 0 0 -the_angle
)--end case
)--end if

nodeTM.rotation = my_euler as quat

-- fix position ------------------
nodeTM.translation = st
)



3:
(
height = abs(distance gridDist [0,0,0])
roomheight = height/floors
-- floors = (distance gridDist [0,0,0]) / roomheight

)



) -- end MouseMove

) -- end tool create


) -- end

ZeBoxx2
07-13-2008, 08:13 PM
1. There is no drag'n'drop with the mapbutton possible, so i have to load it in the ME with "get material"
That's a MaxScript limitation through to 3ds Max 2009 at least. Can only drag&drop to/from map/material buttons inside scripted materials/maps. It's quite annoying: suggest you add a button to let the user use the currently selected material in the material editor; still not pretty but probably easier than always hunting for it in the material/map browser.

-- activemeditslot should be new in Max 4
theMat = meditmaterials[activemeditslot]


2. When i animate the map i don't get any feedback on the mesh, which is basicaly the reason why i want to invest time in rewriting this script. I found a workaround which only works in the viewport and not the renderer: just animate one pluginparam and you will get the animation when using the timeslider. ..dunno why.
There may be other issues at play, but at least one problem is that rendermap() will always render at the current sliderTime, regardless of currentTime. Using "at time ( rendermap() )" doesn't work, and there's no "renderMap <map> time:N" either. I wrote the following function for a scripted render effect that had the same problem:

fn bmpRenderMapAtTime map into: size: filename: scale: filter: display: time:currentTime = (
-- change time so that rendermap takes the correct time into account
-- store the original settings
originalSliderTime = sliderTime
originalAnimationRange = animationRange

-- and change the time
animationRange = interval time (time + 1)
sliderTime = time


local result = renderMap map into:into size:size filename:filename scale:scale filter:filter display:display

-- restore time
animationRange = originalAnimationRange
sliderTime = originalSliderTime

-- return the result
result
)


Not pretty, but should do the trick. Again, though, there may be other problems with your script as well; can't run it at the moment. I think you need to supply a bitmap for into: (seeing the above code); looks like I never added a check for "if (into == unsupplied) then ( )" Basically the into: is there so that the calling script feeds it a bitmap to renderMap into... that way it doesn't allocate memory for a new bitmap every time it gets called.

seismograph
07-13-2008, 08:27 PM
Hi Richard,

thanks for the info. I read about the limitations of the mapbutton, but thought there is maybe a workaround (there is always one ;) ) . ..it don't really hurts, at least IMHO.

I will try your function and will tell you if it works.
The problem is that i don't understand that i can actually animate the blinds in the Viewport, so the rendermap does work when i change the currenttime, but it doesn't work when i render from frame 0-100 , because max doesn't render the bitmap before it renders the Frame itself.

well, i'm also not happy with the way i access the colorvalue of the map - a bit dirty (also in terms of memory usage).
:shrug:

seismograph
07-13-2008, 09:19 PM
After testing your function i can say that the object still needs to have a key to be set on one of the script params, but it renders at least fine! (why do the script do not run on your machine?)
http://www.cgtechniques.com/~upload/chris/blinds_test.avi
http://www.cgtechniques.com/~upload/chris/blinds_test2.avi
http://www.cgtechniques.com/~upload/chris/blinds_test3.avi

..BUT there is now another issue when using for example a MaterialByElement modifier, because it seems that the mesh then becomes corrupted and dissapear after some animated frames... and crashes max with endless viewport refreshes.

Is there a way to run parts of the code only while rendering?

PEN
07-14-2008, 01:11 PM
I"m going to suggest that you bite the bullet and upgrade as Max 4 has many limitations and bugs in systems that have been corrected over the years. Also with the addition of dotNet you could easily create your own drag and drop buttons and tools.

seismograph
07-14-2008, 02:03 PM
the max version is not the problem and to be honest, if i would need dotnet for a script i wouldn't waste my time on writing a script - i would instead write a plugin.
anyway..

I not really have fixed all issues, but i'm now able to render the animation. ...still with the workaround of setting an animationkey (which seems to force the script to rebuild the mesh with every frame)

The problem with crashing max was maybe the sliderTime behave of max, so i disabled the screenrefresh.

I attached a scene for you to have a look at it, because i still hope that someone can fix this stupid animated maptexture bug.
(it's btw a max2008 scene)




plugin simpleobject Blinds_test
name:"Blinds_test"
classID:#(0x6cd7715b, 0x44d977bf)
version:1
category:"AEC Extended"
(

parameters main rollout:params
(
open_map type:#TextureMap ui:ui_open_map animation:true
open_treshold type:#percent ui:ui_open_treshold default:0 animation:true

blinds type:#integer ui:ui_blinds default:24
width type:#worldunits ui:ui_width

floors type:#integer ui:ui_floors default:6
roomheight type:#worldunits ui:ui_roomheight default:3.2
)

rollout params "Blinds Opening" width:162 height:309
(
mapbutton ui_open_map "<<none>>" width:150 tooltip:"Select Opening Map"
spinner ui_open_treshold "treshold: " type:#float scale:1 range:[0,99,0]

spinner ui_blinds "# of Blinds: " fieldwidth:45 type:#integer range:[2,60,24]
spinner ui_width "Fassade Width:" fieldwidth:45 type:#worldunits range:[0,99999,width]

spinner ui_floors "# of floors: " fieldwidth:45 type:#integer range:[2,20,10]
spinner ui_roomheight "Height betw. floors:" fieldwidth:45 type:#worldunits range:[0,9999,roomheight]
)




on buildmesh do
(

local element_width = width/blinds -- this is the width of ONE Element
verts=#()
faces=#()

if open_map == undefined then
(
open_map = gradient()
)

with redraw off
(
--richard
originalSliderTime = sliderTime
originalAnimationRange = animationRange

-- and change the time
animationRange = interval currentTime (currentTime + 1)
sliderTime = currentTime


local open_bitmap = renderMap open_map size:[blinds,floors] scale:10.0 --display:true


-- restore time
animationRange = originalAnimationRange
sliderTime = originalSliderTime
)


-- vertical
for e in 1 to floors do
(
-- horizontal
for i in 1 to blinds do
(
rnd = 100-((getpixels open_bitmap [(i-1),(floors)-e] 1)[1].v/255)*100 -- *offset_blind
if rnd >= (99-open_treshold) then rnd = 99

ctr = (roomheight*rnd)/100
if ctr <= 0 then ctr = 0

/*
--classic blinds : up down
local blindVerts = #(
[element_width*(i-1) , 0 , roomheight*(e-1)+ctr], --right bottom xyz
[element_width*i , 0 , roomheight*(e-1)+ctr], --left bottom
[element_width*(i-1) , 0 , roomheight*e], --rechts top
[element_width*i , 0 , roomheight*e]) --left top
*/

-- windows which rotates
local blindVerts = #(
[element_width*(i-1) , 0 , roomheight*(e-1)], --right bottom xyz
[element_width*i , 0 , roomheight*(e-1)], --left bottom
[element_width*(i-1) , (ctr/2) , roomheight*e], --rechts top
[element_width*i , (ctr/2) , roomheight*e]) --left top



local fi = verts.count+1
for v in 1 to blindVerts.count do append verts blindVerts[v]

local blindfaces = #([fi,(fi+1),(fi+2)],[(fi+3),(fi+2),(fi+1)])
for f in 1 to blindfaces.count do append faces blindfaces[f]

)--end for e
)--end for i

setMesh mesh verts:verts faces:faces
meshop.autoedge mesh mesh.edges 3 --autoedge

)--end buildmesh


tool create
(
-- Need to declare ST as local
local ST
local my_euler = eulerangles 0 0 0
local gdx
local gdy

on mousePoint click do case click of
(
-- Initialize ST variable to record the initial point clicked
1:
(
nodeTM.translation = st = gridPoint
nodeTM.rotation = quat 0 0 1 0
)
3:#stop

) -- end MousePoint

on mouseMove click do case click of
(

2:
(

-- width -------------------------

width = (abs(griddist.x * cos(gridangle.z))) + (abs(griddist.y * sin(gridangle.z)))

--rotation -------------------------
my_euler = eulerangles 0 0 gridangle.z

if shiftKey then --constrain to cardinal directions
(
local grid_z = gridangle.z
case of
(
(grid_z<=45 and -45<grid_z): my_euler = eulerangles 0 0 0
(45<grid_z and grid_z<=135): my_euler = eulerangles 0 0 90
(135<grid_z or grid_z<=-135): my_euler = eulerangles 0 0 180
(-135<grid_z and grid_z<=-45): my_euler = eulerangles 0 0 270
)--end case
)--end if

if ctrlKey then --constrains to 15 degree increments
(
local index, sign, the_angle
local angle_array = #(0,15,30,45,60,75,90,105,120,135,150,165,180)
if gridangle.z >= 0 then sign=1 else sign=2
index = floor(((abs(gridangle.z)+15)/15)+.5)
the_angle = angle_array[index]
case sign of
(
1: my_euler = eulerangles 0 0 the_angle
2: my_euler = eulerangles 0 0 -the_angle
)--end case
)--end if

nodeTM.rotation = my_euler as quat

-- fix position ------------------
nodeTM.translation = st
)



3:
(
height = abs(distance gridDist [0,0,0])
roomheight = height/floors
-- floors = (distance gridDist [0,0,0]) / roomheight

)



) -- end MouseMove

) -- end tool create


) -- end

seismograph
07-14-2008, 04:38 PM
Hi all!

I finaly found another workaround: i added a dummy param which gets an float_script controller. Now max knows that it have to update the mesh with every Frame. Together with the sliderTime + refresh off workaround it works pretty nice.

Thanks All!

There are many other ways how a "dynamic fassade" can be created, but i think this is now a pretty easy way.
Here the basic Script, which can switch between blinds and tilted windows.



plugin simpleobject Blinds_test
name:"Blinds_test"
classID:#(0x6cd7715b, 0x44d977bf)
version:1
category:"AEC Extended"
(

parameters main rollout:params
(
type type:#integer ui:ui_type default:1

open_map type:#TextureMap ui:ui_open_map animation:true subanim:true
open_treshold type:#percent ui:ui_open_treshold default:0 animation:true

blinds type:#integer ui:ui_blinds default:24
width type:#worldunits ui:ui_width

floors type:#integer ui:ui_floors default:6
roomheight type:#worldunits ui:ui_roomheight default:3.2

-- part1 for the animating texture workaround : we add a controller to a dummyparameter
anim_helper type:#float animation:true
)

rollout params "Blinds Opening" width:162 height:309
(
radiobuttons ui_type labels:#("Blinds", "Tilted Windows") default:1

mapbutton ui_open_map "<<none>>" width:110 tooltip:"Select Opening Map" across:2 offset:[15,0]
button ui_send_map ">>ME" width:30 tooltip:"Send material to active Material editor Slot" offset:[20,0]
spinner ui_open_treshold "treshold: " type:#float scale:1 range:[0,99,0]

spinner ui_blinds "# of Blinds: " fieldwidth:45 type:#integer range:[2,60,24]
spinner ui_width "Fassade Width:" fieldwidth:45 type:#worldunits range:[0,99999,width]

spinner ui_floors "# of floors: " fieldwidth:45 type:#integer range:[2,20,10]
spinner ui_roomheight "Height betw. floors:" fieldwidth:45 type:#worldunits range:[0,9999,roomheight]

on ui_send_map pressed do meditmaterials[activemeditslot] = open_map
)




on buildmesh do
(

local element_width = width/blinds -- this is the width of ONE Element
verts=#()
faces=#()


if open_map == undefined then
(
open_map = gradient()
)


with redraw off
(
--richard
originalSliderTime = sliderTime
originalAnimationRange = animationRange

-- and change the time
animationRange = interval currentTime (currentTime + 1)
sliderTime = currentTime


local open_bitmap = renderMap open_map size:[blinds,floors] scale:10.0 --display:true


-- restore time
animationRange = originalAnimationRange
sliderTime = originalSliderTime
)



-- vertical
for e in 1 to floors do
(
-- horizontal
for i in 1 to blinds do
(
rnd = 100-((getpixels open_bitmap [(i-1),(floors)-e] 1)[1].v/255)*100 -- *offset_blind
if rnd >= (99-open_treshold) then rnd = 99

ctr = (roomheight*rnd)/100
if ctr <= 0 then ctr = 0

if type == 1 then
(

--classic blinds : up down
local blindVerts = #(
[element_width*(i-1) , 0 , roomheight*(e-1)+ctr], --right bottom xyz
[element_width*i , 0 , roomheight*(e-1)+ctr], --left bottom
[element_width*(i-1) , 0 , roomheight*e], --rechts top
[element_width*i , 0 , roomheight*e]) --left top

)
else
(
-- windows which rotates
local blindVerts = #(
[element_width*(i-1) , 0 , roomheight*(e-1)], --right bottom xyz
[element_width*i , 0 , roomheight*(e-1)], --left bottom
[element_width*(i-1) , (ctr/2) , roomheight*e], --rechts top
[element_width*i , (ctr/2) , roomheight*e]) --left top

)


local fi = verts.count+1
for v in 1 to blindVerts.count do append verts blindVerts[v]

local blindfaces = #([fi,(fi+1),(fi+2)],[(fi+3),(fi+2),(fi+1)])
for f in 1 to blindfaces.count do append faces blindfaces[f]

)--end for e
)--end for i

setMesh mesh verts:verts faces:faces
meshop.autoedge mesh mesh.edges 3 --autoedge

)--end buildmesh


-- part2 for the animating texture workaround
on Create do anim_helper.controller=float_script()

tool create
(
-- Need to declare ST as local
local ST
local my_euler = eulerangles 0 0 0
local gdx
local gdy

on mousePoint click do case click of
(
-- Initialize ST variable to record the initial point clicked
1:
(
nodeTM.translation = st = gridPoint
nodeTM.rotation = quat 0 0 1 0
)
3:#stop

) -- end MousePoint

on mouseMove click do case click of
(

2:
(

-- width -------------------------

width = (abs(griddist.x * cos(gridangle.z))) + (abs(griddist.y * sin(gridangle.z)))

--rotation -------------------------
my_euler = eulerangles 0 0 gridangle.z

if shiftKey then --constrain to cardinal directions
(
local grid_z = gridangle.z
case of
(
(grid_z<=45 and -45<grid_z): my_euler = eulerangles 0 0 0
(45<grid_z and grid_z<=135): my_euler = eulerangles 0 0 90
(135<grid_z or grid_z<=-135): my_euler = eulerangles 0 0 180
(-135<grid_z and grid_z<=-45): my_euler = eulerangles 0 0 270
)--end case
)--end if

if ctrlKey then --constrains to 15 degree increments
(
local index, sign, the_angle
local angle_array = #(0,15,30,45,60,75,90,105,120,135,150,165,180)
if gridangle.z >= 0 then sign=1 else sign=2
index = floor(((abs(gridangle.z)+15)/15)+.5)
the_angle = angle_array[index]
case sign of
(
1: my_euler = eulerangles 0 0 the_angle
2: my_euler = eulerangles 0 0 -the_angle
)--end case
)--end if

nodeTM.rotation = my_euler as quat

-- fix position ------------------
nodeTM.translation = st
)



3:
(
height = abs(distance gridDist [0,0,0])
roomheight = height/floors
-- floors = (distance gridDist [0,0,0]) / roomheight

)



) -- end MouseMove




) -- end tool create


) -- end

PEN
07-14-2008, 04:49 PM
You can also go with a callback system or you can use the parameter event handlers. So in the param block you can have on theParam get val do. Be careful with this as it is called when the file loads, if there are no changes to make it is still making them. To get around this I implemented a and oldParam and test that against the param that I'm changing. If they are different it updates.

ZeBoxx2
07-14-2008, 04:51 PM
I finaly found another workaround: i added a dummy param which gets an float_script controller. Now max knows that it have to update the mesh with every Frame.
Back late to the party, but yep... that's what I've got in one of our scripts as well;


parameters ... (
_autorefresh_donotmodify type:#float
)

on attachedToNode n do (
n._autorefresh_donotmodify.controller = float_script()
n._autorefresh_donotmodify.controller.script = "-- script controller to force mesh refresh. Do not remove.\n1.0"
)


Then a lot of the actual code on the simple object is handled in the "on _autorefresh_donotmodify get" handler.

Maybe this thread will spawn a cleaner method :D

CGTalk Moderation
07-14-2008, 04:51 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.