.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

synchronicity, friends, & acedcommand

13 REPLIES 13
SOLVED
Reply
Message 1 of 14
Anonymous
5719 Views, 13 Replies

synchronicity, friends, & acedcommand

Anonymous
Not applicable

I had a VBA routine that worked using SENDCOMMAND.

my VB.NET version does not work using SENDCOMMAND (well, it does work, just not at the right time).

 

I've looking into the problem I have found that SENDCOMMAND is asynchronous in the document context but not in the application context. What does that mean? There is document.editor.SENDCOMMAND, but no application.SENDCOMMAND (or is there?).

 

So I tried using acedcommand (&/or acedcmd) but i'm getting an IDE error saying "AcEdCommand' is not accessible in this context because it is 'Friend' "

 

what is the proper syntax or methodology for using acedcommand?

0 Likes

synchronicity, friends, & acedcommand

I had a VBA routine that worked using SENDCOMMAND.

my VB.NET version does not work using SENDCOMMAND (well, it does work, just not at the right time).

 

I've looking into the problem I have found that SENDCOMMAND is asynchronous in the document context but not in the application context. What does that mean? There is document.editor.SENDCOMMAND, but no application.SENDCOMMAND (or is there?).

 

So I tried using acedcommand (&/or acedcmd) but i'm getting an IDE error saying "AcEdCommand' is not accessible in this context because it is 'Friend' "

 

what is the proper syntax or methodology for using acedcommand?

13 REPLIES 13
Message 2 of 14
Alexander.Rivilis
in reply to: Anonymous

Alexander.Rivilis
Mentor
Mentor

Synchronously Send (and wait for) commands in AutoCAD using C# .NET

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"


Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
Expert Elite Member

0 Likes

Synchronously Send (and wait for) commands in AutoCAD using C# .NET

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"


Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
Expert Elite Member

Message 3 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

Thanks for the link, but my C knowledge is very weak, I'm using VB.

 

what does this do:

 

private static extern int acedCmd(System.IntPtrvlist);

 

is it declaring a integer variable named acedcmd? no, not with an arguement.

 

so is it prototyping a function named acedcmd that has an arguement in the form of a system.intptrvlist type and returns an integer? & the intptrvlist is essentially the list of strings that you want to send?

 

but isn't acedcmd aldready a function?

0 Likes

Thanks for the link, but my C knowledge is very weak, I'm using VB.

 

what does this do:

 

private static extern int acedCmd(System.IntPtrvlist);

 

is it declaring a integer variable named acedcmd? no, not with an arguement.

 

so is it prototyping a function named acedcmd that has an arguement in the form of a system.intptrvlist type and returns an integer? & the intptrvlist is essentially the list of strings that you want to send?

 

but isn't acedcmd aldready a function?

Message 4 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable
[DllImport("acad.exe", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCmd")]
private static extern int acedCmd(System.IntPtr vlist);

 The whole of the above is the code needed for PInvoke of acedCmd.  The attribute (stuff between []) is the import of the command and it decorates the private static extern int acedCmd(System.IntPtr vlist) so the acedCmd is available in your code.

 

The System.IntPtr vlist is actually a result buffer that is created a few lines below:

 

ResultBuffer rb = new ResultBuffer();

  // RTSTR = 5005
 
  rb.Add(new TypedValue(5005, "_.INSERT"));
 
  // start the insert command
 
  acedCmd(rb.UnmanagedObject);

After you have the PInvoke and extern declared you can use the acedCmd as above, the result buffer holds a string (code 5005) and in this case the string is the insert command.  This is how the acedCmd is used, you cant just send it a string directly, you need to use result buffers. 

 

If you look at the documentation for result buffer you will see the different codes and how they work.  If you google PInvoke you can get more details on how it works and what exactly is going on.

 

From the documentation the returned int:

 

The return value of acedCmd() does not indicate the success or failure of a command. The function usually returns RTNORM. It returns RTCAN if the user cancels the command by pressing [Ctrl]+[C] or [Ctrl]+[Break]. It returns RTERROR or RTREJ to indicate a failure of undefined origin, not a command failure.

 

http://docs.autodesk.com/ACDMAC/2012/ENU/ObjectARX%20Reference/index.html?frmname=topic&frmfile=aced...

 

RTNORM and RTCAN etc are integers defined somewhere else as constants in the ObjectARX files.  Not sure off the top of my head where though.

 

 

 One more edit, the private static extern int acedCmd(System.IntPtr vlist)

You see the System.IntPtr vlist in there, and from the documentation above you read:

 

int acedCmd(const struct resbuf * rbp);

 

Pointer to a result-buffer list, in which each buffer specifies a data item whose value is passed to AutoCAD as if it were entered at the Command prompt
 
This is why the System.IntPtr vlist is in the command declaration, the underlying command you are Pinvoke'ing defines it takes a pointer to a result buffer, so this needs to be in the command declaration as it is.
0 Likes

[DllImport("acad.exe", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCmd")]
private static extern int acedCmd(System.IntPtr vlist);

 The whole of the above is the code needed for PInvoke of acedCmd.  The attribute (stuff between []) is the import of the command and it decorates the private static extern int acedCmd(System.IntPtr vlist) so the acedCmd is available in your code.

 

The System.IntPtr vlist is actually a result buffer that is created a few lines below:

 

ResultBuffer rb = new ResultBuffer();

  // RTSTR = 5005
 
  rb.Add(new TypedValue(5005, "_.INSERT"));
 
  // start the insert command
 
  acedCmd(rb.UnmanagedObject);

After you have the PInvoke and extern declared you can use the acedCmd as above, the result buffer holds a string (code 5005) and in this case the string is the insert command.  This is how the acedCmd is used, you cant just send it a string directly, you need to use result buffers. 

 

If you look at the documentation for result buffer you will see the different codes and how they work.  If you google PInvoke you can get more details on how it works and what exactly is going on.

 

From the documentation the returned int:

 

The return value of acedCmd() does not indicate the success or failure of a command. The function usually returns RTNORM. It returns RTCAN if the user cancels the command by pressing [Ctrl]+[C] or [Ctrl]+[Break]. It returns RTERROR or RTREJ to indicate a failure of undefined origin, not a command failure.

 

http://docs.autodesk.com/ACDMAC/2012/ENU/ObjectARX%20Reference/index.html?frmname=topic&frmfile=aced...

 

RTNORM and RTCAN etc are integers defined somewhere else as constants in the ObjectARX files.  Not sure off the top of my head where though.

 

 

 One more edit, the private static extern int acedCmd(System.IntPtr vlist)

You see the System.IntPtr vlist in there, and from the documentation above you read:

 

int acedCmd(const struct resbuf * rbp);

 

Pointer to a result-buffer list, in which each buffer specifies a data item whose value is passed to AutoCAD as if it were entered at the Command prompt
 
This is why the System.IntPtr vlist is in the command declaration, the underlying command you are Pinvoke'ing defines it takes a pointer to a result buffer, so this needs to be in the command declaration as it is.
Message 5 of 14
_gile
in reply to: Anonymous

_gile
Mentor
Mentor

Hi,

 

IMO, you'd rather try to hard code what the native command do.

Most of the time it's more easy than understanding how to P/invoke unmanaged methods and it's a good way to learn the API.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Hi,

 

IMO, you'd rather try to hard code what the native command do.

Most of the time it's more easy than understanding how to P/invoke unmanaged methods and it's a good way to learn the API.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 6 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

yeah, looks like I might have to. The thing is that I already have a fully functional VBA routine that has a lot going on. To match the funtionality of VPLAYER, I'm going to have to do a great deal of additional work involving searching layertables & retrieving IDs. Just a lot of extra work to replicate something that already works in VBA. + the viewport object's methods don't cover all of the functinality of VPLAYER, therefore I have to write additional alogorithms to replicate the functionality that my VBA routine provided via VPLAYER. 

 

It is rather irritating.

 

VB.NET, for me, is like using a jet fighter to go to the store to get some beer instead of using a car (VBA), when you have a car that runs & you know how to drive very well, but someone (microsoft) won't let you drive it anymore.

 

 

0 Likes

yeah, looks like I might have to. The thing is that I already have a fully functional VBA routine that has a lot going on. To match the funtionality of VPLAYER, I'm going to have to do a great deal of additional work involving searching layertables & retrieving IDs. Just a lot of extra work to replicate something that already works in VBA. + the viewport object's methods don't cover all of the functinality of VPLAYER, therefore I have to write additional alogorithms to replicate the functionality that my VBA routine provided via VPLAYER. 

 

It is rather irritating.

 

VB.NET, for me, is like using a jet fighter to go to the store to get some beer instead of using a car (VBA), when you have a car that runs & you know how to drive very well, but someone (microsoft) won't let you drive it anymore.

 

 

Message 7 of 14
_gile
in reply to: Anonymous

_gile
Mentor
Mentor
Accepted solution

I agree.
By my side, to get some beers at the store, I'd rather use AutoLISP with which you can easily script native commands.

Anyway, here's a little helper to use acedCmd with managed code (inspired by Tony Tanzillo's CommandLine (thanks again to him).
The Command method build the resultbuffer with the passed arguments.

EDIT: with A2013, replace "acad.exe" with "accore.dll" in the DllImport attribute arguments.

C#

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acad.exe", EntryPoint = "acedCmd",
            CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        extern static private int acedCmd(IntPtr resbuf);
 
        /// <summary>
        /// Call an AutoCAD command (runs synchronously).
        /// </summary>
        /// <param name="args">The command name followed by command inputs.</param>
        public static void Command(params object[] args)
        {
            ResultBuffer resbuf = new ResultBuffer();
            foreach (object obj in args)
            {
                switch (obj.GetType().Name)
                {
                    case "String":
                        resbuf.Add(new TypedValue((int)LispDataType.Text, obj)); break;
                    case "Int16":
                        resbuf.Add(new TypedValue((int)LispDataType.Int16, obj)); break;
                    case "Int32":
                        resbuf.Add(new TypedValue((int)LispDataType.Int32, obj)); break;
                    case "Double":
                        resbuf.Add(new TypedValue((int)LispDataType.Double, obj)); break;
                    case "Point2d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point2d, obj)); break;
                    case "Point3d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point3d, obj)); break;
                    case "ObjectId":
                        resbuf.Add(new TypedValue((int)LispDataType.ObjectId, obj)); break;
                    case "ObjectId[]":
                        foreach (ObjectId id in (ObjectId[])obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id)); 
                        break;
                    case "ObjectIdCollection":
                        foreach (ObjectId id in (ObjectIdCollection)obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id));
                        break;
                    case "SelectionSetDelayMarshalled":
                    case "SelectionSetFullyMarshalled":
                        resbuf.Add(new TypedValue((int)LispDataType.SelectionSet, obj)); break;
                    default:
                        throw new InvalidOperationException("Unsupported type in Command() method");
                }
            }
            acedCmd(resbuf.UnmanagedObject);
        }

 VB

        <System.Security.SuppressUnmanagedCodeSecurity()> _
        <DllImport("acad.exe", EntryPoint:="acedCmd", CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Function acedCmd(resbuf As IntPtr) As Integer
        End Function
 
        '''<summary>
        ''' Call an AutoCAD command (runs synchronously).
        ''' </summary>
        ''' <param name="args">The command name followed by command inputs.</param>
        Public Shared Sub Command(ParamArray args As Object())
            Dim resbuf As New ResultBuffer()
            For Each obj As Object In args
                Select Case obj.GetType().Name
                    Case "String"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Text), obj))
                        Exit Select
                    Case "Int16"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int16), obj))
                        Exit Select
                    Case "Int32"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int32), obj))
                        Exit Select
                    Case "Double"
                        resbuf.Add(New TypedValue(CInt(LispDataType.[Double]), obj))
                        Exit Select
                    Case "Point2d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point2d), obj))
                        Exit Select
                    Case "Point3d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point3d), obj))
                        Exit Select
                    Case "ObjectId"
                        resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), obj))
                        Exit Select
                    Case "ObjectId[]"
                        For Each id As ObjectId In DirectCast(obj, ObjectId())
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "ObjectIdCollection"
                        For Each id As ObjectId In DirectCast(obj, ObjectIdCollection)
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "SelectionSetDelayMarshalled", "SelectionSetFullyMarshalled"
                        resbuf.Add(New TypedValue(CInt(LispDataType.SelectionSet), obj))
                        Exit Select
                    Case Else
                        Throw New InvalidOperationException("Unsupported type in Command() method")
                End Select
            Next
            acedCmd(resbuf.UnmanagedObject)
        End Sub

 

Using examples (VB):

Draws a line.

Command("_.line", New Point3d(10.0, 20.0, 0.0), New Point3d(80.0, 50.0, 0.0), "")

 Freezes the "0" layer in the selected viewport.

Command("_.vplayer", "_freeze", "0", "_select", "\", "", "")

 Note:

- an empty string ("") means a validation (Enter)
- an anti-slash ("\" with VB or "\\" with C#) means a pause for user input.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

I agree.
By my side, to get some beers at the store, I'd rather use AutoLISP with which you can easily script native commands.

Anyway, here's a little helper to use acedCmd with managed code (inspired by Tony Tanzillo's CommandLine (thanks again to him).
The Command method build the resultbuffer with the passed arguments.

EDIT: with A2013, replace "acad.exe" with "accore.dll" in the DllImport attribute arguments.

C#

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acad.exe", EntryPoint = "acedCmd",
            CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        extern static private int acedCmd(IntPtr resbuf);
 
        /// <summary>
        /// Call an AutoCAD command (runs synchronously).
        /// </summary>
        /// <param name="args">The command name followed by command inputs.</param>
        public static void Command(params object[] args)
        {
            ResultBuffer resbuf = new ResultBuffer();
            foreach (object obj in args)
            {
                switch (obj.GetType().Name)
                {
                    case "String":
                        resbuf.Add(new TypedValue((int)LispDataType.Text, obj)); break;
                    case "Int16":
                        resbuf.Add(new TypedValue((int)LispDataType.Int16, obj)); break;
                    case "Int32":
                        resbuf.Add(new TypedValue((int)LispDataType.Int32, obj)); break;
                    case "Double":
                        resbuf.Add(new TypedValue((int)LispDataType.Double, obj)); break;
                    case "Point2d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point2d, obj)); break;
                    case "Point3d":
                        resbuf.Add(new TypedValue((int)LispDataType.Point3d, obj)); break;
                    case "ObjectId":
                        resbuf.Add(new TypedValue((int)LispDataType.ObjectId, obj)); break;
                    case "ObjectId[]":
                        foreach (ObjectId id in (ObjectId[])obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id)); 
                        break;
                    case "ObjectIdCollection":
                        foreach (ObjectId id in (ObjectIdCollection)obj)
                            resbuf.Add(new TypedValue((int)LispDataType.ObjectId, id));
                        break;
                    case "SelectionSetDelayMarshalled":
                    case "SelectionSetFullyMarshalled":
                        resbuf.Add(new TypedValue((int)LispDataType.SelectionSet, obj)); break;
                    default:
                        throw new InvalidOperationException("Unsupported type in Command() method");
                }
            }
            acedCmd(resbuf.UnmanagedObject);
        }

 VB

        <System.Security.SuppressUnmanagedCodeSecurity()> _
        <DllImport("acad.exe", EntryPoint:="acedCmd", CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Function acedCmd(resbuf As IntPtr) As Integer
        End Function
 
        '''<summary>
        ''' Call an AutoCAD command (runs synchronously).
        ''' </summary>
        ''' <param name="args">The command name followed by command inputs.</param>
        Public Shared Sub Command(ParamArray args As Object())
            Dim resbuf As New ResultBuffer()
            For Each obj As Object In args
                Select Case obj.GetType().Name
                    Case "String"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Text), obj))
                        Exit Select
                    Case "Int16"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int16), obj))
                        Exit Select
                    Case "Int32"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Int32), obj))
                        Exit Select
                    Case "Double"
                        resbuf.Add(New TypedValue(CInt(LispDataType.[Double]), obj))
                        Exit Select
                    Case "Point2d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point2d), obj))
                        Exit Select
                    Case "Point3d"
                        resbuf.Add(New TypedValue(CInt(LispDataType.Point3d), obj))
                        Exit Select
                    Case "ObjectId"
                        resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), obj))
                        Exit Select
                    Case "ObjectId[]"
                        For Each id As ObjectId In DirectCast(obj, ObjectId())
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "ObjectIdCollection"
                        For Each id As ObjectId In DirectCast(obj, ObjectIdCollection)
                            resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id))
                        Next
                        Exit Select
                    Case "SelectionSetDelayMarshalled", "SelectionSetFullyMarshalled"
                        resbuf.Add(New TypedValue(CInt(LispDataType.SelectionSet), obj))
                        Exit Select
                    Case Else
                        Throw New InvalidOperationException("Unsupported type in Command() method")
                End Select
            Next
            acedCmd(resbuf.UnmanagedObject)
        End Sub

 

Using examples (VB):

Draws a line.

Command("_.line", New Point3d(10.0, 20.0, 0.0), New Point3d(80.0, 50.0, 0.0), "")

 Freezes the "0" layer in the selected viewport.

Command("_.vplayer", "_freeze", "0", "_select", "\", "", "")

 Note:

- an empty string ("") means a validation (Enter)
- an anti-slash ("\" with VB or "\\" with C#) means a pause for user input.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Tags (2)
Message 8 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

thank you so much, that works in a very awesome manner.

0 Likes

thank you so much, that works in a very awesome manner.

Message 9 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

how can I use LISP functions or custom commands with that Tony's method?

I can't figure that out

0 Likes

how can I use LISP functions or custom commands with that Tony's method?

I can't figure that out

Message 10 of 14
_gile
in reply to: Anonymous

_gile
Mentor
Mentor

If you want to run LISP functions, you'd rather use the Application.Invoke() method (A2011 or later) or P/Invoke the unmanaged acedInvoke() method.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes

If you want to run LISP functions, you'd rather use the Application.Invoke() method (A2011 or later) or P/Invoke the unmanaged acedInvoke() method.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 11 of 14
Anonymous
in reply to: _gile

Anonymous
Not applicable

thanks, it works for commands, but I can't find the right syntax for functions

 

i used a workaround, where i defined a command that just calls the function i need

0 Likes

thanks, it works for commands, but I can't find the right syntax for functions

 

i used a workaround, where i defined a command that just calls the function i need

Message 12 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

in 2014, for the Case ObjectId[],

this line is givng a run time error (unsupported type in command() method )"

 

resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id)

 

Not sure if this case ever woked in previous versions. This is a for a new routine for civil 3d featurelines

0 Likes

in 2014, for the Case ObjectId[],

this line is givng a run time error (unsupported type in command() method )"

 

resbuf.Add(New TypedValue(CInt(LispDataType.ObjectId), id)

 

Not sure if this case ever woked in previous versions. This is a for a new routine for civil 3d featurelines

Message 13 of 14
_gile
in reply to: Anonymous

_gile
Mentor
Mentor

Hi,

 

Tony Tanzillo shared a wrapper for the undocumented runCommand() method which is safer the P/Invoking acedCmd.

Have a look here.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes

Hi,

 

Tony Tanzillo shared a wrapper for the undocumented runCommand() method which is safer the P/Invoking acedCmd.

Have a look here.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 14 of 14
Anonymous
in reply to: Anonymous

Anonymous
Not applicable

so actually my problem was that I was sending a 3d point object to the _aeccbreakfeatureline command, which expects a station, so it nothing to do with the code in question.

0 Likes

so actually my problem was that I was sending a 3d point object to the _aeccbreakfeatureline command, which expects a station, so it nothing to do with the code in question.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

AutoCAD Inside the Factory


Autodesk Design & Make Report