Scripted Plugin Clone Event, detect type?


#1

I have a scripted plugin where when it’s cloned from a user shift-drag action I would like to run a function but if I’m cloning the object with maxops.cloneNodes instead I don’t want it to run. Trying to figure out if there’s a way to detect the user mode as we have many scripts which use cloneNodes()


#2

You could check whether Clone Options dialog was actually opened before #postNodesCloned event.
btw. It works for shift-drag copying but not for Ctrl + V (for that you can use ActionItem Notifications, not sure when it was first added to callbacks)

global n = 
(
	try ( n.Destroy() ) catch()
	local owner
	struct NodeShiftCloneHandler
	(
		selectionChanged = false,
		deleted          = false,
		
		shift_clone_active = false,
		cancelled          = false,
		
		fn OnEvent ev nodes =
		(
			if ev == #callbackBegin or ev == #callbackEnd do return true
			
			case ev of
			(
				#controllerOtherEvent :
				(
                    -- !!! there could be more than one instance of max with Clone Options dialog open, so you need to find them all. Moreover some max instances could be localized so it is better to find another method to check if the dialog is indeed Clone Options dialog. 
					local data = windows.getChildHWND 0 "Clone Options"
					owner.shift_clone_active = if data != undefined then (windows.getMAXHWND()) == data[6] else false
					
					format "Clone dialog is open: %\n" owner.shift_clone_active
				)				

				#selectionChanged : if owner.shift_clone_active do owner.selectionChanged = true
				#deleted          : if owner.shift_clone_active do owner.deleted = true			
			)
			
			if owner.selectionChanged and owner.deleted do owner.cancelled = true
			
			if owner.cancelled do
			(
				format "CANCELLED\n" 
				owner.NodeEvents.enabled = false
			)		
		),
		
		NodeEvents = NodeEventCallback enabled:false all:OnEvent,
		
		fn OnPreNodesCloned =
		(
			format "preNodesCloned\n"
						
			selectionChanged   = false
			deleted            = false	
			shift_clone_active = false
			cancelled          = false
		
			NodeEvents.enabled = true
		),

		fn OnPostNodesCloned =
		(
			format "postNodesCloned\n"
			NodeEvents.enabled = false
			
			if not cancelled and shift_clone_active do
			(
				format "SHIFT-CLONED\n" 
			)
			
		),
		
		fn Destroy =
		(
			NodeEvents = undefined
			callbacks.removeScripts id:#test42
			gc light:true		
		),
		
		on create do
		(
			callbacks.removeScripts id:#test42		
			callbacks.addScript #preNodesCloned  "n.OnPreNodesCloned()" id:#test42
			callbacks.addScript #postNodesCloned "n.OnPostNodesCloned()" id:#test42

			owner = this
		)
		
	)
	NodeShiftCloneHandler()
)

maxOps.CloneNodes objects[1]

#3

Another idea, not fully tested. It can fail if there are other scripts modifying the selection.

(
	global PreNodesCloned, PostNodesCloned
	
	SHIFT_PRESSED = false
	CLONED_NODES  = #{}
	
	fn PreNodesCloned =
	(
		SHIFT_PRESSED = keyboard.shiftPressed
		CLONED_NODES  = (for j in selection collect j.inode.handle) as bitarray
	)
	
	fn PostNodesCloned =
	(
		for j in selection do CLONED_NODES[j.inode.handle] = false
		
		if CLONED_NODES.isempty then
		(
			format "CLONED using \"maxOps.CloneNodes()\"\n"
		)
		else
		(
			if SHIFT_PRESSED == true then
			(
				selection.wirecolor = green
				format "CLONED using \"SHIFT\"\n"
			)
			else
			(
				selection.wirecolor = yellow
				format "CLONED using \"CRTL+V\"\n"
			)
		)
	)
	
	callbacks.removescripts id:#ID_0X4AE4797C
	callbacks.addscript #preNodesCloned  "PreNodesCloned()"  id:#ID_0X4AE4797C
	callbacks.addscript #postNodesCloned "PostNodesCloned()" id:#ID_0X4AE4797C
)

#4

I would fear that some script may use modifier keys to change script behavior
smth like this

	try (destroydialog X ) catch ()
	rollout X ""
	(
		button clone_node "clone node" width:100 tooltip:"Hold Shift to clone as instance, Hold Ctrl to clone as reference"
		
		on clone_node pressed do if selection[1] != undefined do
		(
			local type = #copy
			
			case of
			(
				( keyboard.shiftPressed   ) : type = #instance
				( keyboard.controlPressed ) : type = #reference				
			)
			
			maxOps.CloneNodes selection[1] cloneType:type offset:[100,0,0]
		)
	
	)
	createDialog X pos:[100,100]

#5

Nice catch. I’ve modified the script, but there might be other situations where it doesn’t work properly.


#6

Isn’t it easier and safer to provide your own cloning tool that will work alongside your scripted plugin?

Another way is to catch all cloning events, but update scripted data just by user’s request.
Do SHIFT clone > Update script plugin…


#7

Here is another way to catch “custom” (from UI) cloned nodes. I try to monitor “Clone Options” dialog and check if it finishes with #nodecloned general event.

it should catch both SHIFT and CTRL+V cases… but not maxops.clone or any 3-rd party

DialogMonitorOPS.unRegisterNotification id:#clone_monitor
callbacks.removescripts id:#clone_monitor


global cloned_nodes = #()
global callbackContainer


try 
(
	callbackContainer.enabled = off
	callbackContainer = undefined
)
catch()


fn CloneAccept = 
(
	if isvalidnode (node = callbacks.notificationParam()) do append cloned_nodes node
)

fn CloneEnd e h = 
(
	callbacks.removescripts id:#clone_monitor
	format "cloned >> %\n" cloned_nodes

	callbackContainer.enabled = off
	callbackContainer = undefined	
)

fn CloneMonitor = 
(
	hwnd = DialogMonitorOPS.GetWindowHandle()
	if (UIAccessor.isWindow hwnd) and (UIAccessor.GetWindowText hwnd) == "Clone Options" do
	(
		cloned_nodes = #()
		callbackContainer = NodeEventCallback callbackEnd:CloneEnd enabled:on

		callbacks.removescripts id:#clone_monitor
		callbacks.addscript #nodeCloned "CloneAccept()" id:#clone_monitor
	)
	true
)
DialogMonitorOPS.RegisterNotification CloneMonitor id:#clone_monitor
if not DialogMonitorOPS.enabled do DialogMonitorOPS.enabled = on 

(of course, we can put everything in one global structure, but this is not the point of this task)