Animating IsHidden


#41

the method didn’t support gw.getviewportDIB() or active shade
it’s just for rendering


#42

The main drawback of frame-by-frame animation rendering is the lack of sound rendering capabilities. I don’t know why MZ needs to lighten the viewport (or camera view), but usually (at least in my experience) we need it in heavy cinematic scenes. But in this case, the sound is very important for the animation preview.


#43

That is what we are doing all time when we open Max or any 3d app/editor - if we changing something.

Changing some animated object state to hidden can improve performance while making it invisible will not.

Animating visibility is not demanding - will not have much influence on viewport performance.

What I thought author problem here is (but really I don’t know if there’s any):

-If we have some obj. with demanding animation, and it will become invisible but still influence performance - while we animate other things .

I would suggest to stop that obj. animation at the time it become invisible and thought we exploring ways to do that.
There are more ways , but…
But who cares, he made the decision…
And he said in first post that he decided so :grin:


#44

Here is another approach using .isHidden and node visibility, which should allow you to do hide/unhide objects y viewports and when rendering.

You need to be careful with the memory footprint.

(

	VisibilityTrackDeff = attributes VisibilityTrack
	(
		parameters main rollout:params
		(
			rendering type:#integer ui:rb_render   animatable:false default:1
			visible   type:#boolean ui:chk_visible animatable:true  default:true
		)
		
		rollout params "Parameters"
		(
			groupbox     gb          "Rendering" width:136 height:58
			radiobuttons rb_render   ""        pos:[22,24] labels:#("Enable in Viewport", "Enable in Renderer")
			checkbox     chk_visible "Visible" pos:[16,74] checked:true
			
			on rb_render changed arg do
			(
				for j in (getclassinstances emptyModifier) where j.name == "Visibility" do j.rendering = arg
				completeredraw()
			)
			
			on chk_visible changed arg do completeredraw()
		)
		
		on create do
		(
			visible.controller = boolean_float()
		)
	)
	
	fn AddVisibilityModifier obj =
	(
		mdf = emptyModifier name:"Visibility"
		custattributes.add mdf VisibilityTrackDeff
		
		ctrl = float_script()
		ctrl.addnode   "obj" obj
		ctrl.addobject "modifier" mdf
		ctrl.addobject "visible"  mdf.visible.controller
		
		scr  = "if (modifier.enabled == true) then"         + "\n"
		scr += "("                                          + "\n"
		scr += "	if (modifier.rendering == 1) then"      + "\n"
		scr += "	("                                      + "\n"
		scr += "		obj.ishidden = visible.value == 0"  + "\n"
		scr += "		1.0"                                + "\n"
		scr += "	)else("                                 + "\n"
		scr += "		obj.ishidden = false"               + "\n"
		scr += "		visible.value"                      + "\n"
		scr += "	)"                                      + "\n"
		scr += ")else("                                     + "\n"
		scr += "	obj.ishidden = false"                   + "\n"
		scr += "	1.0"                                    + "\n"
		scr +=")"
		
		ctrl.setexpression scr
		
		obj.visibility = on
		obj.visibility.controller = ctrl
		
		addmodifier obj mdf
		
		return mdf
	)
	
	/* Test Scene -------------------- */
	delete objects
	
	obj = teapot segs:64
	AddVisibilityModifier obj
	
	with animate on
	(
		for j = 1 to 100 by 5 do
		(
			at time j obj.modifiers[1].visible = (random 1 100 < 50)
		)
	)
	
)

Additionally, you could also use the node’s .boxmode instead of the .ishidden property, which would solve many issues.


#45

Great Idea! you always have new idea, I told you!


#46

Me? I didn’t!


#47

I got “illegal self-reference in controller script”


#48

This is same approach with scripted animation plugin, which works in max 2019 and above:

plugin FloatController IsHiddenController
name:"IsHidden Controller"
usePBValidity:false
classID:#(0xc1dbcd7, 0x70ecb08b)
(	
	local getting = false
	
	parameters pblock rollout:params
	(
		valueController type:#maxobject subAnim:true
		ownerMonitor type:#maxobject
	)
	rollout params "FloatController Test Parameters"
	(
		Spinner offset_value "Offset Value:" range:[0, 1e9, 40]
	)
	on getValue do 
	(
		val = if valueController.value == 0 then 0 else 1
		if classof ownerMonitor == NodeMonitor and isvalidnode (owner = ownerMonitor.node) and getting == false do
		(
			getting = true
			owner.isnodehidden = if val == 0 then true else false
			getting = false
		)
		val
	)
	on setValue val relVal commit do 
	(
		val = if val == 0 then 0 else 1
		SetControllerValue valueController val commit #absolute
	)	
	on create do
	(
		valueController = NewDefaultFloatController()
		isLeaf=true
	)
)

delete objects
obj = teapot isselected:true
obj.visibility = cnt = IsHiddenController()
cnt.isKeyable = true
cnt.isleaf = false
cnt.ownerMonitor = nodemonitor node:obj

with animate on
(
	at time 50 obj.visibility.controller.value = 0
)

#49

I don’t get any error from Max 2014 to 2021, however hiding/unhiding the nodes this way may lead to instability.

I tried animating 1500 objects in viewport and rendering without problems.

Doesn’t even the provided sample scene run or did you do something different?


#50

I don’t get the error anymore today, anyway this is great solution! I just don’t know what “Rendering” radio button does?


#51

Anyway, I think the Display Filter solution is much cleaner and safer:

delete objects
sps = for k=1 to 100 collect
(
	s = geosphere segments:150 pos:(random -[200,200,200] [200,200,200])  
	
	animate on
	(
		t = random 0 100
		at time t s.visibility = on
		at time (random t 100) s.visibility = off
	)
	
	s
)

fn visibilityFilter node = (not node.visibility)
registerDisplayFilterCallback visibilityFilter "Visibility"
k = getNumberDisplayFilters()
setDisplayFilter k on

#52

that’s what I ended up with in 2014 :slightly_smiling_face:


Btw shouldn’t this display filter somehow be applied/removed on scene load/reset? Plugin approach seems simpler in this regard


#53

RegisterDisplayFilterCallback as I remember was added to 2016 first time


#54

EmptyModifier changes class of object…


#55

I have an “Illegal self-reference” in your controller script …

easy to reproduce:

fn visibilityFilter node = (not node.visibility)
registerDisplayFilterCallback visibilityFilter "Visibility"
k = getNumberDisplayFilters()
setDisplayFilter k on

#56

There is no built-in mechanism to save custom display filters with the file. But you can do it with persistent general callbacks (e.g. #filePostOpen)


#57

It was already there in 2014 and I used it in my another script.
It was sphere creation that ate all the ram and hung the max. Your script actually works fine if I lower sphere segments count.


#58

The Rendering radio buttons simply unhide all the objects when se to “Enable in Renderer”, so when you render an animation they will be all unhidden but still use their visibility property. You can avoid using it is you use the “Render Hidden Geometry” in the render tab.

Since the “hidden” property can’t be natively animated, if an object is hidden when the render starts, it won’t be rendered in the whole animation. Can’t even set its state in the preRender or preRenderFrame callbacks.

But be aware that using animating these properties are not completely safe. It may be better is you use the .boxMode property instead of the .isHidden, if you can afford to have a bunch of lines in the viewports.

Here is an updated version which solves some redrawing issues and with some options changed.

(

	VisibilityTrackDeff = attributes VisibilityTrack
	(
		parameters main rollout:params
		(
			renderMode type:#integer ui:rb_renderMode animatable:false default:1
			visible    type:#boolean ui:chk_visible   animatable:true  default:true
		)
		
		rollout params "Parameters"
		(
			groupbox     gb            "Render Mode" width:136 height:88
			radiobuttons rb_renderMode ""                     pos:[22,24] labels:#("Enable in Viewport", "Enable in Renderer")
			button       bt_updateAll  "Update All Instances" pos:[22,60] width:118 height:26
			checkbox     chk_visible   "Visible"              pos:[16,102] checked:true
			
			fn UpdateRenderMode obj: =
			(
				if obj == unsupplied do obj = (refs.dependentnodes (custattributes.getowner this) firstonly:off)[1]
				
				obj.visibility.controller.SetConstant "renderMode" rb_renderMode.state
				obj.visibility.controller.Update()
				
				nodeinvalrect obj
			)
			
			on rb_renderMode changed arg do
			(
				UpdateRenderMode()
				completeredraw()
			)
			
			on bt_updateAll pressed do
			(
				for j in (getclassinstances emptyModifier) where j.name == "Visibility" do
				(
					j.renderMode = rb_renderMode.state
					obj = (refs.dependentnodes j firstonly:off)[1]
					UpdateRenderMode obj:obj
				)
				completeredraw()
			)
			
			on chk_visible changed arg do UpdateRenderMode()
		)
		
		on create do
		(
			visible.controller = boolean_float()
		)
	)
	
	fn AddVisibilityModifier obj =
	(
		mdf = emptyModifier name:"Visibility"
		custattributes.add mdf VisibilityTrackDeff
		
		ctrl = float_script()
		ctrl.addnode     "obj" obj
		ctrl.addobject   "modifier" mdf
		ctrl.addobject   "visible"  mdf.visible.controller
		ctrl.addconstant "renderMode" 1
		
		scr  = "if (modifier.enabled == true) then"							+ "\n"
		scr += "("															+ "\n"
		scr += "	obj.isHidden = (renderMode==1 and visible.value==0)"	+ "\n"
		scr += "	obj.boxMode  = (renderMode==2 and visible.value==0)"	+ "\n"
		scr += "	if renderMode==2 then visible.value else 1"				+ "\n"
		scr += ")else("														+ "\n"
		scr += "	obj.boxMode  = false"									+ "\n"
		scr += "	obj.isHidden = false"									+ "\n"
		scr += "	1.0"													+ "\n"
		scr +=")"
		
		ctrl.setexpression scr
		
		obj.visibility = on
		obj.visibility.controller = ctrl
		
		addmodifier obj mdf
		
		return mdf
	)
	
	delete objects
	
	obj = teapot segs:64
	
	AddVisibilityModifier obj
	
	with animate on
	(
		for j = 0 to 100 by 20 do
		(
			at time j obj.modifiers[1].visible = not obj.modifiers[1].visible
		)
	)
	
)

And here is a version that uses the .boxMode property only (also evaluates the panel to avoid the circular dependency error).

(

	VisibilityTrackDeff = attributes VisibilityTrack
	(
		parameters main rollout:params
		(
			visible type:#boolean ui:chk_visible animatable:true default:true
		)
		
		rollout params "Parameters"
		(
			checkbox chk_visible "Visible" checked:true
		)
		
		on create do
		(
			visible.controller = boolean_float()
		)
	)
	
	fn AddVisibilityModifier obj =
	(
		mdf = emptyModifier name:"Visibility"
		custattributes.add mdf VisibilityTrackDeff
		
		ctrl = float_script()
		ctrl.addnode     "obj" obj
		ctrl.addobject   "modifier" mdf
		ctrl.addobject   "visible"  mdf.visible.controller
		
		scr  = "if (modifier.enabled == true) and getCommandPanelTaskMode() != #display then" + "\n"
		scr += "("									+ "\n"
		scr += "	obj.boxMode = visible.value==0"	+ "\n"
		scr += "	visible.value"					+ "\n"
		scr += ")else("								+ "\n"
		scr += "	obj.boxMode  = false"			+ "\n"
		scr += "	1.0"							+ "\n"
		scr +=")"
		
		ctrl.setexpression scr
		
		obj.visibility = on
		obj.visibility.controller = ctrl
		
		addmodifier obj mdf
		
		return mdf
	)
	
	delete objects
	
	obj = teapot segs:64
	
	AddVisibilityModifier obj
	
	with animate on
	(
		for j = 0 to 100 by 20 do
		(
			at time j obj.modifiers[1].visible = not obj.modifiers[1].visible
		)
	)
	
)

#59

I think it is “safer” too, somehow. And you can code it with to work with a modifier, if preferred.


#60

Well, if you code a paradox you end up with an infinite loop. :wink:

Your example is one case, but there are others. For instance, simply go to the display panel and attempt to evaluate the script.