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
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.
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 ) )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).
To fix children scaling twice just skip any object that has a parent.
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 )(BTW, a cool feature would be to resize as you adjust the spinner.)
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.
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
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