Wednesday 3 April 2013

Splay Normals

Foliage models often require edited normals. There are already normal-splaying scripts out there but I wanted to be able to set a position and use a strength value to effectively splay them partly from the centre of the "bush", and partly from the branch or trunk. I also wanted to be able to splay only the selected verts. This is the result.

USAGE
  • Position the working pivot
  • optionally select some of the verts (if none selected, will select all)
  • optionally set strength to a value like 0.4
  • run script



  
-- MixeScript snippet: SplayNormals
-- Splays selected normals away from the working pivot

--USAGE
-- Position the working pivot
-- select some or all of the verts (if none selected, will select all)
-- optionally set strength to a value like 0.4
-- run script
 
--SETTINGS
Strength = 1.0 -- when set to 1.0, entirely replaces existing normal direction. at 0.0, does not adjust normal at all.

max modify mode
obj = $
modPanel.setCurrentObject obj
enMods = (for m in obj.modifiers where classof m == editnormals collect m)
enMod = enMods[1]
 
subobjectLevel = 1 --verts 
vertarray = #{}
vertarray = polyop.getVertSelection obj
if vertarray.numberSet == 0 then -- no selection, select all
(
 polyop.setVertSelection obj #all
 vertarray = polyop.getVertSelection obj
)


if enMod == undefined then
(
 enMod = editnormals()
 modPanel.addModToSelection (enMod) ui:on
)

 
WPTM = WorkingPivot.getTM()
splayfrom = (WPTM[4])
modPanel.setCurrentObject obj

normarray = enMod.GetSelection()
--enMod.MakeExplicit ()
objpos = obj.pos

modPanel.setCurrentObject enMod
  
for vert in vertarray do (
 vertpos = (enMod.GetVertex vert node:obj) + objpos
 newnormangle = normalize (vertpos - splayfrom)
 tempbitarray = #{vert}
 norms = #{}
 enMod.ConvertVertexSelection tempbitarray norms
 enMod.MakeExplicit selection:norms
 for norm in norms do 
 (  
  existingnormangle = normalize (enMod.GetNormal (norm))
  finalangle = normalize ( newnormangle * Strength + existingnormangle * (1 - Strength))
  enMod.SetNormal norm finalangle
 )
)
  

Wednesday 27 March 2013

Fix Meshes

Lots of users experience issues with their meshes. This script provides a convenient way to do all of the common mesh-fixing steps.

USAGE
  • Select one or more geometry objects
  • run script
  • choose from any of the below buttons
    • Reset Xform - Resets selected objects XForms
    • Reset Normals - applies an edit normals modifier and resets all of the normals (so that they are again controlled by smoothing groups)
    • Reset Object - creates a new box object and attaches the existing mesh to it, then deletes the temporary box.. This essentially wipes all object properties. material, wire colour and pivot are all retained.
    • New Scene - exports the selected geometry, creates a new scene and imports it again. deletes the FBX file when done.
    
-- Mixescript snippet: Fix Objects
-- attempts to correct common problems with meshes

-- USAGE
-- select the bad meshes
-- run script
-- choose any of the buttons. I recommend working top to bottom
 
rollout rol_FixObject "Fix Object" width:104 height:168
(
 button btn_ResetXForm "Reset XForm" pos:[8,40] width:88 height:24
 button btn_ResetNormals "Reset Normals" pos:[8,72] width:88 height:24
 button btn_ResetObject "Reset Object" pos:[8,104] width:88 height:24
 button btn_NewScene "New Scene" pos:[8,136] width:88 height:24
 label lbl1 "Warning: will collapse stack" pos:[16,8] width:72 height:32
 
 on btn_ResetXForm pressed do
 (
  undo on 
  (
   objarray = getcurrentselection()
   for obj in objarray do 
   (
    ResetXform obj
    ConvertTo obj Editable_poly
   )
   select objarray
  )
 )
 
 on btn_ResetObject pressed do
 (
  undo on 
  (
   max modify mode
   objarray = getcurrentselection()
   newsel = #{}
   for i = 1 to objarray.count do 
   (
    obj = objarray[i]
    fixedobj = Box name:obj.name lengthsegs:1 widthsegs:1 heightsegs:1 length:1 width:1 height:1 mapcoords:on pos:[0,0,0] isSelected:off
    fixedobj.pivot = obj.position
    fixedobj.wirecolor = obj.wirecolor
    fixedobj.mat = obj.mat
    ConvertTo fixedobj Editable_poly
    polyop.attach fixedobj obj
    polyop.deleteverts fixedobj #{1..8} --delete original box
    objarray[i] = fixedobj
   )
   
   select objarray
  )
 )
 
 on btn_ResetNormals pressed do 
 (
  undo on 
  (
   max modify mode 
   objarray = getcurrentselection()
   for obj in objarray do 
   (
    ConvertTo obj Editable_poly
    select obj
    modPanel.setCurrentObject obj
    polyop.setVertSelection obj #all
    allverts = polyop.getVertSelection obj
    allnormals = #{}
    enMod = Edit_Normals ()
    modPanel.addModToSelection (enMod) ui:on
    enMod.ConvertVertexSelection allverts allnormals
    enMod.Reset selection:allnormals
    ConvertTo obj Editable_poly
   )
   select objarray
  )
 )
 
 
 on btn_NewScene pressed do 
 (
  --checks
  cancel = false
  if selection.count == 0 then (messagebox ("please select one or more objects"); cancel = true)
  if maxfilepath == "" then (messagebox ("please save your scene first."); cancel = true)
  
  if cancel == false then (
   ContinueScript = queryBox "This will create a new scene and only copy across geometry. Unsaved changes will be lost. Continue?"
   if ContinueScript then
   (
    exportpath = maxfilepath + maxfilename + ".FBX"
    exportFile exportpath #noPrompt selectedOnly:true
    actionMan.executeAction 0 "16" --new scene
    --actionMan.executeAction 0 "40005" -- reset scene -switch this out for above line if you want to keep materials etc. this is essentially less aggressive.
    importFile exportpath #noPrompt
    deleteFile exportpath
   )
  )
 )
 
)

CreateDialog rol_FixObject NewDlg
  

Monday 25 March 2013

Snap to nearest gridpoint

By request, a script to move selected vertices to the nearest gridpoint. I also threw in snapping selected objects to nearest gridpoint.




Usage
  • Select verts of an editable poly or select objects
  • run script
  • verts/objects will be at nearest gridpoints. (note: uses objects pivot)
  
-- MixeScript snippet: Snap to Nearest Grid Point
-- Snaps selected verts or objects to nearest grid point

--USAGE
-- Select verts of an editable poly or select objects
-- run script
-- choose button to snap to
-- verts/objects will be at nearest gridpoints. (note: uses objects pivot)

fn findnearestgridpoint frompoint axis = (
 spacing = getGridSpacing()
 
 nearestx = frompoint.x
 nearesty = frompoint.y
 nearestz = frompoint.z
 
 nearestx = (floor (frompoint.x / spacing + 0.5)) * spacing
 nearesty = (floor (frompoint.y / spacing + 0.5)) * spacing
 nearestz = (floor (frompoint.z / spacing + 0.5)) * spacing
 
 if axis == "x" then ( nearesty = frompoint.y; nearestz = frompoint.z)
 if axis == "y" then ( nearestx = frompoint.x; nearestz = frompoint.z)
 if axis == "z" then ( nearesty = frompoint.y; nearestx = frompoint.x)
  
 return [nearestx,nearesty,nearestz]
)

fn DoSnap axis = 
(
 if subobjectlevel == 1 then 
 ( 
   obj = $
   vertselection = polyop.getVertSelection obj
  max modify mode
  for vert in vertselection do (
   polyop.setVert obj vert (findnearestgridpoint (polyop.getVert obj vert) axis)
  )
 ) else (
  objarray = getcurrentselection()
  for obj in objarray do (
   obj.pos = findnearestgridpoint obj.pos axis
  )
 )
)

rollout rol_SnapToGrid "Snap To Grid Points" width:136 height:48
(
 button snapX "X" pos:[8,24] width:24 height:19
 label lbl1 "Snap to Grid Points" pos:[24,5] width:96 height:16
 button snapY "Y" pos:[40,24] width:24 height:19
 button snapZ "Z" pos:[72,24] width:24 height:19
 button SnapAll "All" pos:[104,24] width:24 height:19
 
 on snapX pressed do (undo on (DoSnap "x"))
 on snapY pressed do (undo on (DoSnap "y"))
 on snapZ pressed do (undo on (DoSnap "z"))
 on SnapAll pressed do (undo on (DoSnap "all"))
)

createDialog rol_SnapToGrid dlg_SnapToGrid
  

Sunday 24 March 2013

MixeScript Roundup 1

Thought I'd show in video what the scripts posted up to now do. Also I've whacked them all in a zip if you want to use them as buttons in your interface.







Colour Objects (by random/by position)

I needed a script that would give a random colour to my leaves and also give a colour to them based on height so that I could use this to make the leaves at the back disappear first in autumn.

For the sake of functionality, I gave it three purposes:

Usage:
  • select many objects
  • adjust settings between height by colour / random colour / random greyscale
  • run script
Update:
  • I needed this to give an even distribution of colours, so introduced a new method which sorts based on position, then assigns a colour based on sort order. slightly long-winded code, sorry!
  
  
-- MixeScript snippet: Colour Objects (random/by pos)
-- Removes material from objects and applies a random colour, or colour based on their position.

--USAGE
-- select many objects
-- adjust settings between height by colour / random colour / random greyscale
-- run script

--SETTINGS
useposcolour = true --if false, uses a random colour.
greyscale = false -- if true then r=g=b (grey tones)
sortbasedcolouring = false -- if true, sorts objects then colours based on sort order. ensures even distribution of colours and no matching colours.
 
 
if sortbasedcolouring == false then
(
 minx = $.min.x
 maxx = $.max.x
 xdifference = maxx - minx
 miny = $.min.y
 maxy = $.max.y
 ydifference = maxy - miny
 minz = $.min.z
 maxz = $.max.z
 zdifference = maxz - minz
 sort 

 for o in $ do (
  o.mat = undefined
  
  xcolour = (o.center.x - minx) / xdifference * 255
  ycolour = (o.center.y - miny) / ydifference * 255
  zcolour = (o.center.z - minz) / zdifference * 255 
  
  if useposcolour then (
   r = zcolour ;  g = zcolour ;  b = zcolour
  ) else (
   if greyscale then (
    r = random 0 255 ;  g = r ;  b = r
   ) else (
    r = random 0 255 ;  g = random 0 255 ;  b = random 0 255
   )
  )
  
  o.wirecolor = [r,g,b]
 )
)
else 
(
 
 mysel = getcurrentselection()
  
 fn CompareX newobj prevobj =
 ( 
  case of
  (
   (newobj.center.x >  prevobj.center.x): 1
   (newobj.center.x <  prevobj.center.x): -1
   default:0
  )
 )
  
 fn CompareY newobj prevobj =
 ( 
  case of
  (
   (newobj.center.y >  prevobj.center.y): 1
   (newobj.center.y <  prevobj.center.y): -1
   default:0
  )
 )

 fn CompareZ newobj prevobj =
 ( 
  case of
  (
   (newobj.center.z >  prevobj.center.z): 1
   (newobj.center.z <  prevobj.center.z): -1
   default:0
  )
 )

 selsize = mysel.count
 --print ("SELSIZE = " + (selsize as string))

 qsort mysel CompareX
 for i = 1 to selsize do ( 
  newcolour = (((i-1.0) / (selsize-1.0) * 255) as string)
  mysel[i].wirecolor = [(newcolour as integer),mysel[i].wirecolor.g,mysel[i].wirecolor.b]
  --print ("XVALUE: i = " + (i as string) + " , colour = " + (mysel[i].wirecolor as string))
 )

 qsort mysel CompareY
 for i = 1 to selsize do (
  newcolour = (((i-1.0) / (selsize-1.0) * 255) as string)
  mysel[i].wirecolor = [mysel[i].wirecolor.r,(newcolour as integer),mysel[i].wirecolor.b]
  --print ("YVALUE: i = " + (i as string) + " , colour = " + (mysel[i].wirecolor as string))
 )

 qsort mysel CompareZ
 for i = 1 to selsize do (
  newcolour = (((i-1.0) / (selsize-1.0) * 255) as string)
  mysel[i].wirecolor = [mysel[i].wirecolor.r,mysel[i].wirecolor.g,(newcolour as integer)]
  --print ("ZVALUE: i = " + (i as string) + " , colour = " + (mysel[i].wirecolor as string))
 )

 --optionally, use this to greyscale.
 --for o in mysel do (o.wirecolor = [o.wirecolor.b,o.wirecolor.b,o.wirecolor.b])
)
  

Friday 22 March 2013

Fix Intersections

A common problem when using things like object paint and scatter are intersecting objects - many of your objects cutting through one another.

USAGE
  • select many objects
  • adjust intersection aggressiveness setting (between 0.1 and 1.0)
  • run script
  • you are left with a selection
  • delete the selection, or rotate or scale them or whatever fix is most appropriate.
LIMITATIONS
  • Intersection detection is simple bounding box intersection. Therefore does not work particularly well if bounding box is not a good fit for the object. If I have cause, I'll introduce a sphere/squashed-sphere based intersection detection at a later date.



  
-- MixeScript snippet: Select Intersections
-- selects objects which intersect one another by performing overlapping bounding box detection. optional adjustment for bounding box downscaling.

--USAGE
--select many objects
--adjust intersection aggressiveness setting (between 0.1 and 1.0)
--run script
--you are left with a selection
--delete the selection, or rotate or scale them or whatever fix is most appropriate.

--SETTINGS
IntersectionAggressiveness = 0.5 -- downscales objects to center. choose between 0.1 and 1.0 (1.0 uses bounding boxes exactly as they are)


Global IsIntersecting = #()
Global PivotPositions = #()
Global IntersectingObjects = #()

fn DownScale objarray = (
 if IntersectionAggressiveness != 1.0 then (
  for o in objarray do (
   append PivotPositions o.position
   o.pivot = o.center
   o.scale = o.scale * [IntersectionAggressiveness,IntersectionAggressiveness,IntersectionAggressiveness]
  )
 )
)

fn UpScale objarray = (
 i = 0
 if IntersectionAggressiveness != 1.0 then (
  for o in objarray do (
   i=i+1
   o.scale = o.scale / [IntersectionAggressiveness,IntersectionAggressiveness,IntersectionAggressiveness]
   o.pivot = PivotPositions[i]
  )
 )
 Global PivotPositions = #()
)


objarray = getcurrentselection()
DownScale(objarray)

--Detect intersections
for i = 1 to objarray.count do (
 doesintersect = false
 for y = 1 to objarray.count do ( 
  if y != i and IsIntersecting[y] == false then --ignore same object and objects already detected as intersecting
  (
   if (intersects objarray[i] objarray[y]) == true then (doesintersect = true)
  )
 )
 append IsIntersecting doesintersect
 if doesintersect then (append IntersectingObjects objarray[i])
)

UpScale(objarray)

select IntersectingObjects
  

Monday 18 March 2013

Adjust Spread (between objects)

If you want to increase or reduce the distance between objects.


Usage
  • select several objects
  • run script
  • adjust slider amount
    • values greater than 0 increase spread
    • values less than 0 reduce spread (bunching objects)
  • close dialogue
Limitations
  • Does not take kindly to you changing the selection while the dialogue is still open
  • Does not count as an "undo step" (but setting adjust to 0 puts them back where you started)
Updates
  • introduced axis choice (X, Y, Z, X and Y or All)
  • added an undo point on running the script
  • introduced selection lock and other measures to reduce chance of "user error"
   
  
-- MixeScript snippet: Adjust Spread
-- adjusts distance between objects using the center position of those objects

--USAGE
-- select several objects
-- run script
-- adjust slider amount
 --values greater than 0 increase spread
 --values less than 0 reduce spread (bunching objects)
-- close script window
 
--LIMITATIONS
--Does not take kindly to you changing the selection while the dialogue is still open
--Does not count as an "undo step" (but setting adjust to 0 puts them back where you started)

 
max spacebar -- lock selection
 
with undo on (print "undo point")
 
centerpos = selection.center
objarray = getcurrentselection()
objpositions = #()
for obj in objarray do (append objpositions (obj.position - centerpos))

rollout Spreader "Spreader" width:100 height:130
(
  spinner spn_spread "Spread" pos:[0,11] width:90 height:16 range:[-100,100,0] scale:0.01 
 radioButtons rdo_axis "" pos:[7,40] width:40 height:64 labels:#("X", "Y", "Z", "X and Y", "All") default:5

  on spn_spread changed amt do 
  (
    for i = 1 to objarray.count do
    (
     if rdo_axis.state == 1 then ( --x
    objarray[i].position = [centerpos.x + (objpositions[i].x * (1+amt)),objarray[i].position.y,objarray[i].position.z]
     )
     if rdo_axis.state == 2 then ( --y
    objarray[i].position = [objarray[i].position.x,centerpos.y + (objpositions[i].y * (1+amt)),objarray[i].position.z]
     )
     if rdo_axis.state == 3 then ( --z
    objarray[i].position = [objarray[i].position.x,objarray[i].position.y,centerpos.z + (objpositions[i].z * (1+amt))]
     )
     if rdo_axis.state == 4 then ( --x & y
    objarray[i].position = [centerpos.x + (objpositions[i].x * (1+amt)),centerpos.y + (objpositions[i].y * (1+amt)),objarray[i].position.z]
     )
     if rdo_axis.state == 5 then ( --all
    objarray[i].position = centerpos + (objpositions[i] * (1+amt))
     )
   )
  )
  
  on rdo_axis changed changedto do 
  (
   spn_spread.value = 0.0
     for i = 1 to objarray.count do
   (objarray[i].position = objpositions[i] + centerpos)
   
  )
  
  on Spreader close do 
 (
  max spacebar --unlock selection
 )
 
) --end of rollout

if selection.count == 0 then 
(messagebox "please make a selection first.") 
else 
(
 undo on (createDialog Spreader dlg_Spreader)
)
  

Saturday 16 March 2013

Toggle Viewport Style

I typically use two viewport settings: 1 default light with no shadows/AO, and scene lights with shadows/AO. And I always disable gamma/LUT correction. This script helps me set that up really quickly, and switch between the two.



  
  
-- MixeScript snippet: Toggle ViewStyle
-- toggles between default lights, no shadows and scene lights. also disables gamma/LUT and hides/shows cameras and lights.

--USAGE
-- run script to toggle between the two viewport styles

--SETTINGS
DisableGammaLUTCorrection = true --if you want to use Gamma/LUT correction, set this to false

if NitrousGraphicsManager.IsEnabled() == false then (
 messagebox("Please change your renderer to Nitrous and restart Max if you wish to use the Viewport Setups")
) else (

 actionMan.executeAction -844228238 "1"  -- Viewport Lighting and Shadows: Viewport Visual Style Realistic
 if DisableGammaLUTCorrection then (IDisplayGamma.colorCorrectionMode = #none)
 disp = NitrousGraphicsManager.GetActiveViewportSetting() 
 
 if disp.LightOption != #SceneLight then (
  disp.LightOption = #SceneLight
  disp.ShadowsEnabled = true
  disp.AmbientOcclusionEnabled = true
  hideByCategory.lights = false
  hideByCategory.cameras = false
 ) else (
  disp.LightOption = #DefaultLight
  disp.DefaultLightMode = #OneLight
  disp.ShadowsEnabled = false
  disp.AmbientOcclusionEnabled = false
  hideByCategory.lights = true
  hideByCategory.cameras = true
 )
)
  

Attach Selected

Simple one for attaching objects together based on your selection.


  
-- MixeScript snippet: Attach Selected
-- Attaches selected objewcts to the first object which was selected

--USAGE
-- select an object (the object to attach TO)
-- select more object(s)
-- run script
-- objects now attached to the first object selected

attachtoobj = selection[1]
macros.run "Modifier Stack" "Convert_to_Poly"
objarray = getcurrentselection()
for obj in objarray do ( attachtoobj.EditablePoly.attach obj attachtoobj )
  

Friday 15 March 2013

Select Similar

A student had scattered many duplicates of two types of objects and attached them together to one editable poly. He needed to select type A, which had a different number of faces to type B.

That was my starting point for this script, then it occurred to me I could increase usability by seeking to replace Max's "select similar" with a more intelligent select similar script. I may have got a bit carried away... I ended up with the following functionality:

  • When selecting element sub-objects
    • will select other elements with the same number of faces
  • When selecting geometry at the object-level, selects any/all of:
    • matching size (good for chairs)
    • matching ratio (good for uniformly scaled objects)
    • same number of faces (good when above won't work)
    • instances 
    • object colour (off by defualt)
    • max's delect similar (off by defualt)
  • When selecting non-geometry
    • matching class (ex all omni lights or all point helpers or bones, etc)



  
  
-- MixeScript snippet: Select Similar
-- selects other elements or objects similar to the current selection

--USAGE
-- select an object or an element of an editable poly
-- check settings below
-- run script
-- new selection is made


--SETTINGS
FindBySimilarSize = true
FindBySimilarRatio = true
SimilarityTolerance = 0.005 -- higher tolerance selects more objects which are not quite so similar. must be less than 1.
FindByMatchingFaceCount = true
FindByInstances = true
FindByMaxSelectSimilar = false -- false by defualt because this gets lots of stuff which isnt necessarily similar.
FindByWireColor = false -- false by default since this may find lots of stuff
DoNotSelectHidden = true

(
 obj = selection[1]
 if subobjectlevel == 4 or subobjectlevel == 5 then
 (
  print "select matching elements"
  selectedfaces = polyop.getFaceSelection obj
  facecount = selectedfaces.numberset
  allfaces = obj.faces
  newselection = polyop.getFaceSelection obj
  discardedfaces = #{}
  
  for face in allfaces do (
   if (newselection[face.index] == false and discardedfaces[face.index] == false) then 
   (
    -- we havent seen this face before.
    thiselement = polyop.getElementsUsingFace obj face
    if thiselement.numberset == facecount then (
     --print "found a matching element"
     join newselection thiselement
    ) else (
     join discardedfaces thiselement
    )
   )
  )
  
  polyop.setfaceselection obj newselection
  redrawviews()
 )
 else
 (
  matchingobjs = #()
  append matchingobjs obj
  print "select matching objects"
  if superclassof(obj) == GeometryClass and classof(obj) != Biped_Object then
  (
   --find geometry with matching bounding size OR face count
   
   if FindBySimilarSize or FindBySimilarRatio then (
     originalrotation = obj.rotation
     originalposition = obj.position
     obj.rotation = (quat 0 0 0 0)

    XDimension = (obj.max.x - obj.min.x)
    YDimension = (obj.max.y - obj.min.y)
    ZDimension = (obj.max.z - obj.min.z)
    
    XZRatio = XDimension / ZDimension
    YZRatio = YDimension / ZDimension
    
    obj.rotation = originalrotation
    obj.position = originalposition
    
    max modify mode
    
    for checkobj in geometry do (
     originalrotation = checkobj.rotation
     originalposition = checkobj.position
     checkobj.rotation = (quat 0 0 0 0)
     
     if FindBySimilarSize then
     (
      if XDimension > ((checkobj.max.x - checkobj.min.x) * (1.0 - SimilarityTolerance)) and XDimension < ((checkobj.max.x - checkobj.min.x) * (1 / (1.0 - SimilarityTolerance))) and
       YDimension > ((checkobj.max.y - checkobj.min.y) * (1.0 - SimilarityTolerance)) and YDimension < ((checkobj.max.y - checkobj.min.y) * (1 / (1.0 - SimilarityTolerance))) and
       ZDimension > ((checkobj.max.z - checkobj.min.z) * (1.0 - SimilarityTolerance)) and ZDimension < ((checkobj.max.z - checkobj.min.z) * (1 / (1.0 - SimilarityTolerance)))  then
      ( 
       print (checkobj.name + " is a similarsize match")
       appendIfUnique matchingobjs checkobj
      )
     )
     
     if FindBySimilarRatio then 
     (
      checkXZRatio = (checkobj.max.x - checkobj.min.x) / (checkobj.max.z - checkobj.min.z)
      checkYZRatio = (checkobj.max.y - checkobj.min.y) / (checkobj.max.z - checkobj.min.z)
      
      if XZRatio > (checkXZRatio * (1.0 - SimilarityTolerance)) and XZRatio < (checkXZRatio * (1 / (1.0 - SimilarityTolerance))) and
       YZRatio > (checkYZRatio * (1.0 - SimilarityTolerance)) and YZRatio < (checkYZRatio * (1 / (1.0 - SimilarityTolerance))) then
      ( 
       print (checkobj.name + " is a similarratio match")
       appendIfUnique matchingobjs checkobj
      )
     )
     
     
     checkobj.rotation = originalrotation
     checkobj.position = originalposition
    )
   )
   

   if FindByMatchingFaceCount and ((classof obj) == editable_poly or (classof obj) == editable_mesh) then
   (
    for eachobj in geometry do
    (
     if classof eachobj == editable_poly or classof eachobj == editable_mesh then
     (
      if obj.faces.count == eachobj.faces.count then
      (
        appendIfUnique matchingobjs eachobj
      )
     )
    )
   )
   
   if FindByWireColor then
   (
    for eachobj in geometry do
    (
     if eachobj.wirecolor == obj.wirecolor then
     (
      appendIfUnique matchingobjs eachobj
     )
    )
   )
   
  )
  else
  (
   --select all objects matching class
   for eachobj in $* do 
   (
    if classof eachobj == classof(obj) then
    (
     appendIfUnique matchingobjs eachobj
    )
   )
  )
  
  clearselection()
  
  if FindByMaxSelectSimilar then
  (
   actionMan.executeAction 0 "40099"  -- Selection: Select Similar
  )

  if FindByInstances then
  (
   InstanceMgr.GetInstances obj &instances
   selectmore instances
  )
  
  selectmore matchingobjs
  
  
  if DoNotSelectHidden == true then
  (
   currentselection = getcurrentselection()
   clearselection()
   for eachobj in currentselection do
   (
    if eachobj.ishidden == false then (selectmore eachobj)
   )
  )
  
 )
)
  

Wednesday 13 March 2013

Box-based Spheres

So you primitive sphere has nasty pinching and wasteful geometry... and while your geosphere may be more efficient, it's not very nice for unwrapping. So I prefer to use box-based spheres. Quadded, with 8 tripoles, and unwrapped neatly into six square segments, they're generally nicer to work with. But they're a bit of a fiddle to create, using a box, turbosmooth and spherify modifier. So I made a script that makes them for you, replacing an existing sphere...

SCENARIO
  • You want to make box-based spheres easily
USAGE
  • create a sphere the conventional way and select it
  • run script
  • a new box-based sphere is created at the object's location
  • if DeleteObject setting is true, the old object will be deleted
  • adjust box segments and turbosmooth iterations as desired


  
  
-- USAGE
-- create a sphere the conventional way and select it
-- run script
-- a new box-based sphere is created at the object's location
-- if DeleteObject setting is true, the old object will be deleted
-- adjust box segments and turbosmooth iterations as desired

-- SETTINGS
DeleteObject = true -- should the selected object(s) be deleted?

objarray = getcurrentselection()
max modify mode

for obj in objarray do (
 -- get size
 lengthx = (obj.max.x - obj.min.x)
 lengthy = (obj.max.y - obj.min.y)
 lengthz = (obj.max.z - obj.min.z)
 size = (lengthx + lengthy + lengthz) / 3 * 1.18
 
 newsphere = Box lengthsegs:1 widthsegs:1 heightsegs:1 length:size width:size height:size mapcoords:on pos:[obj.center.x,obj.center.y,(obj.center.z - (size / 2))] isSelected:on
 newsphere.pivot = newsphere.center
 select newsphere
 TS = TurboSmooth ()
 TS.iterations = 3
 modPanel.addModToSelection (TS) ui:on
 modPanel.addModToSelection (Spherify ()) ui:on
 newsphere.wirecolor = obj.wirecolor
 
 if DeleteObject then (delete obj)
)
  

Convert to Editable Poly and Reset XForm

A little time-saver for resetting Xform and collapsing the stack.

Scenario
  • You often convert to editable poly (ex. primitives)
  • You often collapse the stack
  • You often reset Xform
  • You want a button to do any/all the above!
Usage
  • select one or more objects
  • run script
  • objects will be collapsed to editable poly
  • if DoResetXform is true, objects will have their XForms reset.


  
-- MixeScript snippet: Convert to Editable Poly / Reset XForm 
-- little time-saver for resetting Xform and collapsing stack.

--USAGE
-- select one or more objects
-- run script
-- objects will be collapsed to editable poly
-- if DoResetXform is true, objects will have their XForms reset.

--settings
DoResetXform = true

objarray = getCurrentSelection()
for obj in objarray do (
 select obj
 if DoResetXform then (ResetXForm obj)
 macros.run "Modifier Stack" "Convert_to_Poly"
)
  

Center Pivot (or bottom-center)

99% of all obects I make I want the pivot to be at object-center or object-bottom-center (min z). I got tired of using the transform toolbox to do this, so here we are...

Scenario
  • You want to center (or bottom-center) the pivot of one or more objects
  • A button to do this would be splendid
Usage
  • Select one or more objects
  • run script once to center pivot
  • run again if desired to bottom-center

  
-- MixeScript snippet: Center Pivot 
-- Centers pivot. if run again, will bottom-center pivot.

--USAGE
-- select one or more objects
-- run script
-- centers pivot, or if already centered, places at min z

for obj in selection do
if obj.pivot != obj.center then (
 obj.pivot = obj.center
) else (
 obj.pivot = obj.center
 obj.pivot.z = obj.min.z
)
redrawviews()
  

Wednesday 6 March 2013

Flatten unwrap selected objects (for UDK lightmaps)

(credit to this guy for my starting point)

Scenario
  • You need some quick-n-dirty unwraps for lightmap baking
  • You need channel 2 unwrapped uniquely
  • You will improve them later
Usage
  • Select one or more objects
  • Run script
  • Script applies a flatten-map unwrap to channel 2
  • Warning: scripts collapses objects down to editable poly
  
-- MixeScript snippet: Flatten unwrap selected objects 
-- applies a flatten-map unwrap to selected objects, then collapses them down to editable poly.

--USAGE
-- Select one or more objects
-- Run script
-- Script applies a flatten-map unwrap to channel 2
-- Warning: scripts collapses objects down to editable poly

-- SETTINGS
UnwrapChannel = 2
 
objarray = getCurrentSelection()
max modify mode    
  
for obj in objarray do 
(
 convertto obj editable_mesh
    modPanel.setCurrentObject obj
    subObjectLevel = 4
    UnwrapMod = Unwrap_UVW name:"UnwrapMod"
    UnwrapMod.setApplyToWholeObject true
    addModifier obj UnwrapMod
    Temp = obj.modifiers["UnwrapMod"]
    Temp.setMapChannel UnwrapChannel
    Temp.setTVSubObjectMode 3
    Temp.flattenMap 45.0 #() 0.01 true 0 true true     
    subObjectLevel = 0
 UnwrapMod.packnoparams()
 UnwrapMod.pack 1 0.02 true true true
 convertto obj editable_poly 
)
select objarray
  

Saturday 2 March 2013

Make Planar per face

Scenario:
  • you have skewed faces across your geometry
  • you want to perform a make planar operation for each face
Usage:
  • select one or more objects or faces
  • run script
  • faces are made planar
Limitations:
  • Will not work on "edit poly" modifier. needs to be a collapsed editable poly.

  
-- MixeScript snippet: make planar per face
-- performs the make planar operation on each face of the selection

--USAGE
-- select one or more objects or faces
-- run script
-- faces are made planar

-- SETTINGS
numrepeats = 3

--if face selection mode
if subobjectLevel == 4 then 
(
 --only on selected faces
 obj = $
 thisfaceselection = polyop.getFaceSelection obj
 
 for i=1 to numrepeats do 
 (
  for eachface in thisfaceselection do ( polyop.makeFacesPlanar obj #(eachface))
 )
)

else 
(
 --all faces
 objarray = getcurrentselection() 
 
 for obj in objarray do 
 (
  for repeats=1 to numrepeats do 
  (
   for i=1 to (polyop.getNumFaces obj)do(polyop.makeFacesPlanar obj #(i))
  )
 )
)
RedrawViews()
 

Bounding Box Dimensions

Scenario
  • you want to measure an object's dimensions
  • the object is rotated
  • the Measure utility does not provide consistent dimensions for rotated objects :(
Usage
  • select an object
  • run script
  • message box is shown, displaying dimensions
  
  
-- MixeScript snippet: bounding box dimensions
-- displays a message box giving the selected object(s) dimensions

--USAGE
-- select an object
-- run script
-- message box is shown, displaying dimensions

if selection.count == 1 then 
(
 originalrotation = $.rotation
 originalposition = $.position
 $.rotation = (quat 0 0 0 0)
)

XDimension = ($.max.x - $.min.x)
YDimension = ($.max.y - $.min.y)
ZDimension = ($.max.z - $.min.z)
XYDimension = ((XDimension * XDimension) + (YDimension * YDimension)) ^ 0.5
XYZDimension = ((XYDimension * XYDimension) + (ZDimension * ZDimension)) ^ 0.5
XYArea = XDimension * YDimension
Volume = XDimension * YDimension * ZDimension

XString = "X = " + (XDimension as string)
YString = "Y = " + (YDimension as string)
ZString = "Z = " + (ZDimension as string)
XYString = "XY = " + (XYDimension as string)
XYZString = "XYZ = " + (XYZDimension as string)
XYAreaString = "XY Area = " + (XYArea as string)
VolumeString = "Volume = " + (Volume as string)

messagestring = "Bounding Box Dimensions: \n \n" + XString +  "\n" + YString +  "\n" + ZString +  "\n \n" + XYString +  "\n" + XYZString + "\n \n" + XYAreaString + "\n" + VolumeString

if selection.count == 1 then 
(
 $.rotation = originalrotation
 $.position = originalposition
)

messagebox(messagestring)
  

Export Selection Individually (mostly for UDK)

Scenario:
  • Many objects in scene
  • Each object needs to be exported as FBX separately
  • Each object is named
Usage:
  • name your objects
  • select one or more geometry objects
  • run script
  • fbx files will be found in directory of max file in the folder "fbx"

Update: now supports one collision object per model, prefixed "UCX_"
Update: now exports with smoothing groups by default
Update: plays nicer with groups (ignoring the contents of groups and only exporting the group itself)
  
   
-- Mixescript snippet: export selection individually
-- exports each object in your selection on its own.

-- USAGE
-- name your objects
-- select one or more geometry objects
-- run script
-- fbx files will be found in directory of max file in the folder "fbx"

--SETTINGS
ShowCompletedMessage = True

if selection.count == 0 then 
  (
  messagebox("please select an object first.")
 )
else 
 (
  filepath = maxfilepath + "fbx\\"
  if doesFileExist filepath == false then (
   --messagebox("making new folder")
   makedir filepath
  )
  FBXExporterSetParam "SmoothingGroups" true
  
  objarray = getCurrentSelection()
  exportedcount = 0
  
  for obj in objarray do 
  (
   if (matchpattern obj.name pattern:"UCX*" == false) then
   (
    if (matchpattern obj.name pattern:"*-*") then
   (messagebox("object name may not contain hyphens"))
    else 
    (
     if isGroupMember obj == false then 
   (
    --Get original position and move to world zero
    originallocation = obj.pos
    obj.pos = [0,0,0]

    -- Reset xform
     ResetXForm selection
    macros.run "Modifier Stack" "Convert_to_Poly"

    -- Export
    select obj
    execute("colobj = $UCX_" + obj.name)
    if colobj != undefined then 
    (
     coloriginallocation = colobj.pos
     colobj.pos = [0,0,0]
     selectmore colobj
    )
    
    exportpath = maxfilepath + "fbx\\" + obj.name + ".FBX"
    redrawViews()  
    exportFile exportpath #noPrompt selectedOnly:true
    --alternatively, bring up dialogue to ensure settings
    --actionMan.executeAction 0 "40373"  -- File: Export Selected
    print ("exported " + obj.name)

     -- Restore to original position
    obj.pos = originallocation
    if colobj != undefined then 
    (
     colobj.pos = coloriginallocation
    )
    exportedcount += 1
   )
  )
 )
 )


  select objarray  
  redrawViews()  
  if ShowCompletedMessage then (messagebox("exported " + (exportedcount as string) + " objects successfully to " + maxfilepath + "fbx\\"))
 )