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

Retrieve Command names from ARX file

23 REPLIES 23
Reply
Message 1 of 24
alex_b
2308 Views, 23 Replies

Retrieve Command names from ARX file

Hi,

 

I'm looking for a way of retrieving command names defined in an arx/dbx file, and doing it from an external process (i.e. without loading the file into AutoCAD).

Ditto for checking the Acad version the arx was compiled for.

Any pointers will be much appreciated.

 

Thanks in advance

 

alex

23 REPLIES 23
Message 2 of 24
Alexander.Rivilis
in reply to: alex_b

Alex!

Without loading arx-file into AutoCAD you can not retrieve it's commands. More over arx-application can define/undefine commands real-time. 

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | 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 24
alex_b
in reply to: Alexander.Rivilis

Why not?

 

This is exactly what AutoCAD does: it reads the file, retrieves the command strings and adds them to its commands table, etc. This is of course very simplistic but I think it is possible, having the right knowledge about the inner workings, string encoding etc.

I did it for ,NET plug-ins; of course it's easier with Reflection but...

 

alex

Message 4 of 24
artc2
in reply to: alex_b

AutoCAD does NOT read the file and retreive the command strings.  The command strings are provided to AutoCAD by the application as it is initializing.  The applications uses calls to .AcEdCommandStack::addCommand() to register the command strings with AutoCAD.  And, as Alexander has pointed out, commands can be registered and unregistred at any point while the application is loaded, so even if you were to read the file and somehow get the command strings, you would not know if/when those command strings would be registered with AutoCAD.

Message 5 of 24
alex_b
in reply to: artc2

Art

I stand corrected

>>..so even if you were to read the file and somehow get the command strings, you would not know if/when those command strings would be registered with AutoCAD.

I could live with that. I need them for cataloguing purposes, not for using them inside AutoCAD.

Is there a way to get the command strings?

What about checking the Acad version the arx was compiled for?

Thanks

alex

Message 6 of 24
Alexander.Rivilis
in reply to: alex_b


@alex_b wrote:

Art

I stand corrected

>>..so even if you were to read the file and somehow get the command strings, you would not know if/when those command strings would be registered with AutoCAD.

I could live with that. I need them for cataloguing purposes, not for using them inside AutoCAD.

Is there a way to get the command strings?

What about checking the Acad version the arx was compiled for?

Thanks

alex


Alex!
As far as I remember you using ObjectARX 12 years ago.  That is why I think you have to know that in order to register command arx application must call appropriate function. Without this calling AutoCAD do not know about command.

So there are NO methods to identify commands in arx-file.

Moreover there are NO (official) methods to identify for which version of AutoCAD arx-file was compiled.  

 

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | 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 7 of 24
alex_b
in reply to: Alexander.Rivilis

Alexander

 

A for your memory. It is indeed 12 years, but I think at the time it was ADS not OARX. Nice of you to remember.

To the topic:

I know that inside AutoCAD the arx has to call addCommand() in order to let Acad 'know' about it.

But, what I'm looking for is just retrieving the command name strings using an out-of-process aplication.

The strings must be somwhere in the arx file, maybe encoded, but surely they can be found.

Ditto info about Acad version. The arx (or Acad) surely check version compatibility when the arx is loaded.

Granted the above info is not Officially published, but..

I'd hate to give up at this early stage.

 

alex

 

Message 8 of 24
khoa.ho
in reply to: alex_b

Alex,

I also thought there should be a way to read ARX (native DLL) file using .NET, so I jumped to find the solution right after work.

To read a native DLL file, we should use the way Windows works now is to use method LoadLibrary/LoadLibraryEx (defined in kernel32.dll). This will return a pointer for the loaded native DLL file. From there, we use GetProcAddress (and something else) to read defined methods (and other info) in the native DLL file.

ARX file has two special methods: acrxGetApiVersion and acrxEntryPoint. So they could be starting points to find ARX version and stored command names. ObjectARX uses acedRegCmds->addCommand inside method acrxEntryPoint() to add a new AutoCAD command, so it may be something we hope to find from there.

The following code has the problem of using delegates of acrxGetApiVersion and acrxEntryPoint in .NET. I don't know exactly their signatures (found from DllImport) so it does not work now. However, it does read an ARX file and return pointers of acrxGetApiVersion and acrxEntryPoint for us to get started.

We should create a new console application in Visual Studio and copy the following code to Program.cs

 

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace AcadReflection
{
    class Program
    {
        /// <summary>
        /// To load the dll - dllFilePath dosen't have to be const - so I can read path from registry
        /// </summary>
        /// <param name="dllFilePath">file path with file name</param>
        /// <param name="hFile">use IntPtr.Zero</param>
        /// <param name="dwFlags">What will happend during loading dll
        /// <para>DONT_RESOLVE_DLL_REFERENCES = 0x00000001</para>
        /// <para>LOAD_LIBRARY_AS_DATAFILE = 0x00000002</para>
        /// <para>LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008</para>
        /// <para>LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010</para>
        /// </param>
        /// <returns>Pointer to loaded Dll</returns>
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibraryEx(string dllFilePath, IntPtr hFile, uint dwFlags);

        /// <summary>
        /// To get function pointer from loaded dll 
        /// </summary>
        /// <param name="dllPointer">Pointer to Dll witch was returned from LoadLibraryEx</param>
        /// <param name="functionName">Function name with you want to call</param>
        /// <returns>Pointer to function</returns>
        [DllImport("kernel32", CharSet = CharSet.Ansi)]
        public extern static IntPtr GetProcAddress(IntPtr dllPointer, string functionName);

        private delegate string GetApiVersionAddress();
        private delegate int GetEntryPointAddress(int msg, IntPtr appId);

        static void Main(string[] args)
        {
            try
            {
                string assemblyFile = @"C:\Program Files\Autodesk\AutoCAD 2013\acblock.arx";

                // Load a native DLL file without loading dependencies
                IntPtr dllPointer = LoadLibraryEx(assemblyFile, IntPtr.Zero, 0x00000001);
                if (dllPointer == IntPtr.Zero)
                    throw new Win32Exception();

                IntPtr apiVersionAddress = GetProcAddress(dllPointer, "acrxGetApiVersion");
                if (apiVersionAddress == IntPtr.Zero)
                    throw new Win32Exception();

                IntPtr entryPointAddress = GetProcAddress(dllPointer, "acrxEntryPoint");
                if (entryPointAddress == IntPtr.Zero)
                    throw new Win32Exception();

                try
                {
                    var apiVersionAddressFunction = (GetApiVersionAddress)Marshal.GetDelegateForFunctionPointer(apiVersionAddress, typeof(GetApiVersionAddress));
                    var version = apiVersionAddressFunction();

                    var entryPointAddressFunction = (GetEntryPointAddress)Marshal.GetDelegateForFunctionPointer(entryPointAddress, typeof(GetEntryPointAddress));
                    var entryPoint = entryPointAddressFunction(1, IntPtr.Zero);
                }
                catch (AccessViolationException ex)
                {
                }
            }
            catch (Exception ex)
            {
            }
        }
    }
}

 

-Khoa

 

Message 9 of 24
alex_b
in reply to: khoa.ho

Khoa

 

THanks for replying.

I confess that the code you wrote is too advanced for me; I do not understand how you would get the parameter values of the call to acedRegCmds->addCommand() from inside acrxEntryPoint(), nor how we can get the return value of the call to acrxGetApiVersion().

Could you explain it a bit more?

Anyway, the signatures are:

[enum]AcRx::AppRetCode acrxEntryPoint([enum] AcRx::AppMsgCode msg, void* pkt)

acrxGetApiVersion(): can't get the signature (PRIVATE)

 

Thank you

 

alex

 NB

I found this on Google:

http://hi.baidu.com/qazqqcyq/item/35669b415875a88d833ae134

It looks like what we're trying to do.

I only I could read Chinese.

Message 10 of 24
khoa.ho
in reply to: alex_b

Alex,

 

AutoCAD uses AcEdCommandStack reactor in the native DLL accore.dll (A2013) and other reference calls somewhere in acdbxx.dll to add and remove commands. So the .NET console app will not work without importing necessary methods from those C++ assemblies. It is a big challenge so I will check again after work.

 

-Khoa

Message 11 of 24
alex_b
in reply to: khoa.ho

Khoa,

 

A crazy thought:

What if we implemented our own acedRegCmds->addCommand(), then call acrxEntryPoint() in our arx, somehow causing it to call our addCommand().

Once our method is called, it sees the command name string in the call parameters.

As for the acrxGetApiVersion(), it seems its return type is int.

 

alex

Message 12 of 24
khoa.ho
in reply to: alex_b

Alex,

 

ARX file is compiled by ObjectARX SDK so it has all dependent references inside. Those dependencies also have their other nested dependencies, and they are wiring each other to live in the same AutoCAD installation directory. When we call a method inside an ARX file, all related methods (in other native DLLs) will get called accordingly. So a .NET app that uses an ARX file should reference the world of AutoCAD native assemblies.

 

.NET plug-in has commands exposed as .NET attributes (CommandMethodAttribute), so it will be easier to get those commands using .NET reflection (with help from Mono.Cecil library).

 

See an example of registering commands in ARX (C++ code):

AcRx::AppRetCode
    acrxEntryPoint(AcRx::AppMsgCode msg, void* appId)
{
    switch (msg) {
    case AcRx::kInitAppMsg:
        acrxDynamicLinker->unlockApplication(appId);
        acrxDynamicLinker->registerAppMDIAware(appId);
        gpEdReac = new AsdkNODEdReactor();
        acedRegCmds->addCommand(_T("ASDK_CLONENOD_TEST"),
            _T("ASDK_NODSETUP"),
            _T("NODSETUP"),
            ACRX_CMD_TRANSPARENT,
            setup);
        break;
    case AcRx::kUnloadAppMsg:
        clearReactor();
        acedRegCmds->removeGroup(_T("ASDK_CLONENOD_TEST"));
        delete gpEdReac;
    }
    return AcRx::kRetOK;
}

 

ARX plug-in defines a command as code content of acrxEntryPoint method, without exposing outside to retrieve. So unless we can decompile C++ code to read those commands, we need to call acrxEntryPoint method to ask it to run acedRegCmds->addCommand(). This addCommand method will store defined commands somewhere. From there, we can retrieve those commands that we need to find.

 

acedRegCmds is a macro of the class AcEdCommandStack with method addCommand. AcEdCommandStack comes from accmd.h which I don’t know exactly its DLL file.

 

A solution that I can think of is to create some “fake” native DLL files with names copied from AutoCAD real DLL files. Those “fake” DLL files will be implemented only some needed functions with non-actions, except addCommand will return a list of added commands, and we retrieve them from there using DllImport in .NET code.

 

Because ARX is C++ code so it should call another C++ code from its code. “Fake” or real AutoCAD native DLL files are still needed to reference in this ARX file. If we can decompile or binary-read ARX file to extract raw command strings, we can skip C++ calls and ignore all above explanations.

 

I try to make it clear and logic to solve this challenge topic. Not sure to find the final code but at least we can learn something of the complicated world of managed and unmanaged code living together in AutoCAD.

 

-Khoa

Message 13 of 24
alex_b
in reply to: khoa.ho

Khoa

 

This is looking more and more like hacking (in the legit sense of the word, of course) Smiley Happy

To the point:

I looked inside a few .h files and noticed that:

- the command strings are stored in a dictionary maintained by Acad, therefore, I'm not sure how we can look at it without a full Acad running.

- inside acrxEntryPoint() there can be any calls made, not just the traditional ones, therefore we might be forced to load all Acad native assemblies. If we have to do that, it may be simpler to load our arx into a running instance of Acad and use the OARX API with COM to retrieve the command stack contents (it will work of course only for installed versions of Acad, defeating part of the program purpose which is to display a list of defined commands together with the respective Acad versions they were compiled for, ideally without need for Acad to be even installed).

 

It seems that the simplest? way could be disassembling/binary reading the arx and retrieving the command name strings, but I haven't a clue how to do that. Do you know of any library a la Mono.Cecil which can do that for C++ code?

 

Now about acrxGetApiVersion(), things are better. It rerurns an int as:

 

0x000E0000 for V14

0x000F0000 for V2000-2002

0x00100000 for V2004-2006

0x00110000 for V2007-2009

0x00120000 for V2010-2012

 

I changed the delegate method type to int and the code you posted works OK.

It seems that acrxGetApiVersion() does not make any calls to methods in native dll's.

 

Thanks a lot for your efforts

 

alex

 

Message 14 of 24
Alexander.Rivilis
in reply to: alex_b


This is looking more and more like hacking (in the legit sense of the word, of course) Smiley Happy

+1000


It seems that the simplest? way could be disassembling/binary reading the arx and retrieving the command name strings, but I haven't a clue how to do that. Do you know of any library a la Mono.Cecil which can do that for C++ code?

1) What about command name is encrypted (protection) or reading from other file/files (localization)?
2) What about command is registering not inside acrxEntryPoint() function? It is also possible.

3) What about arx-registered lisp-function (with help of acedDefun() function)?


Now about acrxGetApiVersion(), things are better. It rerurns an int as:

 

0x000E0000 for V14

0x000F0000 for V2000-2002

0x00100000 for V2004-2006

0x00110000 for V2007-2009

0x00120000 for V2010-2012
0x00130000 for V2013

 


But arx-application can be or x86 or x64. With external x86 .NET-application you can load only x86 arx-application, not x64 arx-application

 

 

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | 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 15 of 24
alex_b
in reply to: Alexander.Rivilis

Alexander

 

I am aware that there are limitations.

Some of them are probably solvable:

 

>>1) What about command name is encrypted (protection) or reading from other file/files (localization)?

 

Probably either not solvable or not worth the effort to break emcriptions etc. If the people who wrote the plug-in have reasons to be secretive, let them.


>>2) What about command is registering not inside acrxEntryPoint() function? It is also possible.

>>3) What about arx-registered lisp-function (with help of acedDefun() function)?

 

If the main solution is by disassembling or binary reading the arx, then we'll be able to find all the calls to addCommand() and to acedDefun() and retrieve the string arguments (hopefuly not encrypted).

 

As for acrxGetApiVersion(), it's true it may be x64. After solving the problem for x86, I'll give some thought to extending it to x64. By then, we'll maybe have x128.

 

Thanks

 

alex

Message 16 of 24
alex_b
in reply to: khoa.ho

Khoa

 

>>acedRegCmds is a macro of the class AcEdCommandStack with method addCommand. AcEdCommandStack comes from accmd.h which I don’t know exactly its DLL file.

 

It may be not in a dll but in Acad.exe In fact, I think it is in acad.exe

 

alex

Message 17 of 24
Alexander.Rivilis
in reply to: alex_b

addCommand is virtual method.

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | 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 18 of 24
khoa.ho
in reply to: alex_b

Alex,

 

I rewrite the code to make it clean and logic for future improvement, with break-down methods using IntPtr and Delegate. Therefore, the main code looks easier to read and is reusable.

 

The good thing is acrxGetApiVersion does not call any external C++ functions and it works to return a number that presents an AutoCAD version. I only tested on AutoCAD 2009 and 2013 with the return numbers are 1114112 and 1245184 accordingly. So you will check again the new code in your machine.

 

The not good thing as we know is acrxEntryPoint calls many C++ unsafe methods from many external native assembly files in the AutoCAD installation directory, and I also don’t know the IntPtr appId to pass into this method. So this function call will cause “Unsafe Invoke Internal method” errors. Even I tested to copy the .exe file to the AutoCAD folder.

 

To build this console app running as 32 or 64 bits, on Visual Studio Configuration Manager, switch to x86 or x64 and compile each cases. 32-bit x86 app does not read AutoCAD 64 bit ARX files. So it is better to change the platform to build x86 or x64 on VS.

 

See my latest code, with code refactor and without fix to find AutoCAD command names:

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace AcadReflection
{
    class Program
    {
        #region Import external native DLL functions
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr LoadLibrary(string name);

        /// <summary>
        /// To load the dll - dllFilePath dosen't have to be const - so I can read path from registry
        /// </summary>
        /// <param name="dllFilePath">file path with file name</param>
        /// <param name="hFile">use IntPtr.Zero</param>
        /// <param name="dwFlags">What will happend during loading dll
        /// <para>DONT_RESOLVE_DLL_REFERENCES = 0x00000001</para>
        /// <para>LOAD_LIBRARY_AS_DATAFILE = 0x00000002</para>
        /// <para>LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008</para>
        /// <para>LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010</para>
        /// </param>
        /// <returns>Pointer to loaded Dll</returns>
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibraryEx(string dllFilePath, IntPtr hFile, uint dwFlags);

        /// <summary>
        /// To get function pointer from loaded dll 
        /// </summary>
        /// <param name="dllPointer">Pointer to Dll witch was returned from LoadLibraryEx</param>
        /// <param name="functionName">Function name with you want to call</param>
        /// <returns>Pointer to function</returns>
        [DllImport("kernel32", CharSet = CharSet.Ansi)]
        public extern static IntPtr GetProcAddress(IntPtr dllPointer, string functionName);
        #endregion

        private delegate IntPtr ApiVersionMethod();
        private delegate IntPtr EntryPointMethod(int msg, IntPtr appId);

        static void Main(string[] args)
        {
            // Pass in a single file or many files -> use loop
            string assemblyFile = @"C:\Program Files\Autodesk\AutoCAD 2013\acblock.arx";

            ReadNativeAssembly(assemblyFile);
        }

        static void ReadNativeAssembly(string assemblyFile)
        {
            try
            {
                // Load a native DLL file
                IntPtr assemblyPointer = GetAssemblyPointer(assemblyFile);

                // Load pointers of given method names
                // Pointer to get AutoCAD version number
                IntPtr apiVersionPointer = GetAssemblyMethodPointer(assemblyPointer, "acrxGetApiVersion");
                // Pointer to get method that assigns AutoCAD command names
                IntPtr entryPointPointer = GetAssemblyMethodPointer(assemblyPointer, "acrxEntryPoint");

                // Get references of methods from found method pointers
                Delegate apiVersionMethod = GetMethodFromPointer(apiVersionPointer, typeof(ApiVersionMethod));
                Delegate entryPointMethod = GetMethodFromPointer(entryPointPointer, typeof(EntryPointMethod));

                try
                {
                    var version = apiVersionMethod.DynamicInvoke();
                    WriteMessage("AutoCAD version number: " + version);

                    // Error comes here when trying to invoke unsafe methods from AutoCAD native assemblies
                    var entryPoint = entryPointMethod.DynamicInvoke(1, IntPtr.Zero);
                }
                catch (TargetInvocationException ex)
                {
                    WriteMessage(ex);
                }
            }
            catch (Exception ex)
            {
                WriteMessage(ex);
            }
        }

        #region Pointer and Delegate helper
        private static IntPtr GetAssemblyPointer(string assemblyFile)
        {
            try
            {
                // Load a native DLL file without loading dependencies
                return LoadLibraryEx(assemblyFile, IntPtr.Zero, 0x00000001);
            }
            catch (Exception ex)
            {
                WriteMessage(ex);
                return IntPtr.Zero;
            }
        }

        private static IntPtr GetAssemblyMethodPointer(IntPtr assemblyPointer, string methodName)
        {
            return GetProcAddress(assemblyPointer, methodName);
        }

        private static Delegate GetMethodFromPointer(IntPtr methodPointer, Type methodType)
        {
            return Marshal.GetDelegateForFunctionPointer(methodPointer, methodType);
        }
        #endregion

        #region Write to console
        private static void WriteMessage(string message)
        {
            Console.WriteLine(message);
        }

        private static void WriteMessage(Exception ex)
        {
            Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
        }

        private static void WriteMessage(TargetInvocationException ex)
        {
            Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
        }
        #endregion
    }
}

 

-Khoa

Message 19 of 24
alex_b
in reply to: khoa.ho

Khoa

 

About acrxGetApiVersion(), see my message 13 above: the nunber returned is the Acad major version left-shifted by 16 bits. There is no indication of the minor version, as it doesn't matter for the arx binary compatibility.

 

About acrxEntryPoint(), I'm not sure where we can go from here. Loading all the unsafe Acad methods seems too complicated and does not guarantee success.

Maybe the solution is binary reading the file, looking for the calls to addCommand(), finding their string arguments etc., but I know next to nothing about the innards of PE and how to find various offsets in a binary dll.

 

What do you think?

 

Thanks

 

alex

Message 20 of 24
Alexander.Rivilis
in reply to: alex_b


...About acrxEntryPoint(), I'm not sure where we can go from here. Loading all the unsafe Acad methods seems too complicated and does not guarantee success.

Maybe the solution is binary reading the file, looking for the calls to addCommand(), finding their string arguments etc., but I know next to nothing about the innards of PE and how to find various offsets in a binary dll...


As I've wrote above addCommand() is a virtual method. That is why you can not find it in dll-file. The Export table has not method addCommand. You can check it with help of dumpbin.exe utility or with Dependency Walker

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | 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

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

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost