Skip to content

Commit f96ac76

Browse files
committed
v1.1.0
- Added functionality to options to expand selection, clean entire scene. - Added new option to move cleaned objects to the active layer - Changed behavior to delete parents after cleanup instead of sorting things into layers - Added a call to 3ds Max's garbage collector at the beginning of cleanup to wipe the Undo/Redo cache (this was causing hangups if you cleaned large amounts of objects one after another, or undid a cleanup and then re-did it) - Improved exception handling to actually print a traceback - Changed some status labels to be more useful in the event that Max freezes
1 parent d29a9d1 commit f96ac76

File tree

3 files changed

+230
-82
lines changed

3 files changed

+230
-82
lines changed

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Clean DWG
2+
##### v1.1.0
3+
24
A 3dsMax script that will clean up `Block/Style Parent` dummy objects in imported DWGs.
35

46
Install by copying the cleanDWG folder to your 3ds Max scripts directory.
@@ -7,21 +9,19 @@ Run in 3ds Max using with the MaxScript snippet:
79

810
-----
911

10-
This script will operate only on selected objects. In the future,
11-
I'd like to expand it to search for relatives (parents and children) of
12-
the current selection, but for now you ***must make sure*** that you have
13-
all objects in a hierarchy selected. The recommended way to do this is
14-
by selecting top-level objects in the `Scene Explorer`, and then using the
15-
`Select Children` option.
12+
**Please note that while this operation *is* undoable, to prevent freezing
13+
it flushes the undo cache just before running. You will not be able
14+
to undo previous work after cleaning up.**
15+
16+
This script operates on selected objects, optionally expanding that selection to include
17+
their entire hierarchy of parents, children, etc. You may also choose to run the script
18+
on every object in the scene, which in the case of large numbers of objects can be faster
19+
than trying to expand a selection. While the script doesn't enforce this, it's
20+
recommended that you select entire object hierarchies, rather than individual objects.
1621

17-
To use, run the script and then select the objects (remember, Parents *and* Children,)
18-
that you want to clean up. Then, simply press the `Clean Block Parents`.
22+
To use, run the script, select any options you want, select the objects
23+
that you want to clean up, and then press the `Clean Block/Style Parents` button.
1924
Depending on the number of objects you have selected this may take a while,
2025
but should be fairly fast for selections with less than 10,000 objects.
21-
This operation *is* undoable, but don't be surprised if Max hangs up while
22-
undoing / redoing.
23-
24-
In this release, the script will not delete the old `Block/Style Parent`
25-
objects, and instead sort them into a layer. After that, it's
26-
up to the user to delete them. This will likely be changed in a future
27-
release.
26+
Note that the "Expand Selection" option will greatly slow down the script
27+
when large numbers of object are selected.

cleanDWG.py

Lines changed: 115 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# -----------------------
2-
# Clean DWG DEV BUILD
3-
# -----------------------
1+
# ----------------------
2+
# Clean DWG - v1.1.0
3+
# ----------------------
44

55
# Destroys instances of the dialog before recreating it
66
# This has to go first, before modules are reloaded or the ui var is re-declared.
@@ -26,6 +26,7 @@
2626

2727
# Misc
2828
import os
29+
import traceback
2930

3031

3132
# --------------------
@@ -77,13 +78,14 @@ def __init__(self, ui_file, pymxs, parent=MaxPlus.GetQMaxMainWindow()):
7778

7879
# Titling
7980

80-
self._window_title = 'Clean DWG - DEV BUILD'
81+
self._window_title = 'Clean DWG v1.1.0'
8182
self.setWindowTitle(self._window_title)
8283

8384
# ---------------------------------------------------
8485
# Widget Setup
8586
# ---------------------------------------------------
8687

88+
self._chk_layer = self.findChild(QtW.QCheckBox, 'chk_layer')
8789
self._chk_expand = self.findChild(QtW.QCheckBox, 'chk_expand')
8890
self._chk_full_scene = self.findChild(QtW.QCheckBox, 'chk_fullScene')
8991

@@ -110,6 +112,7 @@ def __init__(self, ui_file, pymxs, parent=MaxPlus.GetQMaxMainWindow()):
110112
'[2/4] Building list of Parents / Children...',
111113
'[3/4] Making Transform Controllers unique...',
112114
'[4/4] Cleaning up Parents...',
115+
'Waiting for 3ds Max to un-freeze...',
113116
'Done.',
114117
'%s Cleanup failed! Check the Max Listener for details.' % self._err]
115118
# Set initial status label
@@ -121,44 +124,93 @@ def __init__(self, ui_file, pymxs, parent=MaxPlus.GetQMaxMainWindow()):
121124
# ---------------------------------------------------
122125
# Private Methods
123126
# ---------------------------------------------------
127+
def _get_children(self, obj, input_list):
128+
children = obj.children
129+
for child in children:
130+
# Note that since we're calling this from a list of unique hierarchy roots, we don't have to worry about
131+
# checking for duplicate objects. Crawling down the trees will only yield each object one time.
132+
input_list.append(child)
133+
input_list = self._get_children(child, input_list)
134+
return input_list
124135

125136
# ---------------------------------------------------
126137
# Public Methods
127138
# ---------------------------------------------------
128-
129-
# TODO: Set up options checking
130-
# TODO: Add functionality for "Expand Selection" and "Clean Entire Scene" options
131-
132139
def clean(self):
140+
# UI Options
141+
layer = self._chk_layer.isChecked()
142+
expand = self._chk_expand.isChecked()
143+
scene = self._chk_full_scene.isChecked()
144+
145+
# Misc Variables
133146
rt = self._pymxs.runtime
134-
selection = []
135-
parents = []
136-
children = []
137-
138-
try:
139-
with self._pymxs.undo(True, 'Clean DWG'), self._pymxs.redraw(False):
140-
# It's much faster to sort the parents and children into layers, rather than deleting objects
141-
# Check if these layers already exist, if they do use those. Else, make them.
142-
layer_parents = rt.LayerManager.getLayerFromName('__CLEAN DWG - PARENTS - DELETE')
143-
if layer_parents == None:
144-
layer_parents = rt.LayerManager.newLayerFromName('__CLEAN DWG - PARENTS - DELETE')
145-
layer_children = rt.LayerManager.getLayerFromName('__CLEAN DWG - CHILDREN - CLEANED UP')
146-
if layer_children == None:
147-
layer_children = rt.LayerManager.newLayerFromName('__CLEAN DWG - CHILDREN - CLEANED UP')
148-
149-
layer_parents.isHidden = True
150147

148+
# Run 3ds Max garbage cleanup before we start. This will wipe the Undo/Redo cache, too.
149+
# Clearing the Undo cache prevents Max from hanging up when the script is used multiple times.
150+
rt.gc()
151+
152+
with self._pymxs.undo(True, 'Clean DWG'), self._pymxs.redraw(False):
153+
try:
151154
# 1/4
152155
# Build list of selected objects, optionally including their entire hierarchy.
153156
self._lbl_status.setText(self._status[1])
154157
selection = rt.getCurrentSelection()
158+
rt.clearSelection()
159+
160+
# Clean up selected objects ONLY
161+
if not expand and not scene:
162+
pass
163+
164+
# Expand selection to the full hierarchy of the selected objects
165+
elif expand and not scene:
166+
# Expand up to roots of selected objects
167+
selection_roots = []
168+
self._bar_progress.setMaximum(len(selection)*2)
169+
self._bar_progress.setValue(0)
170+
progress = 0
171+
for x in selection:
172+
progress += 1
173+
x_root = x
174+
x_parent = x.parent
175+
while x_parent is not None:
176+
x_root = x_parent
177+
x_parent = x_root.parent
178+
if x_root not in selection_roots:
179+
selection_roots.append(x_root)
180+
181+
self._bar_progress.setValue(progress)
182+
183+
# Expand down to children of roots
184+
selection_ex = list(selection_roots) # the list(...) format prevents us passing by reference
185+
progress = len(selection_roots)
186+
self._bar_progress.setMaximum(progress*2)
187+
self._bar_progress.setValue(progress)
188+
for x in selection_roots:
189+
progress += 1
190+
selection_ex = self._get_children(x, selection_ex)
191+
self._bar_progress.setValue(progress)
192+
193+
selection = list(selection_ex)
194+
195+
# Expand selection to entire scene
196+
elif scene:
197+
selection = rt.objects
198+
199+
# Something fishy's going on...
200+
else:
201+
self._lbl_status.setText(self._status[6])
202+
print "Unknown error, please re-run the Clean DWG script."
203+
return
155204

156205
# 2/4
157206
# Build Parent / Child lists
158207
self._lbl_status.setText(self._status[2])
159208
self._bar_progress.setMaximum(len(selection))
209+
self._bar_progress.setValue(0)
160210
progress = 0
161-
self._bar_progress.setValue(progress)
211+
212+
parents = []
213+
children = []
162214
for obj in selection:
163215
if rt.classOf(obj) == rt.LinkComposite:
164216
parents.append(obj)
@@ -182,33 +234,58 @@ def clean(self):
182234
self._bar_progress.setValue(progress)
183235

184236
# 4/4
185-
# Unparent children and organize objects into layers
237+
# Unparent children, then delete old parents. Optionally move children to current layer.
186238
self._lbl_status.setText(self._status[4])
187-
self._bar_progress.setMaximum(len(children) + len(parents))
239+
if layer:
240+
self._bar_progress.setMaximum(len(children)*2)
241+
else:
242+
self._bar_progress.setMaximum(len(children))
188243
progress = 0
189244
for child in children:
190245
child.name = child.parent.name
191246
child.parent = None
192-
layer_children.addNode(child)
193247

194248
progress += 1
195249
self._bar_progress.setValue(progress)
196250

197-
for parent in parents:
198-
layer_parents.addNode(parent)
251+
if layer:
252+
# Convert the full selection and list of parents to sets, so we can exclude parents from the for-loop
253+
set_selection = set(selection)
254+
set_parents = set(parents)
255+
set_selection = set_selection.difference(set_parents)
256+
current_layer = rt.LayerManager.current
199257

200-
progress += 1
258+
progress = len(set_selection)
259+
self._bar_progress.setMaximum(progress*2)
201260
self._bar_progress.setValue(progress)
261+
for x in set_selection:
262+
progress += 1
263+
current_layer.addNode(x)
264+
265+
self._bar_progress.setValue(progress)
266+
267+
rt.delete(parents)
202268

203269
# Done.
204270
self._lbl_status.setText(self._status[5])
205-
self._bar_progress.setValue(self._bar_progress.maximum())
271+
self._bar_progress.setMaximum(1)
272+
self._bar_progress.setValue(1)
273+
274+
# Print some info
275+
print "Cleaned up %d Block/Style Parents" % len(parents)
276+
277+
except Exception:
278+
traceback.print_exc()
279+
self._lbl_status.setText(self._status[7])
280+
self._bar_progress.setMaximum(100)
281+
self._bar_progress.setValue(0)
282+
283+
return
284+
285+
# This should run after Max un-freezes
286+
self._lbl_status.setText(self._status[6])
206287

207-
except Exception as e:
208-
print e
209-
self._lbl_status.setText(self._status[6])
210-
self._bar_progress.setMaximum(100)
211-
self._bar_progress.setValue(0)
288+
return
212289

213290

214291
# --------------------
@@ -224,4 +301,4 @@ def clean(self):
224301
ui.show()
225302

226303
# DEBUG
227-
# print "\rTest Version 4"
304+
# print "\rTest Version 7"

0 commit comments

Comments
 (0)