Home Technical Talk

[MAXScript] Object Transform Matrix3

interpolator
Offline / Send Message
Revel interpolator
Hello guys, I've been trying to wrote a script about scaling an object to specified target length, it's based of a "Scale To" script by Walid Abou Ali over at Scriptspot. Wanted to improve it to support multiple selected objects.

Here is the snippet of what I have right now;
(
    target = 10
    
    xlength = selection.max.x - selection.min.x
    ylength = selection.max.y - selection.min.y
    zlength = selection.max.z - selection.min.z
    
    ratio = target/xlength
    
    xPos = selection.center.x*ratio
    yPos = selection.center.y*ratio
    zPos = selection.min.z*ratio
    
    for obj in selection do
    (
        obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [xPos, yPos, zPos])
    )
)
.. so what I have currently works fine with a single selected object, but I'm having a headache with the matrix3 row4 of the object transformation to set the center of the scaling translation.

My question is why wont it use the center of selection for the translation even though I put selection.center.x there? It did however scale to the correct size as specified on the "target", but the center of scaling seems like on a random position (well..I'm sure it wasn't a random number, but I just couldn't make up where is the number coming from).

Replies

  • monster
    Options
    Offline / Send Message
    monster polycounter
    Maybe I'm not understanding you, but you are multiplying the center by ratio. I just commented that out on those line and it's scaling from the XY center of the selection.
     xPos = selection.center.x --*ratio
     yPos = selection.center.y --*ratio
     zPos = selection.min.z*ratio
    
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    Eh, really? that's not the case on my end though..try to make a 10x10x10 box and put it at x=10 y=0 z=0, run the script, since the box dimension is 10 and the target dimension also 10, the box wont scale but it move from x=10 to x=20 to x=40 etc each time you run the script.

    By right it shouldn't be moved since the scale is the same and the selection center is also the same (because it's only 1 object in selection). The same thing happen when I select 2 box, it jumps to some other location when I run the script.
  • monster
    Options
    Offline / Send Message
    monster polycounter
    I see, I only tested multiple objects, and it was working on the setup I tested.

    The reason the box moves from 10 to 20 is because you are multiplying the center position by it's current position.

    The first solution off the top of my head is to just scale the object and handle the position afterward.

    On a side note, I never realized the selection center gizmo in Max is weighted by object locations. But selection.center is the actual center you expect.
    (
    	fn lerp minVal maxVal term = (maxVal - minVal) * term + minVal
    	
        target = 10
        
        xlength = selection.max.x - selection.min.x
        ylength = selection.max.y - selection.min.y
        zlength = selection.max.z - selection.min.z
        
        ratio = target/xlength
        
        xPos = selection.center.x--*ratio
        yPos = selection.center.y--*ratio
        zPos = selection.min.z*ratio
        
        for obj in selection do
        (
    		tempPos = obj.pos
    		obj.pos = [0,0,0]
            obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [0,0,0])
    		obj.pos =  lerp [xPos, yPos, zPos] tempPos ratio
        )
    )
    
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    Damn nice man, thanks! never thought of doing it that way :)
    Still not fully understand the math solution you have on the fn, will look into it more next time, but for now it's all works as expected!

    Well thought I just posted here the complete version of the script that I have for now, probably anyone will benefit from it as well, or might even add some idea or improvements;
    macroScript ScaleTo
    category:"_Modification"
    tooltip:"Scale To"
    buttontext:"Scale To"
    (
        local xlength
        local ylength
        local zlength
        local target
        local ratio
        
        try (destroydialog STrollout) catch()
        rollout STrollout "Scale To"
        (
            group "Reference Axis"
            (
                radiobuttons rdo_axis
                    labels:#("X Coordinate", "Y Coordinate", "Z Coordinate")
            )
            
            label lbl_spn_scaleTo "Scale To" pos:[10,80]
            label lbl_mm "(mm)" pos:[lbl_spn_scaleTo.pos[1]+45, lbl_spn_scaleTo.pos[2]]
            spinner spn_scaleTo fieldWidth:60 height:18 range:[0,1e+006,25] pos:[lbl_mm.pos[1]+27, lbl_spn_scaleTo.pos[2]]
            
            checkbox chk_groupScale "Group Scale" pos:[10, spn_scaleTo.pos[2]+20] checked:false
            checkbox chk_xform "Reset Xform" pos:[10, chk_groupScale.pos[2]+20] checked:true
            button btn_apply "Apply" width:150 height:18
            
            on STrollout open do
            (    
                units.displaytype = #Metric
                MT = units.MetricType
                case MT of
                (
                    (#Millimeters):(lbl_mm.text = "(mm)")
                    (#Centimeters):(lbl_mm.text = "(cm)")
                    (#Meters):(lbl_mm.text = "(m)")
                    (#Kilometers):(lbl_mm.text = "(km)")
                )
                max utility mode
                UtilityPanel.OpenUtility Measure
            )
            on btn_apply pressed do
            (
                try
                (
                    -- isolate the grouphead and non group object
                    filtervalidobj = #()
                    for obj in selection do
                    (
                        result_ghead = isGroupHead obj
                        result_gmember = isGroupMember obj
                        
                        if result_ghead == true and result_gmember == false then ( append filtervalidobj obj )
                        if result_gmember == false then (appendIfUnique filtervalidobj obj )
                    )
                ) catch()
                
                if    selection.count != 0 and
                    spn_scaleTo.value != 0 and
                    filtervalidobj.count != 0 then
                (
                    target = spn_scaleTo.value
                    if chk_groupScale.state then
                    (
                        xlength = selection.max.x - selection.min.x
                        ylength = selection.max.y - selection.min.y
                        zlength = selection.max.z - selection.min.z
                    )
                    else
                    (
                        for obj in selection do
                        (
                            xlength = obj.max.x - obj.min.x
                            ylength = obj.max.y - obj.min.y
                            zlength = obj.max.z - obj.min.z
                        )
                    )
                    
                    if            rdo_axis.state == 1 then ratio = target/xlength
                    else if    rdo_axis.state == 2 then ratio = target/ylength
                    else if    rdo_axis.state == 3 then ratio = target/zlength
                    
                    undo on
                    (
                        fn lerp minVal maxVal term = (maxVal - minVal) * term + minVal
                        
                        if chk_groupScale.state then
                        (
                            xPos = selection.center.x
                            yPos = selection.center.y
                            zPos = selection.min.z
                            
                            for obj in selection do
                            (
                                tempPos = obj.pos
                                obj.pos = [0,0,0]
                                obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [0,0,0])
                                obj.pos =  lerp [xPos, yPos, zPos] tempPos ratio
                            )
                        )
                        else scale filtervalidobj [ratio,ratio,ratio]
                        
                        if chk_xform.state then
                        (
                            for obj in filtervalidobj do
                            (
                                resetxform obj
                                convertToPoly obj
                            )
                        )
                    )
                )
                redrawViews()
            )
            
            on STrollout close do max modify mode
        )
        createdialog STrollout
    )
    
    ..the Group Scale checkbox is whether or not to scale the objects in selection individually (and scale each of them to the target scale) or group of selected objects (for example modeling an object with many small individual pieces that suppose to fit one another, so instead of scaling each of them to the target scale, this checkbox use the selection's dimension and scale all at the same time).

    Note: but please be careful with parented object, it'll do some weird stuff regarding parent/ child relation..the child object got scaled 2 times I believe (scale for its own and scale for its parent).
  • monster
    Options
    Offline / Send Message
    monster polycounter
    You can read about Lerp here: http://tech-artists.org/wiki/Linear_interpolation

    To fix children scaling twice just skip any object that has a parent.
    for obj in selection where obj.parent == undefined do
    
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    Thanks for the link monster! :)
    Hmm..about the parented object, it seems like when I added those line, it will messed up the calculation.
    Just to be sure, its used on this portion?
    for obj in selection where obj.parent == undefined do
    (
        tempPos = obj.pos
        obj.pos = [0,0,0]
        obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [0,0,0])
        obj.pos =  lerp [xPos, yPos, zPos] tempPos ratio
    )
    
    
  • monster
    Options
    Offline / Send Message
    monster polycounter
    I don't know man, works for me. The smaller boxes are children of the larger boxes:

    (BTW, a cool feature would be to resize as you adjust the spinner.)

    groupscale.gif.gif
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    EDIT: it seems like using resetScale obj instead of reserXform obj do the trick..will need to test it abit more to be sure.

    Yeah, you're right monster. The only problem is when I checked both the group scale and reset xform then it'll do some funny stuff, without reset xform it's working correctly.
  • p2021
    Options
    Offline / Send Message
    it was working on the setup I tested
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    Update!
    macroScript ScaleTo
    category:"_Modification"
    tooltip:"Scale To"
    buttontext:"Scale To"
    (
        local xlength
        local ylength
        local zlength
        local target
        local ratio
        
        try (destroydialog STrollout) catch()
        rollout STrollout "Scale To"
        (
            local oldPanel = getCommandPanelTaskMode()
            
            group "Reference Axis"
            (
                radiobuttons rdo_axis
                    labels:#("X Coordinate", "Y Coordinate", "Z Coordinate")
            )
            
            label lbl_spn_scaleTo "Scale To" pos:[10,80]
            label lbl_mm "(mm)" pos:[lbl_spn_scaleTo.pos[1]+45, lbl_spn_scaleTo.pos[2]]
            spinner spn_scaleTo fieldWidth:60 height:18 range:[0,1e+006,25] pos:[lbl_mm.pos[1]+27, lbl_spn_scaleTo.pos[2]]
            
            checkbox chk_groupScale "Group Scale" pos:[10, spn_scaleTo.pos[2]+20] checked:false
            checkbox chk_xform "Reset Xform" pos:[10, chk_groupScale.pos[2]+20] checked:true
            button btn_apply "Apply" width:150 height:18
            
            on STrollout open do
            (    
                units.displaytype = #Metric
                MT = units.MetricType
                case MT of
                (
                    (#Millimeters):(lbl_mm.text = "(mm)")
                    (#Centimeters):(lbl_mm.text = "(cm)")
                    (#Meters):(lbl_mm.text = "(m)")
                    (#Kilometers):(lbl_mm.text = "(km)")
                )
                max utility mode
                UtilityPanel.OpenUtility Measure
            )
            on btn_apply pressed do
            (
                try
                (
                    -- isolate the grouphead and non group object
                    filtervalidobj = #()
                    for obj in selection do
                    (
                        result_ghead = isGroupHead obj
                        result_gmember = isGroupMember obj
                        
                        if result_ghead == true and result_gmember == false then ( append filtervalidobj obj )
                        if result_gmember == false then (appendIfUnique filtervalidobj obj )
                    )
                ) catch()
                
                if    selection.count != 0 and
                    spn_scaleTo.value != 0 and
                    filtervalidobj.count != 0 then
                (
                    target = spn_scaleTo.value
                    fn lerp minVal maxVal term = (maxVal - minVal) * term + minVal
                    
                    undo on
                    (
                        if not chk_groupScale.state then
                        (
                            for obj in filtervalidobj where obj.parent == undefined do
                            (
                                xlength = obj.max.x - obj.min.x
                                ylength = obj.max.y - obj.min.y
                                zlength = obj.max.z - obj.min.z
                                
                                xPos = obj.center.x
                                yPos = obj.center.y
                                zPos = obj.min.z
                                
                                if            rdo_axis.state == 1 then ratio = target/xlength
                                else if    rdo_axis.state == 2 then ratio = target/ylength
                                else if    rdo_axis.state == 3 then ratio = target/zlength
                                
                                scale obj [ratio,ratio,ratio]
                            )
                        )
                        
                        if chk_groupScale.state then
                        (
                            xlength = selection.max.x - selection.min.x
                            ylength = selection.max.y - selection.min.y
                            zlength = selection.max.z - selection.min.z
                            
                            xPos = selection.center.x
                            yPos = selection.center.y
                            zPos = selection.min.z
                            
                            if            rdo_axis.state == 1 then ratio = target/xlength
                            else if    rdo_axis.state == 2 then ratio = target/ylength
                            else if    rdo_axis.state == 3 then ratio = target/zlength
                            
                            for obj in filtervalidobj where obj.parent == undefined do
                            (
                                tempPos = obj.pos
                                obj.pos = [0,0,0]
                                obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [0,0,0])
                                obj.pos =  lerp [xPos, yPos, zPos] tempPos ratio
                            )
                        )
                        
                        if chk_xform.state then
                            (for obj in filtervalidobj do resetScale obj)
                    )
                )
                redrawViews()
            )
            
            on STrollout close do
                setCommandPanelTaskMode oldPanel
        )
        createdialog STrollout
    )
    
    ..now it works with group scale and reset xform checked on, also filtered out parented object (so that it won't do any undesirable result)..it does however follow the parent scale..so essentially selection of non-parented object with group scale and selection of parented object without group scale result the same behavior.

    Hmm..about monster suggestion for an interactive spinner?..I'll see what I can do :)
  • Revel
    Options
    Offline / Send Message
    Revel interpolator
    Update! there's abit of re-structuring the code (hopefully I didn't messed it up somewhere). Pretty much tested on all situation and it looks like all works fine, if anyone find any strange behavior, please let me know.

    Hey monster, please take a look here. I've added the "Interactive mode" like what you suggested. You need to press Apply button in order for it to be available. Not too sure about this behavior, which one do you think better? the interavtive button always available for user to press or like how the currect behavior is?
    macroScript ScaleTo
    category:"_Modification"
    tooltip:"Scale To"
    buttontext:"Scale To"
    (
        -- global variable
        global STrollout
        
        -- local variable
        local
        xlength,
        ylength,
        zlength,
        target,
        ratio,
        firstUndo,
        isSpinner,
        filtervalidobj = #(),
        oldPanel = getCommandPanelTaskMode(),
        interactiveMode = false
        
        -- rollout creation
        try (destroydialog STrollout) catch()
        rollout STrollout "Scale To"
        (
            -- ui group
            group "Reference Axis"
            (
                radiobuttons rdo_axis
                    labels:#("X Coordinate", "Y Coordinate", "Z Coordinate")
            )
            
            label lbl_scaleTo "Scale To" pos:[10,80]
            label lbl_mm "(mm)" pos:[lbl_scaleTo.pos[1]+45, lbl_scaleTo.pos[2]]
            spinner spn_scaleTo fieldWidth:60 height:18 range:[0,1e+006,5] pos:[lbl_mm.pos[1]+27, lbl_scaleTo.pos[2]]
            
            checkbox chk_groupScale "Group Scale" pos:[10, spn_scaleTo.pos[2]+20] checked:false
            checkbox chk_xform "Reset Xform" pos:[10, chk_groupScale.pos[2]+20] checked:true
            button btn_apply "Apply" width:150 height:18 pos:[5, chk_xform.pos[2]+20]
            checkbutton cbtn_interactive "Interactive" width:150 height:18 pos:[5, btn_apply.pos[2]+20] enabled:false
            
            -- function
            fn lerp minVal maxVal term =
                (maxVal - minVal) * term + minVal
            
            fn transformType userDef =
            (
                case userDef of
                (
                    #nonResetXform: () -- do nothing
                    #resetXform: (for obj in filtervalidobj do resetScale obj)
                )
            )
            
            fn scaleType userDef =
            (
                case userDef of
                (
                    #nonGroupScale:
                    (
                        for obj in selection where obj.parent == undefined do
                        (
                            xlength = obj.max.x - obj.min.x
                            ylength = obj.max.y - obj.min.y
                            zlength = obj.max.z - obj.min.z
                            
                            xPos = obj.center.x
                            yPos = obj.center.y
                            zPos = obj.min.z
                            
                            if            rdo_axis.state == 1 then ratio = target/xlength
                            else if    rdo_axis.state == 2 then ratio = target/ylength
                            else if    rdo_axis.state == 3 then ratio = target/zlength
                            
                            scale obj [ratio,ratio,ratio]
                            
                            if chk_xform.state then transformType #resetXform
                            else transformType #nonResetXform
                        )
                    )
                    #groupScale:
                    (
                        xlength = selection.max.x - selection.min.x
                        ylength = selection.max.y - selection.min.y
                        zlength = selection.max.z - selection.min.z
                        
                        xPos = selection.center.x
                        yPos = selection.center.y
                        zPos = selection.min.z
                        
                        if            rdo_axis.state == 1 then ratio = target/xlength
                        else if    rdo_axis.state == 2 then ratio = target/ylength
                        else if    rdo_axis.state == 3 then ratio = target/zlength
                        
                        for obj in filtervalidobj where obj.parent == undefined do
                        (
                            tempPos = obj.pos
                            obj.pos = [0,0,0]
                            obj.transform = obj.transform * (matrix3 [ratio,0,0] [0,ratio,0] [0,0,ratio] [0,0,0])
                            obj.pos =  lerp [xPos, yPos, zPos] tempPos ratio
                        )
                        
                        if chk_xform.state then transformType #resetXform
                        else transformType #nonResetXform
                    )
                )
            )
            
            fn spnChange spnVal =
            (
                if interactiveMode == true then
                (
                    target = spnVal
                    local useUndo = (isSpinner and firstUndo) or (not isSpinner)
                    undo "changed" useUndo
                    (
                        if not chk_groupScale.state then
                            scaleType #nonGroupScale
                        
                        if chk_groupScale.state then
                            scaleType #groupScale
                    )
                    firstUndo = false
                    redrawViews()
                )
            )
            
            fn spnBtnDown =
            (
                isSpinner = true
                firstUndo = true
            )
            
            fn spnBtnUp =
            (
                isSpinner = false
            )
            
            -- ui behavior
            on STrollout open do
            (    
                units.displaytype = #Metric
                MT = units.MetricType
                case MT of
                (
                    (#Millimeters):(lbl_mm.text = "(mm)")
                    (#Centimeters):(lbl_mm.text = "(cm)")
                    (#Meters):(lbl_mm.text = "(m)")
                    (#Kilometers):(lbl_mm.text = "(km)")
                )
                max utility mode
                UtilityPanel.OpenUtility Measure
            )
            
            on btn_apply pressed do
            (
                try
                (
                    -- isolate the grouphead and non group object
                    for obj in selection do
                    (
                        result_ghead = isGroupHead obj
                        result_gmember = isGroupMember obj
                        
                        if result_ghead == true and result_gmember == false then ( append filtervalidobj obj )
                        if result_gmember == false then (appendIfUnique filtervalidobj obj )
                    )
                ) catch()
                
                if    selection.count != 0 and
                    spn_scaleTo.value != 0 and
                    filtervalidobj.count != 0 then
                (
                    target = spn_scaleTo.value
                    undo on
                    (
                        if not chk_groupScale.state then
                            scaleType #nonGroupScale
                        
                        if chk_groupScale.state then
                            scaleType #groupScale
                        
                        if chk_xform.state then
                            transformType #resetXform
                    )
                )
                
                cbtn_interactive.enabled = true
                
                redrawViews()
            )
            
            on cbtn_interactive changed state do
            (
                xlength = selection.max.x - selection.min.x
                ylength = selection.max.y - selection.min.y
                zlength = selection.max.z - selection.min.z
                
                if            rdo_axis.state == 1 then spn_scaleTo.value = xlength
                else if    rdo_axis.state == 2 then spn_scaleTo.value = ylength 
                else if    rdo_axis.state == 3 then spn_scaleTo.value = zlength
                
                if uiState == undefined then
                    uiState = rdo_axis.enabled
                
                rdo_axis.enabled = not uiState
                chk_groupScale.enabled = not uiState
                chk_xform.enabled = not uiState
                btn_apply.enabled = not uiState
                cbtn_interactive.enabled = uiState
                
                interactiveMode = uiState
            )
            
            on spn_scaleTo changed val do spnChange val
            on spn_scaleTo buttonDown do spnBtnDown()
            on spn_scaleTo buttonUp do spnBtnUp()
            
            on STrollout close do
                setCommandPanelTaskMode oldPanel
        )
        
        createdialog STrollout style:#(#style_titlebar, #style_sysmenu, #style_toolwindow) pos:[300,300]
    )
    
    .. it seems like there is still part of the code that can be trimmed down a little. But so far it dose things that I wanted it to do :)
Sign In or Register to comment.