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
 )
)