PDA

View Full Version : Script for sorting out splines


Alex Morris
05-23-2003, 12:51 PM
Hi guys

Here's a script that I wrote for fixing splines imported from autocad getting rid of most of the problems

It works fine for single objects, but bombs on selection sets for some reason...........where is the updateShape function supposed to go exactly?

Any ideas?

SCRIPT:

--define arrays
spl = #()
lin = #()
rec = #()
cir = #()
ell = #()
--assign objects from the initial selection to the relevant array
for i in selection do
(
i.wirecolor = (random black white)
case classOf i of
(
SplineShape: append spl i
Line: append lin i
Rectangle: append rec i
Circle: append cir i
Ellipse: append ell i
)
)

max select none
--sort the splines out
for s in spl do
(
addModifier s (Edit_Spline())
maxOps.CollapseNode s off
updateShape s
subobjectLevel = 1
max select all
updateShape s
splineOps.weld $
subobjectLevel = 0
updateShape s
print s.name
)

--sort the lines out
for L in lin do
(
addModifier L (Edit_Spline())
maxOps.CollapseNode L off
updateShape L
subobjectLevel = 1
max select all
updateShape L
splineOps.weld $
subobjectLevel = 0
updateShape L
print L.name
)

--sort the rectangles out
for r in rec do
(
addModifier r (Edit_Spline())
maxOps.CollapseNode r off
updateShape r
setKnotType r 1 1 #corner
setKnotType r 1 2 #corner
setKnotType r 1 3 #corner
setKnotType r 1 4 #corner
updateShape r
print r.name
)

--sort the circles out
for c in cir do
(
addModifier c (Edit_Spline())
maxOps.CollapseNode c off
updateShape c
c.steps = stps
updateShape c
print c.name
)

--sort the ellipses out
for e in ell do
(
addModifier e (Edit_Spline())
maxOps.CollapseNode e off
updateShape e
e.steps = stps
updateShape e
print e.name
)

LFShade
05-23-2003, 05:46 PM
Hey there, Alex!

Thanks for posting your script, it's good that things are getting going here in the new forum. I have some ideas about how you could optimize this, and fix it so that it works more reliably across a selection of objects. These days, I don't have Max available to test stuff on during the daytime, so bear with me if some of the names and syntax in my sample code aren't 100% correct:)

For starters, I don't think it is necessary to construct separate arrays for each class of object. Pop open Max and create one of each class of object you're after: a line, a rectangle, a circle, an ellipse, and a line collapsed into a splineShape. Select each one (individually) and evaluate superClassOf $ in the listener. If the result is the same for all of them (as I suspect it should be), then you can use this as criteria for collecting the objects you want to work with. Say you've found that the superclass is "Shape." Now you can do this in your script:

splines = for obj in selection where superclassOf obj == Shape collect obj

This is called a for..collect loop, and it works much like a for..do loop except that it -- get ready now -- collects objects! The where clause lets you define criteria for what will be collected, and the result of the collection can be assigned to a variable as I've done here with splines. Now you can loop through splines and do all the other stuff you want to do. The commands you were previously doing once for each object class can instead be done once, period, and you can still use a conditional to do the class-specific stuff:

max select none
for s in splines do
(
original_class = classOf s
addModifier s (Edit_Spline())
maxOps.CollapseNode s off
case original_class of
(
splineShape: ( <do splineshape stuff here> )
Line: ( <do line stuff here> )
etc...
...
...
)
updateShape s
print s.name
)

Assuming all this works, you've now condensed umpteen lines of code into just a few, and made it easier to read and maintain as well. Not a bad deal, eh?

In answer to your updateShape question, you'll typically want to call it after altering the subobjects of a spline. A good rule of thumb would be to use it after doing anything with splineOps. Note that you can do a bunch of splineOps stuff all together, and then call updateShape just once when you're done.

RH

Alex Morris
05-28-2003, 02:33 PM
Thanks RH, thats really useful!
I'll test it out over the next couple of days and repost the script.

I'm really glad that this section of the forum has started. I've been using Max since the beginning but have only really got my head around scripting over the last couple of months. Its really useful! But the max documentation leaves a lot to be desired if you dont have a programming background.

Alex Morris
05-28-2003, 05:51 PM
OK here it is rewritten

there seems to be a problem with collapse_nweld function which I can't get to the bottom of: Here's the listener return on a selection of one of each object:

#($Rectangle:Rectangle01 @ [133.493973,126.506027,0.000000], $Circle:Circle01 @ [36.867470,63.132530,0.000000], $Ellipse:Ellipse01 @ [112.771088,89.156624,0.000000], $Arc:Arc01 @ [-35.055008,49.649311,0.000000], $Line:Line02 @ [136.626511,43.132530,0.000000], $Editable_Spline:Line01 @ [-50.887184,99.189476,0.000000])
2
collapse_nweld()
fixrect()
fixcurve()
OK
"Rectangle01"
"Circle01"
"Ellipse01"
-- Error occurred in fixcurve()
-- Frame:
-- s: undefined
-- called in s loop
-- Frame:
-- ss: undefined
-- s: $Editable_Spline:Arc01 @ [-35.055008,49.649311,0.000000]
-- original_class: Arc
-- No ""addModifier"" function for undefined
OK



I've tried running each line manually and it works fine

any ideas?

LFShade
05-28-2003, 06:18 PM
The listener output points to a typo where you referred to "ss" instead of "s" in the case handler for Arc. There is a problem with collapse_nweld(), though - you called splineOps.weld $ where you should have called splineOps.weld s.

Note: Adding the edit spline modifier and collapsing have already been handled by the main loop, so you can speed up your script a bit by leaving these steps out of the three functions. As it is, they're just redundant.

Fix the above, and your script should run smoothly:thumbsup:

RH

Alex Morris
05-29-2003, 02:32 PM
Thanks again RH, just a quick question before I start coding again....

Can you just clarify why s instead of $?
s refers to the whole object, whereas $ refers to the vertices selected by select all.

LFShade
05-29-2003, 03:14 PM
$ never refers to a subobject selection - it always refers to the object selection. In your case it will always break if you have more than one object selected, or if the selected object is not an editable spline. Anyway, splineOps.weld() isn't supposed to take a selection of knots as its argument; it takes the editable spline object and then just welds whatever subobjects are selected. Hope that makes sense.

RH

Alex Morris
05-29-2003, 03:27 PM
Ahh! Thanks RH.
That fixes a few other things I was having problems with.
Unfortunately it still stalls........

OK so now I get this in the listener:

#($Editable_Spline:Line01 @ [-50.887184,99.189476,0.000000], $Rectangle:Rectangle01 @ [133.493973,126.506027,0.000000], $Circle:Circle01 @ [36.867470,63.132530,0.000000], $Ellipse:Ellipse01 @ [112.771088,89.156624,0.000000], $Arc:Arc01 @ [-35.055008,49.649311,0.000000], $Line:Line02 @ [136.626511,43.132530,0.000000])
2
collapse_nweld()
fixrect()
fixcurve()
OK
-- Error occurred in collapse_nweld()
-- Frame:
-- s: $Editable_Spline:Line01 @ [-50.887184,99.189476,0.000000]
-- called in s loop
-- Frame:
-- s: $Editable_Spline:Line01 @ [-50.887184,99.189476,0.000000]
-- original_class: SplineShape
-- Runtime error: Requested sub-object level out of range: 1
OK

Again I checked it manually and it seems fine.......incidentally I'm running Max 5.1

LFShade
05-29-2003, 04:47 PM
I overlooked this myself, but it's quite simple! Since nothing is selected as execution enters collapse_nweld(), there's no subobject level for it to enter into!

I'm thinking that there's probably a better way to write this function, anyway, which will avoid the overhead of selecting objects and entering subobject level. Look in the docs, under splineOps, for a function that sets the knot selection. If my guess is correct, it will be something smart like splineOps.setKnotSelection(). If there is such a function, you can use it to select all the knots without even having to select the spline object! I wish I could be more specific, but again I don't have Max here at work:(

If you don't want to go that route, just make sure you select s in collapse_nweld() before setting subobjectlevel. Another thing I just remembered is that for subobjectlevel you need to be in modify mode, and your script doesn't check for this. Before the main loop begins, you'll probably want to call max modify mode. One more tip: wrap the main loop in calls to disableSceneRedraw()/enableSceneRedraw(). This cuts down on viewport redraws, which will both speed up your script and make it look cleaner when run. So,

max modify mode
disableSceneRedraw()
sel = getCurrentSelection()
max select none
for s in splines do
(
...
)
select sel
enableSceneRedraw()

The sel = getCurrentSelection()/select sel stuff is cosmetic; I generally consider it a good idea to leave things as much as possible in the state they were in when the user runs a script.

If you still haven't got this stuff sorted out by tonight, I can probably be more helpful when I can at least open up the help docs. Until then, good luck - you're on the right track so far:thumbsup:


RH

Alex Morris
06-02-2003, 12:33 PM
OK I've gone down the select s route and attached the script which now seems to work although I need to test it a bit more yet.

Below is a loop for selecting all the vertices in a spline, but I cant seem to find a way of welding the selected vertices without getting into modify mode:

--for shape called s
--store the number of splines in the shape
sp_n = s.numSplines
--create an array to store the knot index values
sp_v = #()
--loop through all the splines in the shape to create a spline index array and a knot index array
--sii is the spline index integer
for sii in 1 to sp_n do
(
--vn is the number of knots in each spline
spv_n = numKnots s sii
--loop through all the vertices in each spline to add them to a temp index array
for k in 1 to spv_n do
(
append sp_v k
)
--add the vertices for spline k to the selection
setKnotSelection s sii sp_v keep:true
--reset the index array for the next spline loop
sp_v = #()
--at this point all the vertices in the shape should be selected
)

Any ideas?

Alex Morris
06-02-2003, 04:58 PM
Just for a bit of variety, here's the start of a dialogue version of the same script...............be patient this is my first dialogue!
Right now it doesn't work too well, since I got a bit confused about the processing order and where to put/assign variables and functions.

Having some fun with the structure, and a rogue integer problem in one of the spinners.

It would be nice to insert some feedback into the dialogue in the labels TBC1 and TBC2...........how do you do this?

Also maybe a progress bar at some point when I've got my head around the overall structure.

Any thoughts?

LFShade
06-02-2003, 05:08 PM
It's disappointing that splineOps.weld() doesn't work outside of modify mode/subobject level. Since it doesn't, it looks like you're doing things the best way available, so I apologize for steering you down a more confusing path. There are a couple of things left in your script that you can clean up, though.

You're doing sel = getCurrentSelection(), and then later doing splines = for obj in selection where superclassOf obj == Shape collect obj. It would be slightly more efficient (and logical) to use the objects you've already gathered into sel for the collect loop. For instance:

sel = getCurrentSelection()
splines = for obj in sel where superclassOf obj == Shape collect obj

Here's a neat trick - you can condense this all down into a one-liner:

splines = for obj in (sel=getCurrentSelection()) where superclassOf obj == Shape collect obj

This works because assignment returns the value being assigned, and because (sel=getCurrentSelection()) is still being executed in the same namespace where select sel is later being called.

The only other thing that I see is that you're calling max select none twice. Neither of these two things will break your script if you leave them as is, I just wanted to give you some housecleaning tips:p

Next step would be to make an interface so the user can set some of their own options, like what types of shapes to operate on, what types of cleanup to do, setting the steps variable, etc. Are you ready to try this?

RH

<edit> I wrote that last part before noticing your last post. Glad to see that you're one step ahead of me;) </edit>

Alex Morris
06-02-2003, 05:21 PM
thats OK..........your feedback is greatly appreciated!:beer:

Alex Morris
06-02-2003, 05:30 PM
I also tried a version using the avg_dlx50 extension which has a function splineweld which lets you input a weld threshold.

I'll have a look at condensing the selection process as part of doing the interface.

Alex Morris
06-02-2003, 05:35 PM
Eek I suddenly became a veteran!!:bounce:
And I'm still learning:thumbsup:

Alex Morris
06-04-2003, 02:45 PM
OK heres the latest interface........

How do I assign conditions in the main splinesort loop based on the state of the checkboxes in the interface? It looks like the states will get processed before theyre defined as it stands. Does the splinesort function need to be placed elsewhere in the script.

Also when I assign variables from spinners - whats the best way of doing this. Right now they only get assigned when the spinner changes - so I have to set defaults at the beginning of the script.
Can these be automatically set from the default states of the spinners?

I think that there may also be a problem with the release of variables, since I get unpredictable results when I run it twice on the same set of splines.......eg. a second pass to change the steps value if its not right the first time.

I left the image file out for now.
The idea was to create a suite of scripts later on.......how do you do this, so that they can easily be assigned to menus etc.

Phew..........thats a lot of questions!
Thanks again for all your help RH

Baldrick
06-04-2003, 03:01 PM
The best (programming practice) way to do it would be to pass them checkbox states as arguments to your sortsplines routine;


function sortsplines splines checkstate1 checkstate2 ... = (
)

sortsplines splines checkbox1.checked checkbox2.checked...


To avoid a long function definition you could define a struct to hold all the checkbox states;


struct checkstates = (
state1,
state2,
state3...
)

function sortsplines splines states = (
if (states.state1) then (
)
)

var states = checkstates state1:checkbox1.checked state2:checkbox2.checked....

sortsplines splines states

(Hope that wasn't too confusing an example there)


Alternatively you could declare your rollout as a global variable at the head of your script and then refer to the checkboxes directly from within the sortsplines function;


global sortspline

function sortsplines splines = (
if (sortspline.checkbox1.checked) then (
)
)

But global variables are a bit bad practice....

Alex Morris
06-04-2003, 03:11 PM
Thanks Baldrick.........thats a really cunning plan!
I'll need to read around it a bit in the help file first and then give them a try.

Baldrick
06-04-2003, 03:21 PM
No probs :)

I just realised that you asked more than one question...

Not sure about your variable clean up, I'll have to have a closer look at the code for that...

Creating a suite of utilities: Creating your routines as functions that can be called from a UI (as you're already doing) is the way to go. Then so long as your functions are in memory (load them at startup by placing them in scripts\startup) you can call them from anywhere you like - eg, a macroscript wrapper (so can give them icons or put them on quads), call them from the listener, or create your own switchboard UI to call them from - all perfectly possible. Take a look at something like the blurscripts package to see how they've packaged all their utilities together.

LFShade
06-04-2003, 08:50 PM
Rather than blabber on about things here, I decided to modify and comment your code. Hopefully it won't be too difficult to see what I've changed and why. There are more things that could be done differently, but as long as this works there wouldn't be much point to it. Just cosmetic stuff. As always, I had nothing to refer to and no way to test this, so it might complain a little over some minor syntax or spelling issues;)

Thanks, Baldrick, for adding your wisdom to the discussion. I can see that you're fairly new to posting here at CGTalk, but you obviously have some Maxscript savvy! I look forward to your input here:airguitar


RH

LFShade
06-04-2003, 08:52 PM
... and now the attachment:p

Alex Morris
06-05-2003, 09:57 AM
Wow RH thats what I call a rewrite!
I fixed the problem with the second structure statement and its lack of commas.

There seems to be a problem with it trying to restore sel and not finding it...............I'll search back through previous versions and try and work out where we lost it.

Also seem to be missing a select Objects button.........I thought it would be more intuitive to do the selection from the dialogue, and just filter shapes at that point. Although I can see what the filter function does and how it works with the current selection set.

Can you explain a bit more about instancing structures. I think I get what you did, but not why ;)) I'm a bit confused on how the values for the threshold and steps are passed back to the function from the spinners.

Alex Morris
06-05-2003, 11:11 AM
Just a thought........

How would you implement a progress bar to show, say, the % of objects passed by the script.

And as part of this.......echoing the fixed object name in the interface rather than in the listener.

Alex Morris
06-05-2003, 03:11 PM
Oh and another question..........
Do I need a separate struct to hold the functions and variables for each rollout if there is more than one, or can these be done once for a utility with several rollouts?

Was just trying to apply it to another script I was working on :))

LFShade
06-05-2003, 05:06 PM
You don't actually need a struct for it, I was just trying to be illustrative of Baldrick's idea. You could just put a suite of local variables in each rollout and get the same effect.

Feel free to add back the selection button. It shouldn't affect the operation of the execute button handler one way or the other, so it's your call. You might even devise a way of writing a similar filter function, but that fits the requirements for use with selectByName().

For the progress bar, just add a progressBar control to your UI, then update its .value property from within the main loop. This will mean modifying sortsplines() to take the rollout as a parameter, or to make the rollout explicitly global so that the function can refer to it. It will also mean normalizing the number of objects in the splines array to a 0-100 range.

Echoing the fixed object names to the dialog sounds possible, but I'd need to do some testing on my own to tell you how it would be done.

I've used structs in the rewrite in two different ways. One is just a warehouse for functions, that does not get instanced. I do this commonly so that I can use function names with reckless abandon, not worrying about whether they will conflict with built-in ones or those of other scripts. For instance, you could include a copy() function in a struct and it won't conflict with Maxscript's copy() function. The other type of struct I used is different in that it requires an instance to work. Any struct that uses member variables will need to be instanced in order to access them. This is because a structure definition is really just an abstraction. You can say that struct foo() has a variable width, but foo() itself doesn't actually have a width - only an instance of a foo can have a width. Structs in Maxscript are really quite similar to structs in the C language, if that helps at all.


RH

CGTalk Moderation
01-15-2006, 05:00 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.