converting max array to dotnet array doesn’t speed up addRange. You can add max array as well. Max does do it automatically.
dotNet + MXS
Just a thought: AddRange for a tree is fun and all, but the drawback is of course that all the nodes you add through it will be on the same branch.
Of course you should still try to optimize your code towards doing this, but there can definitely be situations where it just doesn’t help much
I ran a quick benchmark on what it’d mean for performance to pass an object from .NET to maxscript. The reason for this is that I was expecting it to be expensive, as I explained a few posts ago.
I created a simple object in C#:
public class OutlinerLayer
{
public String Name { get; set; }
public Boolean IsHidden { get; set; }
public Boolean IsFrozen { get; set; }
public Boolean IsActive { get; set; }
public OutlinerLayer(String name, Boolean isHidden, Boolean isFrozen, Boolean isActive)
{
this.Name = name;
this.IsHidden = isHidden;
this.IsFrozen = isFrozen;
this.IsActive = isActive;
}
}
And two functions to create (and store) the object. One return void, the other returned the created object:
public void CreateLayerObj (String name, Boolean isHidden, Boolean isFrozen, Boolean isActive)
{
OutlinerLayer layer = new OutlinerLayer(name, isHidden, isFrozen, isActive);
this.AddLayer(layer); //Stores the object internally in a dictionary.
}
public OutlinerLayer CreateReturnLayerObj (String name, Boolean isHidden, Boolean isFrozen, Boolean isActive)
{
OutlinerLayer layer = new OutlinerLayer(name, isHidden, isFrozen, isActive);
this.AddLayer(layer); //Stores the object internally in a dictionary.
return layer;
}
The performance difference between the two is quite significant:
CreateLayerObj, 10.000x - 568ms
CreateReturnLayerObj, 10.000x - 808ms
So I guess that shows that it will pay off not to juggle too much with objects between mxs and .net if it can be avoided. That is one of the reasons why in this object for example, I have only one constructor which sets all properties. This avoids having to return the created object to set properties in maxscript that weren’t set yet.
This can, however, come at a price: readability: :surprised
tree_Scene_AddNodeFn o.inode.handle parentHandle o.name ((classof o) as string) ((superclassof o) as string) o.layer.name o.isHiddenInVpt o.isFrozen (isGroupHead o) (isGroupMember o) o.material o.wireColor.r o.wireColor.g o.wireColor.b
Hello,
Is it possible to load a C# assembly from a network path ?
dotnet.loadAssembly network_path – network_path=“J:\bin\assemblies\assembly.dll”
obj = dotNetObject “Namespace.Class” – this line throws .net security exception.
Maybe you could try loading the assembly using (dotnetobject “System.Reflection.Assembly”).Load in combination with an Evidence object:
http://msdn.microsoft.com/en-us/library/ms145229.aspx
http://msdn.microsoft.com/en-us/library/system.security.policy.evidence.aspx
at one stage I was trying to find a way to load dlls in a more dynamic way that would allow me to unload them to make testing and updating and such easier. It never worked out but this should be useful to you:
fn CreateDynamicDllClass =
(
global dynamicDll
if dynamicDll == undefined OR classof dynamicDll != dotnetobject OR NOT matchpattern ( dynamicDll.ToString() ) pattern:"Dynamics.DynamicDll" do
(
source = ""
append source "using System;
"
append source "using System.Reflection;
"
append source "using System.IO;
"
append source "namespace Dynamics
"
append source "{
" -- open namespace Dynamics
append source "public class DynamicDll
"
append source "{
" -- open class
append source "public static Assembly GetAssembly(string filename)
"
append source "{
" -- open GetAssembly Method
append source "Assembly library = null;
"
append source "using (FileStream fs = File.Open(filename, FileMode.Open))
"
append source "{
" -- open using FileStream
append source "using (MemoryStream ms = new MemoryStream())
"
append source "{
" -- open using MemoryStream
append source "byte[] buffer = new byte[1024];
"
append source "int read = 0;
"
append source "while ((read = fs.Read(buffer, 0, 1024)) > 0)
"
append source "ms.Write(buffer, 0, read);
"
append source "library = Assembly.Load(ms.ToArray());
"
append source "}
" -- end using MemoryStream
append source "}
" -- end using FileStream
append source "return library;
"
append source "}
" -- end GetAssembly Method
append source "}
" -- end class
append source "}
" -- end namespace Dynamics
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.GenerateInMemory = on
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
dynamicDll = compilerResults.CompiledAssembly.CreateInstance "Dynamics.DynamicDll"
)
dynamicDll
)
CreateDynamicDllClass()
dynamicDll.GetAssembly "J:\\bin\\assemblies\\assembly.dll"
basically it reads the dll into a byte array and calls assembly.Load on that byte array. You could probably do all of the above in pure maxscript but at the time I must have decided this way was easier / better. As long as you have read access to the file on your network it should work.
I started learning Maxscript 2 days ago and I’m now working on a script wich can save data to an XML file, the writing isn’t much of a problem but I’m stuck with the removing of a XML node.
I have a treeview in which I present the data from the XML file, the objects in the XML node have a name attribute and the text in the treeview corresponds to this name.
What I want to do is remove the item that is selected from the treeview and the XML node, removing it from the treeview isn’t a problem but I can’t get it removed from the XML node.
I assume that I’ve to use xmlDoc.removeChild but I don’t know how I can select the node I want, I’ve tried xmlDoc.selectSingleNode and xmlDoc.getElementsByTagName but I can’t figure out how to get it working.
on deleteGroup pressed do
(
root.removeChild ???????????
tv.nodes.remove tv.selectedNode
)
Hopefully someone can point me in the right direction.
Why do you need to unload it? Just load newly compiled assembly by Gravey’s way… Just it.
Becouse I needed restarting the 3dsMax after changing and recompiling my assembly dll.
The new options and changed in recompiled assembly can not be used before restarting the programm.
I had a request on LR.net to ask if it was possible to get associated file icons into 3dsMax. Just wanted to share what I came up with -
fn FileTypeIconClass =
(
source = ""
source += "Imports System.Drawing
"
source += "Imports Microsoft.VisualBasic
"
source += "Imports System.Windows.Forms
"
source += "Public Class FileTypeIcons
"
source += "Dim cIcons As New System.Collections.Hashtable
"
source += "Public Declare Auto Function SHGetFileInfo Lib \"shell32.dll\" (ByVal pszPath As String, ByVal dwFileAttributes As Integer, ByRef psfi As SHFILEINFO, ByVal cbFileInfo As Integer, ByVal uFlags As Integer) As System.IntPtr
"
source += "Public Const SHGFI_ICON As Integer = &H100
"
source += "Public Const SHGFI_SMALLICON As Integer = &H1
"
source += "Structure SHFILEINFO
"
source += "Public hIcon As System.IntPtr
"
source += "Public iIcon As Integer
"
source += "Public dwAttributes As Integer
"
source += "<System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> Public szDisplayName As String
"
source += "<System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=80)> Public szTypeName As String
"
source += "End Structure
"
source += "Private Function RetrieveShellIcon(ByVal argPath As String) As Image
"
source += "Dim mShellFileInfo As SHFILEINFO
"
source += "Dim mSmallImage As System.IntPtr
"
source += "Dim mIcon As System.Drawing.Icon
"
source += "Dim mCompositeImage As System.Drawing.Image
"
source += "mShellFileInfo = New SHFILEINFO
"
source += "mShellFileInfo.szDisplayName = New String(Strings.Chr(0), 260)
"
source += "mShellFileInfo.szTypeName = New String(Strings.Chr(0), 80)
"
source += "mSmallImage = SHGetFileInfo(argPath, 0, mShellFileInfo, System.Runtime.InteropServices.Marshal.SizeOf(mShellFileInfo), SHGFI_ICON Or SHGFI_SMALLICON)
"
source += "Try
"
source += "mIcon = System.Drawing.Icon.FromHandle(mShellFileInfo.hIcon)
"
source += "mCompositeImage = mIcon.ToBitmap
"
source += "Catch ex As System.Exception
"
source += "mCompositeImage = New Bitmap(16, 16)
"
source += "End Try
"
source += "Return mCompositeImage
"
source += "End Function
"
source += "Public Function GetIcon(ByVal argFilePath As String) As Image
"
source += "Dim mFileExtension As String = System.IO.Path.GetExtension(argFilePath)
"
source += "If cIcons.ContainsKey(mFileExtension) = False Then
"
source += "cIcons.Add(mFileExtension, RetrieveShellIcon(argFilePath))
"
source += "End If
"
source += "Return cIcons(mFileExtension)
"
source += "End Function
"
source += "End Class"
VBProvider = dotnetobject "Microsoft.VisualBasic.VBCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.ReferencedAssemblies.add "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll"
compilerParams.ReferencedAssemblies.add "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll"
compilerParams.GenerateInMemory = on
compilerResults = VBProvider.CompileAssemblyFromSource compilerParams #(source)
-- this is very useful to debug your source code and check for referencing errors
if (compilerResults.Errors.Count > 0 ) then
(
errs = stringstream ""
for i = 0 to (compilerResults.Errors.Count-1) do
(
err = compilerResults.Errors.Item[i]
format "Error:% Line:% Column:% %
" err.ErrorNumber err.Line \
err.Column err.ErrorText to:errs
)
MessageBox (errs as string) title: "Errors encountered while compiling VB code"
return undefined
)
-- create a dotnetobject from the class
return compilerResults.CompiledAssembly.CreateInstance "FileTypeIcons"
)
FileTypeIconClass()
fn CreateTransControls forceRecompile:on = if forceRecompile do
(
source = ""
source += "using System;
"
source += "using System.Drawing;
"
source += "using System.Windows.Forms;
"
source += "namespace FormEx
"
source += "{
"
source += " public class TransControl : Control
"
source += " {
"
source += " private Point textLocation = new Point(0,0);
"
source += " private double opacity = 1;
"
source += " private int alpha = 1;
"
source += " public TransControl()
"
source += " {
"
source += " SetStyle(ControlStyles.SupportsTransparentBackColor, true);
"
source += " SetStyle(ControlStyles.Opaque, true);
"
source += " this.BackColor = Color.Transparent;
"
source += " }
"
source += " public Point TextLocation
"
source += " {
"
source += " get { return this.textLocation; }
"
source += " set
"
source += " {
"
source += " this.textLocation = value;
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " }
"
source += " }
"
source += " public double Opacity
"
source += " {
"
source += " get
"
source += " {
"
source += " return this.opacity;
"
source += " }
"
source += " set
"
source += " {
"
source += " this.opacity = (value > 1) ? 1 : (value < 0) ? 0 : value;
"
source += " this.alpha = (int)(this.opacity * 255);
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " }
"
source += " }
"
source += " protected override CreateParams CreateParams
"
source += " {
"
source += " get
"
source += " {
"
source += " CreateParams cp = base.CreateParams;
"
source += " cp.ExStyle |= 0x20;
"
source += " return cp;
"
source += " }
"
source += " }
"
source += " protected override void OnPaint(PaintEventArgs e)
"
source += " {
"
source += " Graphics g = e.Graphics;
"
source += " Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
"
source += " SolidBrush foreBrush = new SolidBrush(Color.FromArgb(alpha, this.ForeColor));
"
source += " SolidBrush backBrush = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
"
source += " if (this.BackColor != Color.Transparent) { g.FillRectangle(backBrush, bounds); }
"
source += " if (this.ForeColor != Color.Transparent) { g.DrawString(this.Text, this.Font, foreBrush, (PointF)this.textLocation); }
"
source += " foreBrush.Dispose();
"
source += " backBrush.Dispose();
"
source += " g.Dispose();
"
source += " base.OnPaint(e);
"
source += " }
"
source += " protected override void OnTextChanged(EventArgs e)
"
source += " {
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " base.OnTextChanged(e);
"
source += " }
"
source += " protected override void OnSizeChanged(EventArgs e)
"
source += " {
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " base.OnSizeChanged(e);
"
source += " }
"
source += " protected override void OnLocationChanged(EventArgs e)
"
source += " {
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " base.OnLocationChanged(e);
"
source += " }
"
source += " protected override void OnForeColorChanged(EventArgs e)
"
source += " {
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " base.OnForeColorChanged(e);
"
source += " }
"
source += " protected override void OnBackColorChanged(EventArgs e)
"
source += " {
"
source += " if (this.Parent != null) Parent.Invalidate(this.Bounds, true);
"
source += " base.OnBackColorChanged(e);
"
source += " }
"
source += " protected override void OnParentBackColorChanged(EventArgs e)
"
source += " {
"
source += " this.Invalidate();
"
source += " base.OnParentBackColorChanged(e);
"
source += " }
"
source += " }
"
source += "}
"
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.ReferencedAssemblies.AddRange #("System.dll", "System.Drawing.dll", "System.Windows.Forms.dll")
compilerParams.GenerateInMemory = true
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
if (compilerResults.Errors.Count > 0 ) then
(
errs = stringstream ""
for i = 0 to (compilerResults.Errors.Count-1) do
(
err = compilerResults.Errors.Item[i]
format "Error:% Line:% Column:% %
" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
)
MessageBox (errs as string) title: "Errors encountered while compiling C# code"
format "%
" errs
global TransControls = undefined
)
else
(
global TransControls = compilerResults.CompiledAssembly
)
)
CreateTransControls()
if TransControls != undefined do
(
global wf = dotNetObject "MaxCustomControls.MaxForm"
lb_1 = dotNetObject "Label"
lb_1.Text = "Label 01"
lb_1.BackColor = lb_1.BackColor.Red
lb_1.Bounds = (dotNetObject "System.Drawing.Rectangle" 50 50 100 100)
global lb_2 = TransControls.CreateInstance "FormEx.TransControl"
lb_2.Text = "XOPOIIIO !"
-- lb_2.Text = "Label 02"
lb_2.Opacity = 0.5
lb_2.ForeColor = lb_2.foreColor.Black
lb_2.BackColor = lb_2.backColor.Yellow --Transparent
lb_2.Bounds = (dotNetObject "System.Drawing.Rectangle" 80 80 100 100)
wf.controls.add lb_2
wf.controls.add lb_1
fn onClick s e = print "Click!"
dotNet.addEventHandler lb_2 "Click" onClick
wf.showmodeless()
)
Please let me know if anything is not working (or if you have any questions)…
Hello maxscripters,
if you are interested in WPF in maxscript I’m starting my blog here.
WPF under maxscript looks very promising :bounce:
I also have a blog with some Max/WPF information. It’s definitely the way to go for making tools in Max now. WPF is a great addition to MaxScript when it comes to making custom tools, and I look forward to see what you come up with. Thanks for sharing.
I have same problems as Track when using Graveys script, I still need to restart max to make it understand the assembly has changed. Or did we understand wrong how the script should work?
As I stated in my previous post, I was never able to successfully get a dll to load/unload dynamically. The variables/class names etc in the code provided in that post may be deceptively named dynamicDll but it does not do what you want. It was posted as a solution to loading a dotnet dll from a network. I have not worked on dynamic loading/unloading since sometime before that post.
One idea I didn’t try was to increment the version number of the dll(s) to be loaded with each build. I vaguely remember reading something about this somewhere on the internet… I have no idea if it will work or not but it’s an idea if you want to look into it.
after several steps of optimization I got a final “dynamic” way of loading DLL:
(dotnetclass "System.Reflection.Assembly").Load ((dotnetclass "System.IO.File").ReadAllBytes dll_filename)
it creates new assembly the same way as in your code. And of course we have to use CreateInstance assembly method to create classes from newly created assembly. MAX dontenobject and dotnetclass methods create object(class) using first loaded assembly.