[Closed] Edit_Poly LoopSelect behavior
For the life of me, I can’t find the logic (if there’s any) behind LoopSelect behavior in the Edit_Poly modifier. It should pretty much map to SetLoopShift behavior in Editable_Poly, the parameters are the same, the functions called behind the scenes are named the same. Yet it seems to give me totally unpredictable results (and it’s not based on if I hold ctrl, shift or alt when running it, I tried clicking on the Evaluate All in script editor with the same results). Here’s what happens when repeatedly running $.SetLoopShift -1 false true with editable poly:
Nice and repeatable addition in one direction, cool. With Edit_Poly using i.LoopSelect -1 false true[/i] either nothing happens at all or one neighboring edge gets selected and that’s it. Funnily enough, if you keep alternating -1 with 1 to select up and down, it will work properly (almost, whether the first one will select anything is a lottery, but after that, it’s stable):
As a sanity check, I tried the same with EpModSetLoopShift but the behavior was the same as with LoopSelect…
do not know why…but if I use -2 in place of -1…it’s faaaaaaar more stable !!!
(or 2 instead of 1 if you want to go positive)
Basically, abs > 1 is more stable
i remember this unstable behavior too. that’s why i rewrote this piece of code in my mxs extension
Good to have a confirmation from you. Did you use EpModUpdateLoopEdgeSelection at all or did you rewrite it from scratch?
the sdk base is :
IMNMeshUtilities8* meshToStep = static_cast<IMNMeshUtilities8*>(mesh.GetInterface( IMNMESHUTILITIES8_INTERFACE_ID ));
meshToStep->SelectEdgeLoopShift(dir,newSel);
// or // meshToStep->SelectEdgeRingShift(dir,newSel);
...
it works correct on this level
I think while trying to implement this…the unfortunate thing is the programmer prolly did it one drunken night and got his knickers in a twist…
Shifting one or all edge loops by three using all possible argument combinations:
[i]MoveOnly[/i]
[i]Add[/i]
[i]Result:[/i]
[i]true[/i]
[i]false[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-C4C2CA7A-8EDB-47F7-96A3-3B8C6C3746C8-low.gif [/img]
[i]true[/i]
[i]false[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-891F8677-9987-4D58-AFA0-30646477E753-low.gif [/img]
[i]true[/i]
[i]true[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-195A197F-B79E-458E-850F-0859FF6CC698-low.gif [/img]
[i]true[/i]
[i]true[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-0AED138B-F8D7-4D3A-AF5A-556EE3CF4D9B-low.gif [/img]
[i]false[/i]
[i]true[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-BCBCFE38-FE53-4192-B862-B9F0DEA5DAEE-low.gif [/img]
[i]false[/i]
[i]true[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-44CB33BE-AFC0-4A26-8D58-A86F6ED9804E-low.gif [/img]
[i]false[/i]
[i]false[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-56846322-FFBD-4071-B164-EB3BBF53917B-low.gif [/img]
[i]false[/i]
[i]false[/i]
[img] https://help.autodesk.com/cloudhelp/2017/ENU/MAXScript-Help/images/GUID-0CFC063D-0B55-4E65-80D5-21C90DE181BC-low.gif [/img]
Close but not quite.
There are issues with certain edges, open edges and it doesnot always play well if you alternate between it and the EditPoly UI spinner.
(
try destroydialog ::RO_LOOP catch()
rollout RO_LOOP "" width:72
(
button bt1 "+1" width:48 height:32
button bt2 "-1" width:48 height:32
checkbox chk1 "Add"
local lastdir
local count = 1
fn Loop dir:1 =
(
md = modPanel.getCurrentObject()
if lastdir != dir do count = 1
lastdir = dir
sel = md.GetSelection #Edge
md.LoopSelect (count*dir) true false
count = amin (count += 1) 3
if chk1.checked or keyboard.controlPressed do md.SetSelection #Edge (sel + md.GetSelection #Edge)
)
on bt1 pressed do Loop dir:1
on bt2 pressed do Loop dir:-1
)
createdialog RO_LOOP
)
Close enough to be useful most of the time, I really like it, thanks!
Worth noting that the problem after using the UI is also present when switching between different objects with different modifiers since it’s not enough to just reset values if the current object changes but you’d have to keep a list of previous modifiers with their current values.
Yes, it has a weird behavior. Just posted it so perhaps you could find something else, but I wouldn’t actually use it for anything as it is.
Here is a C++/MXS code that may work. I would plan better the C++ method, but currently it is flexible enough to be modified from MXS. Tested on Max 2014
def_visible_primitive(LoopSelect, "LoopSelect");
Value* LoopSelect_cf(Value** arg_list, int count)
{
check_arg_count_with_keys(LoopSelect, 3, count);
int dir = (arg_list[0]->to_int()<0) ? -1 : 1;
int cnt = arg_list[1]->to_int();
BOOL single = arg_list[2]->to_bool();
BaseObject* base = GetCOREInterface()->GetCurEditObject();
if (base->ClassID() == EDIT_POLY_MODIFIER_CLASS_ID)
{
BitArray newSel;
EPolyMod13* ep = (EPolyMod13*) base->GetInterface(EPOLY_MOD13_INTERFACE);
MNMesh &nmesh = *ep->EpModGetMesh();
newSel.SetSize(nmesh.nume);
newSel.ClearAll();
IMNMeshUtilities8* meshToLoop = static_cast<IMNMeshUtilities8*>( nmesh.GetInterface (IMNMESHUTILITIES8_INTERFACE_ID) );
if (single)
{
meshToLoop->SelectEdgeLoopShift(dir*cnt, newSel);
}else{
for (int j=0; j<cnt; j++) meshToLoop->SelectEdgeLoopShift(dir*(j+1), newSel);
}
return new BitArrayValue(newSel);
}
return &undefined;
}
(
try destroydialog ::RO_LOOP catch()
rollout RO_LOOP "" width:88
(
button bt1 "+" pos:[ 8, 8] width:32 height:24
button bt2 "-" pos:[48, 8] width:32 height:24
spinner sp_count "Count:" pos:[ 8,40] fieldwidth:28 type:#integer range:[1,100,1]
checkbox chk_single "Single Edge" pos:[ 8,64]
radiobuttons rb_action "" pos:[ 8,84] labels:#("Move", "Add", "Remove")
fn LoopEdgeSelection dir =
(
md = modPanel.getCurrentObject()
if not iskindof md Edit_Poly do return()
oldSel = md.GetSelection #Edge
count = sp_count.value
single = chk_single.checked
result = case rb_action.state of
(
1: LoopSelect dir count true
2: oldSel + (LoopSelect dir count single)
3: oldSel * (LoopSelect -dir count true)
)
undo "Edge Loop"on md.SetSelection #Edge result
)
on bt1 pressed do LoopEdgeSelection 1
on bt2 pressed do LoopEdgeSelection -1
)
createdialog RO_LOOP
)
Perfect, that settles it. Since I don’t have to care about older versions and I kinda prefer C# whenever I can use it, this is, here’s my version (I think NativePointer was called handle before and BitArray.Create wasn’t there before max 2016 so it definitely won’t work on older max):
if not isKindOf EPolyMod dotNetObject do
(
local compilerParams = dotNetObject "System.CodeDom.Compiler.CompilerParameters" #(
getDir #maxRoot + "Autodesk.Max.dll",
getDir #maxRoot + "MaxPlusDotNet.dll", "System.Core.dll",
getDir #maxRoot + "\bin\assemblies\Autodesk.Max.Wrappers.dll")
compilerParams.GenerateInMemory = true
local compilerResults = (dotNetObject "Microsoft.CSharp.CSharpCodeProvider").CompileAssemblyFromSource compilerParams #(
"using System;
using Autodesk.Max;
using System.Linq;
using System.Collections.Generic;
using Wrappers = Autodesk.Max.Wrappers;
using Core = Autodesk.Max.MaxPlus.Core;
using Constants = Autodesk.Max.MaxPlus.Constants;
using InterfaceIds = Autodesk.Max.MaxPlus.InterfaceIds;
internal static class IBitArrayExtensions {
// https://ephere.com/autodesk/max/forums/general/thread_2527.html
public static IBitArray BitwiseOr(this IBitArray A, IBitArray B) {
if (A.Size > B.Size) B.SetSize(A.Size, 1); else A.SetSize(B.Size, 1);
return B.BitwiseXor(A.BitwiseXor(A.BitwiseAnd(B)));
}
public static IEnumerable<int> ToEnumerable(this IBitArray ba) {
for (int i = 0; i < ba.Size; i++) if (ba[i] > 0) yield return i;
}
}
class EPolyMod {
private static readonly IGlobal Global = GlobalInterface.Instance;
private static readonly IInterface_ID EPolyModInterfaceID = GetInterfaceID(InterfaceIds.EpolyMod);
private static readonly IInterface_ID IMNMeshUtilities8 = GetInterfaceID(InterfaceIds.Imnmeshutilities8);
private static IInterface_ID GetInterfaceID(Autodesk.Max.MaxPlus.Interface_ID id) {
return Global.Interface_ID.Create(id.GetPartA(), id.GetPartB());
}
private static IBaseInterface GetInterface(UIntPtr handle, IInterface_ID interfaceID) {
return Global.Animatable.GetAnimByHandle(handle).GetInterface(interfaceID);
}
public static int[] GetShiftedLoop(UIntPtr epmHandle, int dir, int count, bool moveOnly, bool add) {
var ePoly = (IEPolyMod)GetInterface(epmHandle, EPolyModInterfaceID);
var mm = ePoly.EpModGetMesh(null);
var marshaller = Wrappers.CustomMarshalerIMNMeshUtilities8.GetInstance(string.Empty);
var mmu = (IIMNMeshUtilities8)marshaller.MarshalNativeToManaged((mm.GetInterface(IMNMeshUtilities8) as Autodesk.Max.Wrappers.BaseInterface).INativeObject__NativePointer);
var newSel = Global.BitArray.Create(mm.Nume);
var currentSel = Global.BitArray.Create(mm.Nume);
mm.GetEdgeSel(currentSel);
if (moveOnly || !add) mmu.SelectEdgeLoopShift(dir * count, newSel);
else for (int shift = 1; shift <= count; shift++)
mmu.SelectEdgeLoopShift(dir * shift, newSel);
if (!moveOnly) newSel = add ? currentSel.BitwiseOr(newSel) : currentSel.BitwiseAnd(newSel);
return newSel.IsEmpty ? new int[0] : newSel.ToEnumerable().Select(i => i + 1).ToArray();
}
}"
)
for err = 0 to compilerResults.errors.count - 1 do print (compilerResults.errors.item[err].ToString())
::EPolyMod = compilerResults.CompiledAssembly.CreateInstance "EPolyMod"
)
fn setLoopShift poly loopShift moveOnly add = if isKindOf poly Edit_Poly do
(
local count = abs loopShift
local dir = loopShift / count
local newSel = EPolyMod.GetShiftedLoop (getHandleByAnim poly) dir count moveOnly add as bitArray
poly.SetSelection #Edge #{}
poly.Select #Edge newSel
)
setLoopShift (modPanel.getCurrentObject()) -3 off on
why is Epoly Modifier only? You can use this interface for Editable Poly object as well
Pretty much because of the necessity of MXS SetSelection in edit_poly case… Makes it kinda wonky, and EPMeshSetEdgeFlags doesn’t seem to play well with the MN_EDITPOLY_OP_SELECT flag
[b]getEdgeSelectionShift [/b]obj dir action type:<> edges:<bitarray> select:<bool>
where:
obj – editable poly or edit poly modifier
dir – direction and number steps
action – #move or #shrink or #grow
type – #loop and #ring
edges – list of old edges (default – current selection)
select – select or not select new edges
returns – new edge selection
here is a logic of my function… (sorry but i can’t share the code because of a little trick i use, which changes everything and makes it better than MAX)
but I can explain where the problem is:
try to make a cylinder and select full stack of edges for all loops including cap loops
shift them
you should see that cap edges shift-move in the opposite direction than all other… it’s just an example, but you can meet this issue very often with poly objects.
the idea is to shift-move them all in the same direction (it doesn’t matter forward or backward).
another problem is similar – make for all loops(rings) shift-shrink and shift-grow in the same direction.
You can see the same with a default plane, for example, if you select just two opposite open edges. They will shift in opposite directions.
The algorithm seems to be based on two rules:
- Loop edges clusters.
- Edges vertices order. Where for each cluster, the lower index edge is the one that leads the direction in which the loop will move.
So if the edge verts are #(1,2) the loop will move in one direction and if they are #(2,1) it will move in the opposite direction. As soon as you add an edge to a cluster, it could change the direction if it has the lower edge index and reversed vertices order compared with the previous leading edge.
With a custom algorithm, you can’t predict in which direction it will move, but you can make all the clusters to move in the same direction.