Automatically Add Custom Attribute?


#1

It’s been a while since I played with Custom Attributes, is there a way I can automatically add a Custom Attribute to a textureMap class, if that class is created by user or script?

I want to have a little extra rollout added to a certain texturemap class that basically adds some presets UI. I have a CA set up that does this, just want it always there :slight_smile:


#2
plugin textureMap AdvBitmap
	name:"AdvBitmap"
	classID:#(0x00de00, 0xde00de)
	extends:BitmapTexture
	replaceUI:off
(
	local AdvBitmapAttr = attributes AdvBitmapAttr attribid:#(0x00de01, 0xde00de) 
	(
		parameters params rollout:params
		(
		)
		rollout params "Advanced Attribute"
		(
		)
	)
	
	parameters params rollout:params
	(
	)
	rollout params "Advanced Bitmap"
	(
	)
	on create do
	(
		custattributes.add this AdvBitmapAttr 
	)
)
/*
a = AdvBitmap()
meditmaterials[1].diffusemap = a
*/

Capture(adv_attr)


#3

If you write your own (sdk) Texmap plugin you can use RefAdded method to handle all extras including Cust Attributes specific to the target (this)


#4

Working with a scripting plugin, you can double-check for the presence of a CA on load or update using the appropriate event handlers, and add the CA if necessary.


#5

yeah my first route was to look at making a texmap scripted plugin, but this relies on people remembering to use the ‘AdvBitmap’ instead of ‘Bitmap’. I wanted to make this only an extension to the existing map.


#6

How do you specify what BitmapTexture needs extra CA and what does not?

For example, I won’t be happy if someone starts adding some CA to all my BitmapTextures in the scene …


#7

Well in this case it is purely a studio-wide thing which would add a presets menu. BitmapTexture is not the target map here, a Vray one is.


#8

If the presets will only be accessed manually through the max materials editor interface, it might be better to add CAs on demand when the user switches to / opens / creates a specific texture class.


#9

Do you know? I have a local “security tool” for my clients that automatically finds and removes all CAs that I don’t know


#10

Yeah and that wouldn’t break anything with this as it’s just basically a UI extension. I can have a script button of course to add this extension to all-in-scene or something but I’m just curious to see if it can be done automatically?


#11

for sme you can try something like this, but I don’t know if it could provoke some slowdowns in views with lots of material nodes
it fires every time user activates some material or texturemap in param editor

deleteAllChangeHandlers id:#sme_test
when subAnimStructure trackViewNodes[#sme] changes id:#sme_test val do
(
	format ">> % - %\n" (SME.GetMtlInParamEditor()) val
)

#12

I like it … it’s a nice catch…


#13

another option would be to subclass sme parameter editor window and check if CA is present at each repaint event

parameters paint event
(

local   source = ""
		source  = "using System;\n"
		source += "using System.Collections.Generic;\n"
		source += "using System.Text;\n"
		source += "using System.Runtime.InteropServices;\n"
		source += "using System.Windows.Forms;\n"
		source += "\n"
		source += "namespace WinAPI\n"
		source += "{\n"
		source += "		class MessageSnooper : NativeWindow\n"
		source += "		{\n"
		source += "		public class MsgEventArgs : EventArgs
							{
								public MsgEventArgs( Message message )
								{
									Message = message;
									Handled = false;
								}
								public readonly Message Message;
								public bool Handled = false;
							}
						
							public event EventHandler MessageEvent;
												
							
							protected override void WndProc( ref Message message )
							{
								if ( MessageEvent != null )
								{
									MsgEventArgs msg = new MsgEventArgs( message );
									MessageEvent( this, msg );
									
									if ( msg.Handled ) return;
								}

								base.WndProc( ref message );
							}
						}
					}
					"		

				
		csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		compilerParams.ReferencedAssemblies.Add("System.dll");
		compilerParams.ReferencedAssemblies.Add("System.IO.dll");
		compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");		
		compilerParams.GenerateInMemory = on
		compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
		
		
		if (compilerResults.Errors.Count > 0 ) then
		(
			local errs = stringstream ""
			for i = 0 to (compilerResults.Errors.Count-1) do
			(
				local err = compilerResults.Errors.Item[i]
				format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
			)
			format "%\n" errs
			undefined
		)

		compilerResults.CompiledAssembly.CreateInstance "WinAPI.MessageSnooper"	

		global messagesnooper = dotnetobject "WinAPI.MessageSnooper"
		
)

try (destroydialog X ) catch ()
rollout X "SME Param Editor Subclassing" width:250
(	
	fn GetSMEParamEditorHWND = 
	(
		try
		(
			local max_hwnd          = windows.getMAXHWND()
			local sme_window_hwnd   = for w in windows.getChildrenHWND 0 where w[4] == "NodeJoeMainWindow" and w[6] == max_hwnd do exit with w[1]	
			local param_editor_hwnd = (windows.getChildHWND sme_window_hwnd "Material Parameter Editor")[1]
		
		)catch()
	)

	local param_editor_hwnd = GetSMEParamEditorHWND()
		
	checkbutton hook "Disabled" align:#center width:240
	
	fn OnMessageEvent msg =
	(		
		if msg.Message.Msg == 0x0085 do -- WM_NCPAINT
		(
			format "HWnd:%  LParam:% WParam:% Result:%\n" msg.Message.HWnd msg.Message.LParam msg.Message.WParam msg.Message.Result
			
			local item = SME.GetMtlInParamEditor()
			
			if item != undefined do
			(
				format ">> %\n" item
			)
		)		
	)		
	
	on x open do
	(	
		if not SME.IsOpen() do
		(
			SME.Open()
			param_editor_hwnd = GetSMEParamEditorHWND()

			setFocus X	
		)
		
		::messagesnooper.releaseHandle()
		dotNet.removeAllEventHandlers messagesnooper		
	)

	on hook changed state do
	(		
		if state and UIAccessor.IsWindow param_editor_hwnd then
		(			
			hook.Text = "Enabled"
			::messagesnooper.releaseHandle()
			dotNet.removeAllEventHandlers messagesnooper
			::messagesnooper.assignHandle (dotNetObject "System.IntPtr" param_editor_hwnd)
			dotnet.addEventHandler messagesnooper "MessageEvent" OnMessageEvent
			
		) else
		(			
			hook.checked = false
			hook.Text = "Disabled"
			::messagesnooper.releaseHandle()
			dotNet.removeAllEventHandlers messagesnooper
			
		)
		
	)

)
createDialog X

upd
actually it is better to catch WM_SETTEXT event sent to MtlName edit text control as it is fired less often and only when user changes node name or opens a texmap/mtl in parameters editor


(

local   source = ""
		source  = "using System;\n"
		source += "using System.Collections.Generic;\n"
		source += "using System.Text;\n"
		source += "using System.Runtime.InteropServices;\n"
		source += "using System.Windows.Forms;\n"
		source += "\n"
		source += "namespace WinAPI\n"
		source += "{\n"
		source += "		class MessageSnooper : NativeWindow\n"
		source += "		{\n"
		source += "		public class MsgEventArgs : EventArgs
							{
								public MsgEventArgs( Message message )
								{
									Message = message;
									Handled = false;
								}
								public readonly Message Message;
								public bool Handled = false;
							}
						
							public event EventHandler MessageEvent;
												
							
							protected override void WndProc( ref Message message )
							{
								if ( MessageEvent != null )
								{
									MsgEventArgs msg = new MsgEventArgs( message );
									MessageEvent( this, msg );
									
									if ( msg.Handled ) return;
								}

								base.WndProc( ref message );
							}
						}
					}
					"		

				
		csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		compilerParams.ReferencedAssemblies.Add("System.dll");
		compilerParams.ReferencedAssemblies.Add("System.IO.dll");
		compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");		
		compilerParams.GenerateInMemory = on
		compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
		
		
		if (compilerResults.Errors.Count > 0 ) then
		(
			local errs = stringstream ""
			for i = 0 to (compilerResults.Errors.Count-1) do
			(
				local err = compilerResults.Errors.Item[i]
				format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
			)
			format "%\n" errs
			undefined
		)

		compilerResults.CompiledAssembly.CreateInstance "WinAPI.MessageSnooper"	

		global messagesnooper = dotnetobject "WinAPI.MessageSnooper"
		
)

try (destroydialog X ) catch ()
rollout X "SME Param Editor Subclassing" width:250
(	
	fn GetSMEParamEditorHWND = 
	(
		try
		(
			local max_hwnd          = windows.getMAXHWND()
			local sme_window_hwnd   = for w in windows.getChildrenHWND 0 where w[4] == "NodeJoeMainWindow" and w[6] == max_hwnd do exit with w[1]	
			local param_editor_hwnd = (windows.getChildHWND sme_window_hwnd "MtlName")[1]
			for w in windows.getChildrenHWND param_editor_hwnd where w[4] == "Edit" do exit with w[1]

		)catch()
	)

	local param_editor_hwnd = GetSMEParamEditorHWND()
		
	checkbutton hook "Disabled" align:#center width:240
	
	fn OnMessageEvent msg =
	(	
		if msg.Message.Msg == 0x000C do -- WM_SETTEXT
		(
			format "HWnd:%  LParam:% WParam:% Result:%\n" msg.Message.HWnd msg.Message.LParam msg.Message.WParam msg.Message.Result
			
			local item = SME.GetMtlInParamEditor()
			
			if item != undefined do
			(
				format ">> %\n" item
			)
		)		
	)		
	
	on x open do
	(	
		if not SME.IsOpen() do
		(
			SME.Open()
			param_editor_hwnd = GetSMEParamEditorHWND()

			setFocus X	
		)
		
		::messagesnooper.releaseHandle()
		dotNet.removeAllEventHandlers messagesnooper		
	)

	on hook changed state do
	(		
		if state and UIAccessor.IsWindow param_editor_hwnd then
		(			
			hook.Text = "Enabled"
			::messagesnooper.releaseHandle()
			dotNet.removeAllEventHandlers messagesnooper
			::messagesnooper.assignHandle (dotNetObject "System.IntPtr" param_editor_hwnd)
			dotnet.addEventHandler messagesnooper "MessageEvent" OnMessageEvent
			
		) else
		(			
			hook.checked = false
			hook.Text = "Disabled"
			::messagesnooper.releaseHandle()
			dotNet.removeAllEventHandlers messagesnooper
			
		)
		
	)

)
createDialog X

#14

How about this (?):

global AdvBitmapAttr = attributes AdvBitmapAttr attribid:#(0x00de01, 0xde00de) 
(
	parameters params rollout:params
	(
	)
	rollout params "Advanced Attribute"
	(
	)
)

macroScript BitmapTextureCheck
	category:"Debug Ops"
	buttonText:"BMP CHECK"
	toolTip:"BitmapTexture Check"
	autoUndoEnabled:off
	silentErrors:off
(
	local need_add = off
	local enable_edit = on
	
	fn enabled = 
	(
		bb = getclassinstances BitmapTexture
		need_add = off
		for b in bb while not need_add where b.custattributes[#AdvBitmapAttr] == undefined do need_add = on 
		need_add
	)
	fn add_bitmap_ca = 
	(
		bb = getclassinstances BitmapTexture
		for b in bb where b.custattributes[#AdvBitmapAttr] == undefined do custattributes.add b AdvBitmapAttr
	)
	
	on isEnabled do (enabled())
	--on isChecked do (enabled() and enable_edit)
	on execute do if enabled() do 
	(
		undo "Add Bitmap Attribute" on add_bitmap_ca()
		updateToolbarButtons()
	)
)			

deleteAllChangeHandlers id:#bitmap_ca_check 
when geometry meditmaterials change id:#bitmap_ca_check do (updateToolbarButtons())
when geometry scenematerials change id:#bitmap_ca_check do (updateToolbarButtons())

when construct can be used for automatic update as well

bitmaptexture must be used in a scene or an edit material in this case

PS. of course all above can be combined in one Struct to minimize extra globals


#15

So CAs assigned only manually?

It seems to ‘randomly’ call updateToolbarButtons from the first change handler when switching between materials in basic medit editor.

when geometry meditmaterials change id:#bitmap_ca_check do (format ">> 1\n"; updateToolbarButtons()) -- called multiple times when switching between basic mtl editor materials
when geometry scenematerials change id:#bitmap_ca_check do (format ">> 2\n"; updateToolbarButtons())
gif

something very weird happens on scene reset
lG10BRTQur


#16

when you open/close material editor the system calls NOTIFY_MTL_REFADDED/NOTIFY_MTL_REFDELETED callbacks … once in the past it might have made sense, but now the need for it is completely incomprehensible :face_with_raised_eyebrow:

also you can see these callbacks every time you change the active slot of the material editor … (#added for selected and #deleted for deselected)

in this case, of course, no new references are creating and no old ones are deleting