Notifications
Clear all

[Closed] Array data goes missing after closing

Hello again,

I’m having a few issues with a scripted plug-in I’ve been working on; it’s a helper object that contains 5 arrays of data.One of the problems that I’m seeing is the loss of array data after max is closed or a file is closed and reopened.The data is fine as long as the file does not close or a scene is not fetched, the data goes missing however after the file is close or fetched.

Another issue happens when a scene object that is contained in one of the arrays is deleted, I’m going to guess there is some why of writing over that section of the array with a string to indicate a delete scene node. Is there some simple function to detected if an object contained in an array has been deleted?

Thank you in advance, all input and idea are greatly appreciated.

James

11 Replies

You may want to look at using Appdata. From “Access to MAXWrapper Appdata”:

The AppData mechanism provides a way for plug-ins to attach arbitrary data to any 3ds Max object in a scene, such as nodes, materials, modifiers, controllers, etc., that is permanently stored with that object in the 3ds Max scene file.
So once set you would use Appdata to get/set the specific object settings as needed. This should stick with the scene and be unique per scene.

Never used it myself, but it should do what you need.
-Eric

Thanks Eric,

I made a simple helper object to test it all out and here is what I have:


plugin Helper testing
name:"testing"
classID:#(6452581739,258649731)
category:"Data Holders"
extends:point
replaceUI:true
autoPromoteDelegateProps:true
version:1
(
local lastSize,meshObj
local data
 
parameters pdisplay rollout:displayparams
(
size type:#float animatable:true ui:size_spnr default:5.0
 
)
rollout displayparams "Display Parameters"
(
label size_lbl "Size:" align:#left
spinner size_spnr "" type:#float align:#right range:[0,1e9,5.0] width:50 offset:[0,-20]
edittext textholder "text" feildWidth:160
button savedata "saveData" width:160
button loaddata "load data" width:160
button printdata "print data" width:160
 
on savedata pressed do
(
	data = textholder.text
	setAppdata $ 1 data
)
 
on loaddata pressed do
(
	data = getAppData $ 1
	print data
)
 
on printdata pressed do
(
	print data
)
 
on displayparams open do
(
	if (data != undefined) then
	(
	 textholder.text = data
	) else (
	 textholder.text = "No Data Loaded"
	)
)
)
 
on getDisplayMesh do
(
if (meshObj == undefined) do
(
	meshObj = createInstance sphere radius:size segs:4 mapCoords:false 
	lastsize = size
)
if size != lastsize do
(
	meshObj.radius = size
	lastsize = size
)
meshObj.mesh
)
 
tool create
(
on mousePoint click do
(
	nodeTM.translation = gridPoint;#stop
)
)
)

This solves my problem perfectly, now all I need to do is convert all my array data into a string when using AppData, and to build a conversion script that will convert my string back into an array.

Unfortunately the arrays that I have are a collection of nodes, not string.I guess I’ll have to make the array store the node names as strings; then re-gather the nodes using the getnodebyname command and store that in a local variable and call that variable each time I need information form that node.

Thanks again

 JHN

You should definitely have a look at stringTab for storing string arrays in the parameter block.
For storing node references use a maxObjectTab and use nodeTransformMonitor, maxNodeTab can be used to, but I wouldn’t suggest using it, because it’s not storing weak references. With a lot of dependencies it could slow down.

Check this very useful tutorial from Paul Neale : weakReferences

That’s the proper way to go. Nodes get stored over a save. If you clone the object the nodes will persist, appdata not so much (if I’m right).
Local variables should be considered session variables, meaning that they are only useful when the modifier panel is opened, they will not persist. Use the paramblock as much as you can. Read up on the possibilities in the mxs helpfile : Scripted Plug-in Clauses.

Scripted plugins are very powerful if done well, so it’s definitely worth investing some more time in it.

-Johan

Thanks Johan

I’m still a little confused about weak references even after reading and following along with PEN’s tutorial.However, I did manage to get the #nodetab parameter working properly, and it works like a dream.It has allowed me to reduce some code, and to incorporate the deleted node conditional while keeping my arrays of nodes organized.It also allows for updated names and transforms to be automatically incorporated and adjusted on the spot with minimal data manipulation.


plugin Helper testing
name:"testing"
classID:#(6452581739,258649731)
category:"Data Holders"
extends:point
replaceUI:true
autoPromoteDelegateProps:true
version:2
(
local lastSize,meshObj
 
fn updateComboBox ListBox TabList = 
(
local holder_ary = #()
for i=1 to TabList.count do
(
if (tabList[i] != undefined) then
(
	append holder_ary tabList[i].name
) else (
	append holder_ary "<Deleted>"
)
)
ListBox.items = holder_ary
)
 
parameters pdisplay rollout:displayparams
(
size type:#float animatable:true ui:size_spnr default:5.0
nodetab type:#nodeTab tabSizeVariable:true
)
rollout displayparams "Display Parameters"
(
label size_lbl "Size:" align:#left
spinner size_spnr "" type:#float align:#right range:[0,1e9,5.0] width:50 offset:[0,-20]
button getnodes_btn "Get Nodes" width:160
comboBox nodelist_cmbx "" width:160 align:#left offset:[-12,0]
button testNodes_btn "Test Nodes" width:160
button clearNodes_btn "Clear Nodes" width:160
 
on displayparams open do --creates the collumns used in the listview
(
updateComboBox nodelist_cmbx nodetab
)
 
on getnodes_btn pressed do
(
local selection_ary = pickObject count:#multiple forceListenerFocus:false rubberBand:$.pos rubberBandColor:yellow
for i=1 to selection_ary.count do
(
	appendifunique nodetab selection_ary[i]
)
updateComboBox nodelist_cmbx nodetab
)
 
on testNodes_btn pressed do
(
if (nodelist_cmbx.selection != 0) then
(
	print (nodetab[nodelist_cmbx.selection])
) else(
	messageBox "There are no Nodes in the selection list.
 
 Use the 'Get Nodes' button to select nodes to add to list" title:"No Node Selected"
)
)
 
on clearNodes_btn pressed do
(
nodetab = #()
updateComboBox nodelist_cmbx nodetab
)
)
 
on getDisplayMesh do
(
if (meshObj == undefined) do
(
meshObj = createInstance sphere radius:size segs:4 mapCoords:false 
lastsize = size
)
if size != lastsize do
(
meshObj.radius = size
lastsize = size
)
meshObj.mesh
)
 
tool create
(
on mousePoint click do
(
nodeTM.translation = gridPoint;#stop
)
)
)

Thanks again, now it’s time to update my plug-in

 JHN

Weak referencing makes sure that a now you are referencing is not notifying it’s dependents that an update has happened. Maybe you remember when we had the old script controllers, when there was no weak referencing, a changed node would sent out a lot of messages to the dependents saying it updated or changed geometry or whatever. Lot’s of overhead messaging going on. Weak referencing fixes that, in that it holds a pointer to a monitor that checks the messaging of the reference. Now the reference object is not updating your scripted helper anymore. Performance loss on 1 object should be minimal, but if you need a lot of them, be ready for slowdowns.

So here’s your tool using weakreferences:


plugin Helper testing
name:"testing"
classID:#(6452581739,258649731)
category:"Data Holders"
extends:point
replaceUI:true
autoPromoteDelegateProps:true
version:2
(
	local lastSize,meshObj

	parameters pdisplay rollout:displayparams
	(
		size type:#float animatable:true ui:size_spnr default:5.0
		nodetab type:#maxObjectTab tabSize:0 tabSizeVariable:true
	)
	
	rollout displayparams "Display Parameters"
	(
		
		label size_lbl "Size:" align:#left
		spinner size_spnr "" type:#float align:#right range:[0,1e9,5.0] width:50 offset:[0,-20]
		button getnodes_btn "Get Nodes" width:160
		comboBox nodelist_cmbx "" width:160 align:#left offset:[-12,0]
		button testNodes_btn "Test Nodes" width:160
		button clearNodes_btn "Clear Nodes" width:160

		fn updateRollout =
		(
			nodelist_cmbx.items = for o in nodeTab collect if isValidNode o.node then o.node.name else "<Deleted>"
		)
		
		fn addNodes =
		(
			local selection_ary = pickObject count:#multiple forceListenerFocus:false rubberBand:$.pos rubberBandColor:yellow
			-- Loop over array everytime to check if the newly added nodes are added as well
			for s in selection_ary \
				where findItem (for o in nodeTab where isValidNode o.node collect o.node) s == 0 \
				do 
			(
				append nodeTab (nodeTransformMonitor node:s forwardTransformChangeMsgs:false)
			)
		)
		
		on displayparams open do --creates the collumns used in the listview
		(
			updateRollout()
		)
		 
		on getnodes_btn pressed do
		(
			addNodes()
			updateRollout()
		)
		 
		on testNodes_btn pressed do
		(
			if (nodelist_cmbx.selection != 0) then
			(
				print (nodetab[nodelist_cmbx.selection].node)
			) else(
				messageBox "There are no Nodes in the selection list.
 
 Use the 'Get Nodes' button to select nodes to add to list" title:"No Node Selected"

			)
		)
		 
		on clearNodes_btn pressed do
		(
			nodetab = #()
			updateRollout()
		)
	)
 
	on getDisplayMesh do
	(
		if (meshObj == undefined) do
		(
			meshObj = createInstance sphere radius:size segs:4 mapCoords:false 
			lastsize = size
		)
		if size != lastsize do
		(
			meshObj.radius = size
			lastsize = size
		)
		meshObj.mesh
	)
	 
	tool create
	(
		on mousePoint click do
		(
			nodeTM.translation = gridPoint;#stop
		)
	)
)

The only difference is in creating the weak reference (nodeTransformMonitor) and accessing it. nodeTab[1].node. That’s it. I cleaned the code a bit too…

Hope it helps,
-Johan

Thank Johan,

Looks like I still have a way to go till I can organize and format my code to legible standards. I do have another question though, not sure if this should be part of thread or a new one all together.

I want to move the comboBox out to its own dialog box, so I can show more than just the name of an object through the use of a dotnet listView object dialog window without it being squished in the modify panel. I know this is possible, I’ve seen other tools do this and pass data back and forth, like the skin modifier does with Weight Table.

Am I just looking in the completely wrong direction?
Are they two separate plug-ins/scripts?
How would I pass parameters or variable back and forth?

 
plugin Helper testing
name:"testing"
classID:#(6452581739,258649731)
category:"Data Holders"
extends:point
replaceUI:true
autoPromoteDelegateProps:true
version:1
(
local lastSize,meshObj
 
rollout nodeData "Node Data"
(
comboBox nodelist_cmbx "" width:160 align:#left offset:[-12,0]
)
 
parameters pdisplay rollout:displayparams
(
size type:#float animatable:true ui:size_spnr default:5.0
nodetab type:#maxObjectTab tabSize:0 tabSizeVariable:true
)
 
rollout displayparams "Display Parameters"
(
 
label size_lbl "Size:" align:#left
spinner size_spnr "" type:#float align:#right range:[0,1e9,5.0] width:50 offset:[0,-20]
button getnodes_btn "Get Nodes" width:160
checkbutton viewNodesData_ckbn "View Node Data" width:160 checked:false
button clearNodes_btn "Clear Nodes" width:160
fn updateRollout =
(
this.nodeData.nodelist_cmbx.items = for o in nodeTab collect if isValidNode o.node then o.node.name else "<Deleted>"
)
 
fn addNodes =
(
local selection_ary = pickObject count:#multiple forceListenerFocus:false rubberBand:$.pos rubberBandColor:yellow
-- Loop over array everytime to check if the newly added nodes are added as well
for s in selection_ary \
	where findItem (for o in nodeTab where isValidNode o.node collect o.node) s == 0 \
	do 
(
	append nodeTab (nodeTransformMonitor node:s forwardTransformChangeMsgs:false)
)
)
 
on displayparams open do --creates the collumns used in the listview
(
updateRollout()
)
 
on getnodes_btn pressed do
(
addNodes()
updateRollout()
)
 
on viewNodesData_ckbn changed state do
(
if (state) then
(
	local window1 = NewRolloutFloater "testing" 200 100 --opens a rolloutFloater
	addrollout this.nodeData window1 --fails to add rollout to window1 floater
 
	local window2 = CreateDialog this.nodeData width:200 height:100 pos:[100,200] --does not show any dialog window
 
	print ("window2 position: "+(GetDialogPos this.nodeData)as string) --prints position of dialog window
	print ("window2 size: "+(GetDialogSize this.nodeData)as string) ----prints size of dialog window
) 
else 
(
	if (window1 != undefined) do (closerolloutfloater window1; print "removing window1") --unable to close rolloutFloater but will print
	if (window2 != undefined) do (DestroyDialog nodeData; print "removing window2") --unable to close rolloutFloater but will print
)
 
if (window1 != undefined) then (print "window1 found") else (print "NO window1") --looks for window1, it does find the value for the floater
if (window2 != undefined) then (print "window2 found") else (print "NO window2") --looks for window2, it does find the value for the dialog
)
 
on clearNodes_btn pressed do
(
nodetab = #()
updateRollout()
)
)
 
on getDisplayMesh do
(
if (meshObj == undefined) do
(
meshObj = createInstance sphere radius:size segs:4 mapCoords:false 
lastsize = size
)
if size != lastsize do
(
meshObj.radius = size
lastsize = size
)
meshObj.mesh
)
 
tool create
(
on mousePoint click do
(
nodeTM.translation = gridPoint;#stop
)
)
)

 JHN

Take a look, the easiest way is to define the parameterblock to the rollout so it can easy access it. Then you make sure that the functions needed for displaying data are defined in the rollout you want to popup, so it can easy access it too. Then to pop the rollout, it cannot be active at that time, because only 1 unique instance of it can be displayed at a time. So switch to create mode, pop it and switch back.

Now if you want a listview all the functions should be in the same rollout as well, and to get you started on the listview, again head over to paul’s website, he has some great primers on dotnet controls.

And as a tip, please have a look at indenting your code properly, for small snippets indention is no problem, but code over 100 lines get really messy to look at.


plugin Helper testing
name:"testing"
classID:#(6452581739,258649731)
category:"Data Holders"
extends:point
replaceUI:true
autoPromoteDelegateProps:true
version:1
(
	local lastSize,meshObj

	parameters pobjects rollout:nodeData
	(
		nodetab type:#maxObjectTab tabSize:0 tabSizeVariable:true
	)
	
	rollout nodeData "Node Data"
	(
		comboBox nodelist_cmbx "" width:160 align:#left offset:[-12,0]
		
		fn updateRollout =
		(
			nodelist_cmbx.items = for o in nodeTab collect if isValidNode o.node then o.node.name else "<Deleted>"
		)
		
		on nodeData open do updateRollout()

	)
	 
	parameters pdisplay rollout:displayparams
	(
		size type:#float animatable:true ui:size_spnr default:5.0
	)
	 
	rollout displayparams "Display Parameters"
	(
			 
		label size_lbl "Size:" align:#left
		spinner size_spnr "" type:#float align:#right range:[0,1e9,5.0] width:50 offset:[0,-20]
		button getnodes_btn "Get Nodes" width:160
		button viewNodesData_ckbn "View Node Data" width:160 checked:false
		button clearNodes_btn "Clear Nodes" width:160
		
		fn addNodes =
		(
			local selection_ary = pickObject count:#multiple forceListenerFocus:false rubberBand:$.pos rubberBandColor:yellow
			-- Loop over array everytime to check if the newly added nodes are added as well
			for s in selection_ary \
				where findItem (for o in nodeTab where isValidNode o.node collect o.node) s == 0 \
				do 
			(
				append nodeTab (nodeTransformMonitor node:s forwardTransformChangeMsgs:false)
			)
		)
		 
		on displayparams open do --creates the collumns used in the listview
		(
-- 			updateRollout()
		)
		 
		on getnodes_btn pressed do
		(
			addNodes()
			this.nodeData.updateRollout()
		)

		on viewNodesData_ckbn pressed do
		(
			-- switch to create mode, to release the rollout from displaying
			max create mode
			createDialog this.nodeData
			-- switch back to modify
			max modify mode
		)
	
		on clearNodes_btn pressed do
		(
			nodetab = #()
			this.nodeData.updateRollout()
		)
	)
	 
	on getDisplayMesh do
	(
		if (meshObj == undefined) do
		(
			meshObj = createInstance sphere radius:size segs:4 mapCoords:false 
			lastsize = size
		)
		
		if size != lastsize do
		(
			meshObj.radius = size
			lastsize = size
		)
		meshObj.mesh
	)
	 
	tool create
	(
		on mousePoint click do
		(
			nodeTM.translation = gridPoint;#stop
		)
	)
)

-Johan

You are a headache saving genius Johan.

The indention issues happens on my work computer only and only when I copy my code out of max to another software or text editor, not sue why but it replaces all my indents with a single space. All the code shows fine in my maxscript editor though.

One more quick question, I know I’m asking a lot but its for the greater good of my plug-ins and tools. I haven’t done much searching around yet, just a little in Google and the help file so I thought I’d ask anyways.

Is there a way to set the rollout used in the dialog to be hidden automatically when the helper is selected? Should I just add a remove rollout function to a ‘display Parameters’ on an open handler?

Thanks again

 JHN

There’s actually several ways to achieve some lock mode for this.

  1. You can walk over the controls and disabling every single one like:
for control in rollout.controls.do control.visible= false
  1. You can change the height of the rollout
rollout.height = 0

I don’t think you can actually hide it, it has to live somewhere. To prove my above points head over (once again) to Paul’s site http://www.paulneale.com/scripts/penHelpers/penHelper.htm
And found this snippet, in that tool (when you press lock UI checkbox.


 	fn lockDisplay enable:true roll:undefined=
 	(
 		if roll!=undefined then
 		(
 			for x in roll.controls do 
 			(
 				if x.text!=":Lock UI" and x.text!="Options:" and x.text!="About" then
 				(
 					x.visible=not enable
 				)
 			)
 			if enable then 
 			(
 				roll.height=50
 			)else
 			(
 				roll.height=677
 			)
 		)
 	)
 

As you can see, he even does both, disabling the controls and shrinking the rollout.

Hope this helps,
-Johan

Page 1 / 2