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