3ds max crashes when I run my script. A windows Error message box is produced (by the process "Windows Problem Reporting"). There are no errors produced in Maxscript (the listener does not display any errors during script run-time). CPU and RAM usage are at acceptable levels during script run-time. My script worked without crashed and produced the required results on a smaller scene (10-20 objects) with only standard primitive objects. The error/crash occurs on a larger scene containing 4051 polygonal objects.
The script looks at all objects, finds identical objects and deletes all but one of the identical objects leaving you with a scene without any duplicate objects).
When running the script on a scene containing 4051 objects windows gives me an error message box ; "3ds max has stopped working - A problem caused the program to stop working correctly. Windows will close the program and notify you if a solution is available."
During script run-time RAM and CPU usage of 3ds max remain acceptable (according to task manager)(30-40% CPU (i5-6600k) and 3-4GB RAM out of 16GB).
Image of the 'Windows Problem Reporting' error message box:
How the process looks in task manager:
Image of CPU/RAM usage of 3ds max during script runtime:
Note: after the error messagebox has appeared 3ds max's RAM/CPU/DISK usage drop to near zero values. After the error message box appears windows gives me no other choice than to close 3ds max.
I've let the script run a lot of times to check if the error message occurred at the same object each run, this was not the case, the error message occurred at a different object (when a different object was being processed by the script) each time I ran the script. The script tends to crash when it is comparing the first object in the scene ("Mesh_000") to another object in the scene with a mesh number in between "700" and "900". The following array contains the index of the mesh on which my script crashed during some of the tests I've performed: #(805,832,733,766,877). The script runs for 10-20 minutes before crashing.
The .max scene which causes the error can be found here:
larger .max scene which causes error
The script works like this(pseudo code):
duplicate_objs = #()
meshes_array = Collect all meshes in scene
for mesh in meshes_array do(
Compare mesh to all other meshes in meshes_array
if an identical mesh is found do(append duplicate_objs identical_mesh)
)
--Delete all the duplicate meshes:
delete duplicate_objs
The code for my script:
fn fn_construct_objs_array x = (
clearselection()
select geometry
deselect helpers
objs = selection as array
checked_objs_list = #()
items_to_delete_from_objs = #()
)
fn fn_delete_duplicates equal_pos_boolean =(
--print "DELETING DUPLICATES"
obj_dups = #()
ConvertTo objs[1] Editable_Poly
PolyCount_src = polyop.getNumFaces objs[1]
TriCount_src = (objs[1].mesh.numFaces)
VertCount_src = (getnumverts objs[1])
Position_src = objs[1].pos
for i in 1 to objs.count do(
if i <= objs.count and objs[i] != objs[1] do(
format "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n--> Comp. original obj (%) to comparison-obj(%) \n" objs[1] objs[i]
ConvertTo objs[i] Editable_Poly
PolyCount = polyop.getNumFaces objs[i]
Tricount = (objs[i].mesh.numFaces)
Position = objs[i].pos
VertCount = (getnumverts objs[i])
if(Tricount == Tricount_src and PolyCount == PolyCount_src and VertCount == VertCount_src and ( if equal_pos_boolean then( Position == Position_src)else(true) )) do(
if((finditem checked_objs_list (objs[i]) == 0) ) do(
appendifunique obj_dups objs[i]; --print "||||||||||||||||||||| FOUND DUPLICATE OBJECT |||||||||||||||||||||| \n"
appendifunique checked_objs_list objs[i]
append items_to_delete_from_objs objs[i]
)-- end if(finditem checked_objs_array objs[i] == 0 ) do(
)-- end if(Tricount == Tricount_src and PolyCount == PolyCount_src and VertCount == VertCount_src and ( if equal_pos_boolean then( Position == Position_src)else(true) )) do(
)-- end if i <= objs.count(
)
for item in items_to_delete_from_objs do(
deleteitem objs (finditem objs item)
)
items_to_delete_from_objs = #()
deleteitem objs 1
for duplicate in obj_dups do(delete duplicate)
if objs.count > 0 do (
--format "objs left to process = % \n" objs.count ;
fn_delete_duplicates equal_pos_boolean
)--end if objs.count > 0 do(
)--end fn_delete_duplicates
fn_construct_objs_array 1
fn_delete_duplicates true
Note: meshes being identical is (in this context) defined as the meshes having the same polycount, vertcount, edgecount, position.
I'm bad at debugging others' code, so again just a quick glance - I'd definitely avoid for loop in for duplicate in obj_dups do(delete duplicate), delete is a mapped function and you can pass a collection as an argument to it. That way, there won't be so many scene explorer updates and it should be much faster. I'd also prefer iteration over recursion. All in all, this is how I'd write it:
struct objInfo
(
obj, polyCount, vertCount, pos,
fn isSamePos pos1 pos2 eps:1e-6 =
(
local diff = pos2 - pos1
dot diff diff < eps
),
fn isEqual info checkPos:off =
(
this.vertCount == info.vertCount and
this.polyCount == info.polyCount and
(not checkPos or isSamePos this.pos info.pos)
),
on create do
(
local polyVertCount = getPolygonCount obj
polyCount = polyVertCount[1]
vertCount = polyVertCount[2]
pos = obj.pos
)
)
fn collectDuplicates checkPos:off =
(
items = for obj in geometry collect objInfo obj:obj
itemCount = items.count
collected = #{}
duplicates = #()
for item = 1 to itemCount where not collected[item] do
(
local current = items[item]
for nextItem = item + 1 to itemCount
where not collected[nextItem] and
(
local next = items[nextItem]
next.isEqual current checkPos:checkPos
)
do
(
append duplicates next.obj
append collected nextItem
)
)
return duplicates
)
delete (collectDuplicates checkPos:off)
From a cursory look, I'd warn against using objs[i].mesh.numFaces since it creates new mesh instance in memory and you're not calling dispose to delete it - if you opt to go this way and compare both polycount and tricount, definitely do the conversion and collect the meshes outside the loop first (and preferably use snapshotAsMes, too). Also, you might want to use getPolygonCount <node> which gives you node polygon count (if the result of the stack is polyobject) and vertex count.
I've edited my code using Swordslayer's suggestions; the collection of mesh information now takes place outside of the loop and I am using getpolygonCount to get both poly- and vertcount.
The scene mentioned in my original question containing 4051 standard primitive objects does not cause a crash anymore (and is significantly faster), however another scene containing imported objects now causes a 'Stack Overflow Error' inside 3ds max... The strange thing is that this error only occurs the first time I run the script after starting up 3ds max. When running the script a second (or third etc.) time after the stack overflow error has occured the script executes without any errors (takes approx 10-20 seconds for it to finish) and the script functions as it should (as in it deletes the duplicate objects). The stack overflow error occurs each time I run the script on the original scene AFTER just having started 3ds max.
Another strange thing (to me) is that even though the stack overflow error occurs, the script does indeed delete the proper objects (the duplicate objects) before producing said error.
RAM usage by 3ds max before/after the system exception error is as follows:
3ds max scene opened, before running script: RAM usage = approx 1800MB
during script run, before the error messagebox pops up: RAM usage increased from initial 1800 to a final value of 2132.6MB
system exception error messagebox has appeared: RAM usage = 2132.6MB
after clicking OK on the error messagebox: RAM usage = 2132.6MB
(note: as mentioned in my original question I have 16GB of ram)
My current code:
checked_objs_list; items_to_delete_from_objs; theMeshes; equal_pos_bool ; mesh_data
fn construct_arrays x =(
max select none ; select geometry ; deselect helpers
theMeshes = selection as array
mesh_info = for theMesh in theMeshes collect ( #(theMesh, getpolygoncount theMesh, theMesh.pos) ) --collects #(tricount, vertcount) for all meshes in 'getpolycount_meshes'
max select none
--Declare initial values of array variables
checked_objs_list = #()
items_to_delete_from_objs = #()
return mesh_info
)--end fn construct_arrays
fn delete_duplicates equal_pos_bool =(
obj_dups = #()
appendifunique items_to_delete_from_objs mesh_data[1]
if i <= mesh_data.count and i != 1 do(
if(mesh_data[i][2][1] == mesh_data[1][2][1] and mesh_data[i][2][2] == mesh_data[1][2][2] and ( if equal_pos_bool then( mesh_data[i][3] == mesh_data[1][3])else(true) )) do(
if((finditem checked_objs_list (mesh_data[i]) == 0) ) do(
appendifunique obj_dups mesh_data[i][1] ; --print "||||||||||||||||||||| FOUND DUPLICATE OBJECT |||||||||||||||||||||| \n"
appendifunique checked_objs_list mesh_data[i]
appendifunique items_to_delete_from_objs mesh_data[i]
)-- end if(finditem checked_objs_array objs[i] == 0 ) do(
)-- end if(Tricount == Tricount_src and PolyCount == PolyCount_src and VertCount == VertCount_src and ( if equal_pos_boolean then( Position == Position_src)else(true) )) do(
)-- end if i <= objs.count(
) --end for i in 1 to mesh_data[1].count do(
for item in items_to_delete_from_objs do(
deleteitem mesh_data (finditem mesh_data item)
)
items_to_delete_from_objs = #()
for duplicate in obj_dups do(delete duplicate)
if mesh_data.count > 0 do (
delete_duplicates equal_pos_bool
)
)
mesh_data = construct_arrays 1 --call construct_arrays function and save returned data in variable 'mesh_data'
delete_duplicates false --function call
The issue does not appear to be related to the amount of objects/polygons in the scene; I ran the script on a scene containing 16204 standard primitive objects This scene consists of 11,418,736 polygons and 5,780,404 vertices whilst the scene with the imported objects which is causing the stack overflow error consists of just 703,737 polygons and 769,886 vertices (which is significantly lower).
I let the stack overflow error occur multiple times, each producing a slighty different error messagebox, all of which can be seen below:
Error produced on 1st crash:
Error produced on 2nd crash:
Error produced on 3rd crash:
The error that is shown in the listener window tells me the error occured in 'Macro_SceneExplorer.mcr' in line 1001, this line contains the following code:
sceneexplorermanager.ExplorerIsOpen ~SE_SCENE_EXPLORER_NAME~
It also tells me this line of code was called from line 44 of my 'delete_duplicates.ms' script, which contains the following code:
for duplicate in obj_dups do(delete duplicate)
Here is part of the error message produced in the listener window after the stack overflow error has occured (the full error and stacktrace can be found here: https://drive.google.com/open?id=1s-BplnpiM_sz3yfN4XVySmhcsK5zraeX ) :
-- Error occurred in anonymous codeblock; filename: C:\Program Files\Autodesk\3ds Max 2018\MacroScripts\Macro_SceneExplorer.mcr; position: 29880; line: 1001
-- MAXScript MacroScript Error Exception:
-- Known system exception
-- ########################################################################
-- Address: 0x6745e920; nCode: 0x00000000C00000FD
-- Desc: EXCEPTION_STACK_OVERFLOW The thread used up its stack.
-- ########################################################################
-- MAXScript callstack:
-- thread data: threadID:5300
-- ------------------------------------------------------
-- [stack level: 0]
-- In isChecked(); filename: C:\Program Files\Autodesk\3ds Max 2018\MacroScripts\Macro_SceneExplorer.mcr; position: 29881; line: 1001
-- member of: codeblock macroScript: Scene Explorer_SESceneExplorer
-- Locals:
-- Externals:
-- owner: <CodeBlock:Scene Explorer_SESceneExplorer>
-- Scene Explorer_SESceneExplorer: <CodeBlock:Scene Explorer_SESceneExplorer>
-- ------------------------------------------------------
-- [stack level: 1]
-- called from codeblock macroScript: Scene Explorer_SESceneExplorer; filename: C:\Users\Philips\Desktop\ninjaripper1.7.1\tools \3dmax_maxscript_importer\0000_delete duplicates (pre-construct_arrays) 9 (noprint) 2.ms; position: 2755; line: 44
-- Locals:
-- Externals:
-- ------------------------------------------------------
-- [stack level: 2]
-- called from duplicate loop; filename: C:\Users\Philips\Desktop\ninjaripper1.7.1\tools\3dmax_maxscript_importer\0000_delete duplicates (pre-construct_arrays) 9 (noprint) 2.ms; position: 2755; line: 44
-- Parameters:
-- duplicate: <Deleted scene node>
-- Locals:
-- duplicate: <Deleted scene node>
-- Externals:
-- owner: undefined
-- ------------------------------------------------------
-- [stack level: 3]
-- called from delete_duplicates(); filename: C:\Users\Philips\Desktop\ninjaripper1.7.1\tools\3dmax_maxscript_importer\0000_delete duplicates (pre-construct_arrays) 9 (noprint) 2.ms; position: 2755; line: 44
-- Parameters:
-- equal_pos_bool: false
-- Locals:
-- equal_pos_bool: false
-- obj_dups: #(<Deleted scene node>)
-- Externals:
-- owner: undefined
-- delete_duplicates: Global:delete_duplicates : delete_duplicates()
-- mesh_data: Global:mesh_data : #(#($Editable_Mesh:Mesh_2184 # [0.000000,0.000000,0.000000], #(128, 28), [0,0,0]), #($Editable_Mesh:Mesh_2185 # [0.000000,0.000000,0.000000], #(128, 28), [0,0,0]), #($Editable_Mesh:Mesh_2256 # [0.000000,0.000000,0.000000], #(256, 768), [0,0,0]), #($Editable_Mesh:Mesh_2258 # [0.000000,0.000000,0.000000], #(2048, 1057), [0,0,0]), #($Editable_Mesh:Mesh_2261 # [0.000000,0.000000,0.000000], #(512, 273), [0,0,0]), #($Editable_Mesh:Mesh_2262 # [0.000000,0.000000,0.000000], #(512, 281), [0,0,0]), #($Editable_Mesh:Mesh_2263 # [0.000000,0.000000,0.000000], #(2048, 1057), [0,0,0]), #($Editable_Mesh:Mesh_2265 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2266 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2267 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2268 # [0.000000,0.000000,0.000000], #(128, 77), [0,0,0]), #($Editable_Mesh:Mesh_2269 # [0.000000,0.000000,0.000000], #(512, 273), [0,0,0]), #($Editable_Mesh:Mesh_2270 # [0.000000,0.000000,0.000000], #(2048, 1041), [0,0,0]), #($Editable_Mesh:Mesh_2271 # [0.000000,0.000000,0.000000], #(32, 21), [0,0,0]), #($Editable_Mesh:Mesh_2272 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2273 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2274 # [0.000000,0.000000,0.000000], #(512, 265), [0,0,0]), #($Editable_Mesh:Mesh_2276 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2277 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2278 # [0.000000,0.000000,0.000000], #(50, 60), [0,0,0]), ...)
-- items_to_delete_from_objs: Global:items_to_delete_from_objs : #()
-- ------------------------------------------------------
-- [stack level: 4]
-- called from delete_duplicates(); filename: C:\Users\Philips\Desktop\ninjaripper1.7.1\tools\3dmax_maxscript_importer\0000_delete duplicates (pre-construct_arrays) 9 (noprint) 2.ms; position: 2958; line: 49
-- Parameters:
-- equal_pos_bool: false
-- Locals:
-- equal_pos_bool: false
-- obj_dups: #()
-- Externals:
-- owner: undefined
-- delete_duplicates: Global:delete_duplicates : delete_duplicates()
-- mesh_data: Global:mesh_data : #(#($Editable_Mesh:Mesh_2184 # [0.000000,0.000000,0.000000], #(128, 28), [0,0,0]), #($Editable_Mesh:Mesh_2185 # [0.000000,0.000000,0.000000], #(128, 28), [0,0,0]), #($Editable_Mesh:Mesh_2256 # [0.000000,0.000000,0.000000], #(256, 768), [0,0,0]), #($Editable_Mesh:Mesh_2258 # [0.000000,0.000000,0.000000], #(2048, 1057), [0,0,0]), #($Editable_Mesh:Mesh_2261 # [0.000000,0.000000,0.000000], #(512, 273), [0,0,0]), #($Editable_Mesh:Mesh_2262 # [0.000000,0.000000,0.000000], #(512, 281), [0,0,0]), #($Editable_Mesh:Mesh_2263 # [0.000000,0.000000,0.000000], #(2048, 1057), [0,0,0]), #($Editable_Mesh:Mesh_2265 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2266 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2267 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2268 # [0.000000,0.000000,0.000000], #(128, 77), [0,0,0]), #($Editable_Mesh:Mesh_2269 # [0.000000,0.000000,0.000000], #(512, 273), [0,0,0]), #($Editable_Mesh:Mesh_2270 # [0.000000,0.000000,0.000000], #(2048, 1041), [0,0,0]), #($Editable_Mesh:Mesh_2271 # [0.000000,0.000000,0.000000], #(32, 21), [0,0,0]), #($Editable_Mesh:Mesh_2272 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2273 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2274 # [0.000000,0.000000,0.000000], #(512, 265), [0,0,0]), #($Editable_Mesh:Mesh_2276 # [0.000000,0.000000,0.000000], #(32, 23), [0,0,0]), #($Editable_Mesh:Mesh_2277 # [0.000000,0.000000,0.000000], #(128, 73), [0,0,0]), #($Editable_Mesh:Mesh_2278 # [0.000000,0.000000,0.000000], #(50, 60), [0,0,0]), ...)
-- items_to_delete_from_objs: Global:items_to_delete_from_objs : #()
The error goes on like this until stack level 386... the rest of the error looks similar to the section shown below (once again the error goes on for a long time, please see the previously mentioned .txt file for the full error):
-- [stack level: 387]
-- called from top-level
-- ########################################################################
-- C++ callstack:
-- (ucrtbase): (filename not available): malloc_base
-- (maxutil): (filename not available): GetWindowTextW
-- (MAXScrpt): (filename not available): InterfaceFunction::val_to_FPValue
-- (MAXScrpt): (filename not available): Generic::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): Generic::apply
-- (MAXScrpt): (filename not available): SourceFileWrapper::apply
-- (MAXScrpt): (filename not available): SourcePositionWrapper::apply_no_alloc_frame
-- (MAXScrpt): (filename not available): MacroEntry::DisableCompileGuard::DisableCompileGuard
-- (MAXScrpt): (filename not available): InitMacroScriptDir
-- (core): (filename not available): MacroButtonData::SetLabel
-- (core): (filename not available): MaxSDK::QMaxMultiResIconManager::trUtf8
-- (Qt5Core): (filename not available): QMetaObject::activate
-- (core): (filename not available): MaxSDK::QMaxMacroButtonUpdater::qt_static_metacall
-- (core): (filename not available): MaxSDK::QMaxMacroButtonUpdater::updateAll
-- (MAXScrpt): (filename not available): collect_nodes
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): Array::map
-- (MAXScrpt): (filename not available): clear_error_source_data
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): SourceFileWrapper::eval
-- (MAXScrpt): (filename not available): Generic::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): clear_error_source_data
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): SourceFileWrapper::eval
-- (MAXScrpt): (filename not available): Generic::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): clear_error_source_data
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): SourceFileWrapper::eval
-- (MAXScrpt): (filename not available): Generic::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
-- (MAXScrpt): (filename not available): clear_error_source_data
-- (MAXScrpt): (filename not available): Primitive::apply
-- (MAXScrpt): (filename not available): CodeTree::eval
Sometimes, instead of the error messagebox 'Stack Overflow Error' I get the error messagebox 'Unknown System Exception' (most of the time I get the stack overflow error messagebox, I have not been able to spot a reason for one of the other to occur yet). Other times no error messagebox is produced by 3ds max and windows problem reporting gives me a messagebox saying 3ds max has stopped working.
I tried finding more info about the '3ds max has stopped working' error using window's event viewer:
Files:
The scene containing 4000+ standard primitive objects on which the script works perfectly: https://drive.google.com/open?id=1ittqWwvqgJjrsNcNkbhM_mOHUnz1W-SH
The scene containing imported objects which causes the aforementioned stack overflow/unknown system exception errors:
https://drive.google.com/open?id=1JpncS9pJcYxGeZU4UI_uzmLIPnB9r0uJ
listener output (error) after stack overflow error message has occurred:
https://drive.google.com/open?id=1s-BplnpiM_sz3yfN4XVySmhcsK5zraeX
listener output (error) after unknown system exception error message has occurred:
https://drive.google.com/open?id=1QPl0e0RHcqKIPkMaorMRJm-Q_LyWvG1E
'delete_duplicates.ms' maxscript code:
https://drive.google.com/open?id=1FCpJNlgII6ouLYsh0ySjHXuTiKmfkWEK
Another .max scene I used for testing which produced similar errors:
https://drive.google.com/open?id=142JOth12rATD3sW4LlTmMX2vVzCLnmJs
Any suggestion on what I'm doing wrong/what might be the cause of these errors will be greatly appreciated, thank you!