Notifications
Clear all

[Closed] Attach node to patch

As title suggests, I’m trying to figure out a decent workaround to attaching a specific node to the current patch object. patchOps.startAttach only lets you specify the node you are attaching to, and goes into pick mode.

The only solution I have so far, would be to get all of the positions of each vertex handle and its tangent type, set the patch steps to 0, convert to edit poly, attach node, convert to patch, reset the handle positions, and their tangent types, and set the patch steps back to what they were previously.

I haven’t tried this yet, I was curious if anyone had a more elegant workaround. My goal is to make a script that would attach selected patch objects together and I think my solution would be very slow on even an average number of objects.

Any help would be appreciated, thanks!

32 Replies

it’s very easy with c++/SDK and so complicated with MXS.

so the basic idea is to click in viewport in place where the attached node is using postmessage.
after that using change event of the patch exit the attaching mode.
here is a sample:


global PickSupport
fn CreateMessagesAssembly =
(
	source  = "using System;
"
	source += "using System.Runtime.InteropServices;
"
	source += "class PickSupport
"
	source += "{
"
	source += "	[DllImport(\"user32.dll\", EntryPoint=\"GetWindowRect\")]
"
	source += "	static extern bool GetWindowRect(IntPtr hWnd, out POS rect);
"
	source += "	public struct POS
"
	source += "	{
"
	source += "		public int Left;
"
	source += "		public int Top;
"
	source += "		public int Right;
"
	source += "		public int Bottom;
"
	source += "	}
"
	source += "	public int[] GetWindowPosAndSize(Int32 hWnd)
"
	source += "	{
"
	source += "		POS rect;
"
	source += "		if ( GetWindowRect((IntPtr)hWnd, out rect) )
"
	source += "		{
"
	source += "			return new int[] { rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };
"
	source += "		}
"
	source += "		return null;
"
	source += "	}
"
	source += " [DllImport(\"user32.dll\")]
"
	source += " public static extern int PostMessage(Int32 hWnd, int wMsg, int wParam, int lParam);
"
	source += "}
"

	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

	compilerParams.ReferencedAssemblies.AddRange #("System.dll")

	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	
	PickSupport = compilerResults.CompiledAssembly.CreateInstance "PickSupport"
)
CreateMessagesAssembly()

fn makeParam LoWord HiWord = 
(
	bit.or (bit.shift HiWord 16) (bit.and LoWord 0xFFFF)
)
fn findActiveLabel = 
(
	fn windowpos hwnd =
	(
		local p = PickSupport.GetWindowPosAndSize hwnd
		#([p[1],p[2]],[p[3],p[4]])
	)
	hwnd = for c in (windows.getChildrenHWND #max) where c[4] == "ViewPanel" do exit with c[1]
	d3ds = #()
	lbls = #()
	for c in (windows.getChildrenHWND hwnd parent:hwnd) do case c[4] of
	(
			"Label": append lbls c[1]
		"D3DWindow": append d3ds c[1] 
	)
	p = windowpos d3ds[viewport.activeViewport]
	for lb in lbls where (windowpos lb)[1] == p[1] do exit with lb
)

fn pickPatchAttach node = if iskindof (patch = modpanel.getcurrentobject()) Editable_Patch do
(
	bt = (windows.getchildhwnd #max "Attach")[1]
	toolmode.CommandMode = #select
	uiaccessor.pressbutton bt

	when parameters patch change id:#custom_patch_pick patch do
	(
		deleteAllChangeHandlers id:#custom_patch_pick
		toolmode.CommandMode = #select
	)
		
	hwnd = findActiveLabel()
	mesh = snapshotasmesh node
	pp = getvert mesh 1
	gw.settransform (matrix3 1)
	pv = gw.TransPoint pp

	ps = mouse.screenpos - mouse.pos + pv

	WM_LBUTTONDOWN 		= 0x201
	WM_LBUTTONUP 		= 0x202
	
	xy = makeParam (pv.x as integer) (pv.y as integer)
	PickSupport.postmessage hwnd WM_LBUTTONDOWN 0 xy
	PickSupport.postmessage hwnd WM_LBUTTONUP 0 xy
)

/* simple scene and attach in action */
(
	viewport.resetAllViews()
	delete objects
	max create mode
	target = Quadpatch name:"target" pos:[-10,0,0] length:10 lengthsegs:4 width:10 widthsegs:4 isselected:on
	convertto target Editable_Patch
	source = Quadpatch name:"source" pos:[10,0,0] length:10 lengthsegs:2 width:10 widthsegs:2

	max modify mode
	pickPatchAttach source
)

and multi-attach version
with using nodePreDelete callback


global PickSupport
fn CreateMessagesAssembly =
(
	source  = "using System;
"
	source += "using System.Runtime.InteropServices;
"
	source += "class PickSupport
"
	source += "{
"
	source += "	[DllImport(\"user32.dll\", EntryPoint=\"GetWindowRect\")]
"
	source += "	static extern bool GetWindowRect(IntPtr hWnd, out POS rect);
"
	source += "	public struct POS
"
	source += "	{
"
	source += "		public int Left;
"
	source += "		public int Top;
"
	source += "		public int Right;
"
	source += "		public int Bottom;
"
	source += "	}
"
	source += "	public int[] GetWindowPosAndSize(Int32 hWnd)
"
	source += "	{
"
	source += "		POS rect;
"
	source += "		if ( GetWindowRect((IntPtr)hWnd, out rect) )
"
	source += "		{
"
	source += "			return new int[] { rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };
"
	source += "		}
"
	source += "		return null;
"
	source += "	}
"
	source += " [DllImport(\"user32.dll\")]
"
	source += " public static extern int PostMessage(Int32 hWnd, int wMsg, int wParam, int lParam);
"
	source += "}
"

	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

	compilerParams.ReferencedAssemblies.AddRange #("System.dll")

	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	
	PickSupport = compilerResults.CompiledAssembly.CreateInstance "PickSupport"
)
CreateMessagesAssembly()

fn makeParam LoWord HiWord = 
(
	bit.or (bit.shift HiWord 16) (bit.and LoWord 0xFFFF)
)
fn findActiveLabel = 
(
	fn windowpos hwnd =
	(
		local p = PickSupport.GetWindowPosAndSize hwnd
		#([p[1],p[2]],[p[3],p[4]])
	)
	hwnd = for c in (windows.getChildrenHWND #max) where c[4] == "ViewPanel" do exit with c[1]
	d3ds = #()
	lbls = #()
	for c in (windows.getChildrenHWND hwnd parent:hwnd) do case c[4] of
	(
			"Label": append lbls c[1]
		"D3DWindow": append d3ds c[1] 
	)
	p = windowpos d3ds[viewport.activeViewport]
	for lb in lbls where (windowpos lb)[1] == p[1] do exit with lb
)
fn nodePreDelete handle: =
(
	if handle == unsupplied or handle == gethandlebyanim (callbacks.notificationparam()) do 
	(
		callbacks.removescripts id:#custom_patch_pick 
		toolmode.CommandMode = #select
	)	
)
fn pickPatchAttach nodes = if iskindof (patch = modpanel.getcurrentobject()) Editable_Patch do
(
	if isvalidnode nodes do nodes = #(nodes) 
	nodes = for node in nodes where canconvertto node Editable_Patch collect node 
	if nodes.count > 0 do
	(
		bt = (windows.getchildhwnd #max "Attach")[1]
		toolmode.CommandMode = #select
		uiaccessor.pressbutton bt

		callbacks.addscript #nodePreDelete ("nodePreDelete handle:" + (gethandlebyanim nodes[nodes.count]) as string) id:#custom_patch_pick	
			
		hwnd = findActiveLabel()

		WM_LBUTTONDOWN 		= 0x201
		WM_LBUTTONUP 		= 0x202
		
		for node in nodes do
		(
			mesh = snapshotasmesh node
			pp = getvert mesh 1
			gw.settransform (matrix3 1)
			pv = gw.TransPoint pp

			ps = mouse.screenpos - mouse.pos + pv

			xy = makeParam (pv.x as integer) (pv.y as integer)
			PickSupport.postmessage hwnd WM_LBUTTONDOWN 0 xy
			PickSupport.postmessage hwnd WM_LBUTTONUP 0 xy
		)
	)
)
callbacks.removescripts id:#custom_patch_pick 

/* simple scene and attach in action */
with redraw off
(
	viewport.resetAllViews()
	delete objects
	max create mode
	target = Quadpatch name:"target" pos:[-10,0,0] length:10 lengthsegs:4 width:10 widthsegs:4 isselected:on
	convertto target Editable_Patch
	sources = for k=1 to 4 collect (Quadpatch pos:[10*k,0,0] length:8 lengthsegs:2 width:8 widthsegs:2)

	max modify mode
	pickPatchAttach sources
)


Whoa you weren’t lying, that does look complicated! Thanks I will have to give that a try… Does this method work with off screen objects? Or does the picked nodes have to be visible in the viewport?

I tried doing a test of my idea just seeing if everything would work after going to edit poly and back –

fn ResetPatchVectorPositions Obj:$ = 
(
	PosArray = #()
	VertTypesArray = for i = 1 to (patch.getNumVerts $) collect patch.getVertType $ i
	Steps = getPatchSteps Obj
	setPatchSteps Obj 0
	for i = 1 to (patch.getNumVerts Obj) do 
	(
		local VertVecs = (patch.getVertVecs Obj i as array)
		append PosArray (for j = 1 to VertVecs.count collect (patch.getVec Obj VertVecs[j]))
	)
	modPanel.addModToSelection (Turn_to_Poly ()) ; macros.run "Modifier Stack" "Convert_to_Poly"
	----------------------------------------------------------------------------------------------- attach here
	modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch"
	for i = 1 to (patch.getNumVerts Obj) do 
	(
		local VertVecs = (patch.getVertVecs Obj i as array)
		if (PosArray[i]) != undefined do (for j = 1 to VertVecs.count do (if (PosArray[i][j]) != undefined do (patch.setVec Obj VertVecs[j] PosArray[i][j])))
	)
	setPatchSteps Obj Steps
	for i = 1 to (patch.getNumVerts $) do (if (VertTypesArray[i]) != undefined do patch.changeVertType $ i (VertTypesArray[i]))
)

It seems to work ok, without any noticeable differences after conversion, but I believe I will have to offset the node’s vertex list by the node’s total vertex count, after its attached, in order to access them properly.

you are right… the method needs several thing to do.
#1 we have to do it without view flickering (i showed how to disable view redraw)
#2 we have to insure the attached node’s visibility and not overlapping by other node for picking

so… the plan is:

store viewport state

store max scene state

disable redraw max

hide everything except the attached node

set view to be ready to pick for sure

do attach

use any event to restore views and scene state back

enable redraw and redraw the max

Sorry for not getting back sooner, had to get some real sleep Ok so I tried evaluating your single attach version in an empty scene in max 2013 x64 and the listener spit out some errors –

OK
CreateMessagesAssembly()
dotNetObject:PickSupport
makeParam()
findActiveLabel()
pickPatchAttach()
-- Error occurred in windowpos(); filename: ; position: 1739; line: 51
--  Frame:
--   p: undefined
--   hwnd: undefined
--   called in findActiveLabel(); filename: ; position: 2080; line: 62
--  Frame:
--   windowpos: windowpos()
--   d3ds: #()
--   lbls: #(264734P, 264736P, 264738P, 264740P)
--   p: undefined
--   hwnd: 264742P
--   called in pickPatchAttach(); filename: ; position: 2542; line: 78
--  Frame:
--   bt: 34802382P
--   pp: undefined
--   ps: undefined
--   XY: undefined
--   node: $source
--   WM_LBUTTONDOWN: undefined
--   WM_LBUTTONUP: undefined
--   pv: undefined
--   patch: Editable Patch
--   hwnd: undefined
--   mesh: undefined
--   called in anonymous codeblock; filename: ; position: 3288; line: 104
--  Frame:
--   target: $target
--   source: $source
-- Runtime error: No method found which matched argument list

And the multi version looks like it has similar problems, which I have no clue what they are –

OK
CreateMessagesAssembly()
dotNetObject:PickSupport
makeParam()
findActiveLabel()
nodePreDelete()
pickPatchAttach()
OK
-- Error occurred in windowpos(); filename: ; position: 1737; line: 50
--  Frame:
--   p: undefined
--   hwnd: undefined
--   called in findActiveLabel(); filename: ; position: 2078; line: 61
--  Frame:
--   windowpos: windowpos()
--   d3ds: #()
--   lbls: #(264734P, 264736P, 264738P, 264740P)
--   p: undefined
--   hwnd: 264742P
--   called in pickPatchAttach(); filename: ; position: 2898; line: 84
--  Frame:
--   bt: 25366036P
--   nodes: #($QuadPatch001, $QuadPatch002, $QuadPatch003, $QuadPatch004)
--   WM_LBUTTONDOWN: undefined
--   WM_LBUTTONUP: undefined
--   patch: Editable Patch
--   hwnd: undefined
--   called in anonymous codeblock; filename: ; position: 3779; line: 117
--  Frame:
--   target: $target
--   sources: #($QuadPatch001, $QuadPatch002, $QuadPatch003, $QuadPatch004)
-- Runtime error: No method found which matched argument list

they changed something in 2013. something is in the ViewPanel…
i don’t have max 2013 so could you show me the print of:


(
	hwnds = for c in (windows.getChildrenHWND #max) where c[4] == "ViewPanel" collect 
	(
		format ">> %
" c
		c
	)
	if hwnds[1] != undefined then for c in (windows.getChildrenHWND hwnds[1][1]) do format "	%
" c else print "no found"
)

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

interesting… where did they move D3DWindows?

Sure, btw I have Quickmaximize and Outliner 2.0 scripts installed, if it makes a difference, maximized I get –

>> #(264742P, 15010910P, 15010910P, "ViewPanel", "", 0P, 15010910P, 15010910P)
	#(264740P, 264742P, 264742P, "Label", "Perspective", 0P, 15010910P, 15010910P)
	#(264734P, 264742P, 264742P, "Label", "Top", 0P, 15010910P, 15010910P)
	#(264736P, 264742P, 264742P, "Label", "Left", 0P, 15010910P, 15010910P)
	#(264738P, 264742P, 264742P, "Label", "Front", 0P, 15010910P, 15010910P)
OK

Unmaxized, with the four default views I get –


>> #(264742P, 15010910P, 15010910P, "ViewPanel" , "", 0P, 15010910P, 15010910P)
	#(264734P, 264742P, 264742P, "Label", "Perspective", 0P, 15010910P, 15010910P)
	#(264736P, 264742P, 264742P, "Label", "Left", 0P, 15010910P, 15010910P)
	#(264738P, 264742P, 264742P, "Label", "Front", 0P, 15010910P, 15010910P)
	#(264740P, 264742P, 264742P, "Label", "Top", 0P, 15010910P, 15010910P)
	#(6886352P, 264742P, 264742P, "VptSplitterBar", "", 0P, 15010910P, 15010910P)
	#(26677438P, 264742P, 264742P, "VptSplitterBar", "", 0P, 15010910P, 15010910P)
OK

Also if it matters, I believe I get the same errors, with perspective maximized or not maximized, and the view gets unmaximized on error.

it’s easier for 2013… could you select other than perspective view and run the same code?

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

here is the 2013 version:


global PickSupport
fn CreateMessagesAssembly =
(
	source  = "using System;
"
	source += "using System.Runtime.InteropServices;
"
	source += "class PickSupport
"
	source += "{
"
	source += " [DllImport(\"user32.dll\")]
"
	source += " public static extern int PostMessage(Int32 hWnd, int wMsg, int wParam, int lParam);
"
	source += "}
"

	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

	compilerParams.ReferencedAssemblies.AddRange #("System.dll")

	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	
	PickSupport = compilerResults.CompiledAssembly.CreateInstance "PickSupport"
)
CreateMessagesAssembly()

fn makeParam LoWord HiWord = 
(
	bit.or (bit.shift HiWord 16) (bit.and LoWord 0xFFFF)
)
fn findActiveLabel = 
(
	local hwnd
	hwnd = for c in (windows.getChildrenHWND #max) where c[4] == "ViewPanel" do exit with c[1] 
	for c in (windows.getChildrenHWND hwnd parent:hwnd) where c[4] == "Label" do exit with c[1]
)
fn nodePreDelete handle: =
(
	if handle == unsupplied or handle == gethandlebyanim (callbacks.notificationparam()) do 
	(
		callbacks.removescripts id:#custom_patch_pick 
		toolmode.CommandMode = #select
	)	
)
fn pickPatchAttach nodes = if iskindof (patch = modpanel.getcurrentobject()) Editable_Patch do
(
	if isvalidnode nodes do nodes = #(nodes) 
	nodes = for node in nodes where canconvertto node Editable_Patch collect node 
	if nodes.count > 0 do
	(
		bt = (windows.getchildhwnd #max "Attach")[1]
		toolmode.CommandMode = #select
		uiaccessor.pressbutton bt

		callbacks.addscript #nodePreDelete ("nodePreDelete handle:" + (gethandlebyanim nodes[nodes.count]) as string) id:#custom_patch_pick	
			
		hwnd = findActiveLabel()

		WM_LBUTTONDOWN 		= 0x201
		WM_LBUTTONUP 		= 0x202
		
		for node in nodes do
		(
			mesh = snapshotasmesh node
			pp = getvert mesh 1
			gw.settransform (matrix3 1)
			pv = gw.TransPoint pp

			ps = mouse.screenpos - mouse.pos + pv

			xy = makeParam (pv.x as integer) (pv.y as integer)
			PickSupport.postmessage hwnd WM_LBUTTONDOWN 0 xy
			PickSupport.postmessage hwnd WM_LBUTTONUP 0 xy
		)
	)
)
callbacks.removescripts id:#custom_patch_pick 

/* simple scene and attach in action */
with redraw off
(
	viewport.resetAllViews()
	delete objects
	max create mode
	target = Quadpatch name:"target" pos:[-10,0,0] length:10 lengthsegs:4 width:10 widthsegs:4 isselected:on
	convertto target Editable_Patch
	sources = for k=1 to 4 collect (Quadpatch pos:[10*k,0,0] length:8 lengthsegs:2 width:8 widthsegs:2)

	max modify mode
	pickPatchAttach sources
)

Page 1 / 3