Community
PowerShape and PowerMill API Forum
Welcome to Autodesk’s PowerShape and PowerMill API Forums. Share your knowledge, ask questions, and explore popular API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Tutorial - Connecting to Active PowerMill Instance Without DropDown Selection (C#)

7 REPLIES 7
Reply
Message 1 of 8
barr.jarryd
487 Views, 7 Replies

Tutorial - Connecting to Active PowerMill Instance Without DropDown Selection (C#)

Hey all, 

 

I've seen this quite a bit lately in the forums, about trying to connect to the active session with an API. Unfortunately due to the nature of Windows Processes, there is not a concrete way of doing this without generating a list of all the sessions and selecting it within the API.

 

That method has proven to be quite useful, although I find myself forgetting to update that list when running multiple sessions.

 

I have devised a way to 'Dock' an API (not plugin) into a PowerMill session (standalone API attached to the session itself) and have it connect and stay connected to that session. With this method you can run multiple sessions all with their own API that do not conflict with each other.

 

The only problem with this method is that the boot time for the API is a little slow on heavier sessions. I suggest running the API and minimizing it within the session until you need it; at launch of the session (pmuser).

 

I will provide the code for launching the API as well as the 'Docking' procedure code for the API itself. Since PowerMill is a little fickle, I will also provide the code to Minimize and Maximize the form without losing it (since the API does not appear on the task bar).

 

To start you will need to add this code to either your pmuser for boot, or to a standalone macro that you can run at any time:

 

CREATE PATTERN 'api_start'

ACTIVATE PATTERN 'api_start'

CD "YOUR API EXE FOLDER PATH" // USUALLY IN THE DEBUG FOLDER

INFOBOX NEW "PowerMill Information"
INFOBOX STYLE "NORMAL"
INFOBOX APPEND "Loading Automation API... Please wait."

ole fileaction 'open' 'YOUR API.exe'

 

 

This code will cause PowerMill to create a pattern that the API is going to look for. If the session does not have that pattern, it will skip it and carry on until it finds a session with that pattern. Once it's found that session with the pattern, it will connect to that session, dock to it, then delete the pattern that way any other instances don't connect to that session as well.

Here is the code that needs to be placed inside the .cs file of your API:

 

// ADD THIS DLL IMPORT IN FORM1 CLASS BEFORE FORM1 METHOD

[DllImport("user32.dll")]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

//ADJUST YOUR Form1_Load with this code to initiate the Search for the pattern

private void Form1_Load(object sender, EventArgs e)
{
    List<object> sessionList = PMAutomation.GetListOfPmComObjects();
    foreach (object obj in sessionList)
    {
        powerMill = new PMAutomation(obj);
        session = powerMill.ActiveProject;
        foreach (PMPattern pattern in session.Patterns)
        {
            if (pattern.Name == "api_start")
            {
                // Connect to the instance with the specific entity
                ConnectToInstance(obj);
                return; // Stop iterating if connected
            }
        }
    }
}

// ADD THIS METHOD AFTER SO THAT THE API CONNECTS TO THAT SESSION

private void ConnectToInstance(object obj)
{
    // Connect to the first session that has the pattern
    powerMill = new PMAutomation(obj);
    session = powerMill.ActiveProject;
    // Delete the pattern
    session.Patterns.ActiveItem.Delete();
}

 

 

What this code does is cycle through each session starting from the first session to find the session that holds the 'api_start' pattern. After it finds a session with the pattern, it stops the search and connects directly to that session. After that it proceeds to delete that pattern from the session and the API list so that it cannot be recalled.

This is the only step required to get your API to connect to a session without the use of dropdown menus.

 

This next section is about 'Docking' the API interface to the session. This will cause the interface to be exclusive to that session. It will limit it from leaving the view port of the session and stop it from connecting to any other session.

 

To get started we need adjust our source code as such:

 

// ADD THESE DLL IMPORTS IN FORM1 CLASS BEFORE THE FORM1 METHOD CALL

// Import necessary user32.dll functions
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

// ADJUST OUR FORM1_LOAD METHOD TO THIS

private void Form1_Load(object sender, EventArgs e)
{
    SetParent(this.Handle, GetMainWindowHandleOfActiveProcess());
    List<object> sessionList = PMAutomation.GetListOfPmComObjects();
    foreach (object obj in sessionList)
    {
        powerMill = new PMAutomation(obj);
        session = powerMill.ActiveProject;
        foreach (PMPattern pattern in session.Patterns)
        {
            if (pattern.Name == "api_start")
            {
                // Connect to the instance with the specific entity
                ConnectToInstance(obj);
                return; // Stop iterating if connected
            }
        }
    }
}

// ADD THESE METHODS AFTER THE CONNECTTOINSTANCE METHOD

private IntPtr GetMainWindowHandleOfActiveProcess()
{
    // Get the active process
    Process activeProcess = GetActiveProcess();
    // Return the main window handle of the active process
    return activeProcess.MainWindowHandle;
}

private Process GetActiveProcess()
{
    // Get the process ID of the foreground window
    GetForegroundWindow();
    GetWindowThreadProcessId(GetForegroundWindow(), out uint processId);
    // Return the process associated with the process ID
    return Process.GetProcessById((int)processId);
}

 

 

This code will get the ID of the current foreground window which happens to the PowerMill instance that the macro was ran in. This will dock it that session so that it cannot be used by any other instance of PowerMill.

 

Now because PowerMills interface doesn't allow for proper minimizing and maximizing, I'm going to add a few methods that will allow you to minimize the interface into the bottom right corner of the session and maximize back to the center of the session. The values that are in this code for sizing can be adjusted at anytime to fir your API. Also the positioning can be changed with ease in the event you do not want the API to mount to the center of the screen.

 

Here is the code structure for Resizing the API:

 

// FIRST WE NEED TO ADJSUT OUR FORM1 METHOD CALL TO THIS

public Form1()
{
    InitializeComponent();   
    this.Resize += Form1_Resize;
}

// AFTER THE GETACTIVEPROCESS METHOD CALL WE NEED TO ADD THIS CODE

protected override void WndProc(ref Message m)
{
    const int WM_SYSCOMMAND = 0x0112;
    const int SC_MINIMIZE = 0xF020;
    const int SC_MAXIMIZE = 0xF030;

    Screen activeScreen = Screen.FromControl(this);

    if (m.Msg == WM_SYSCOMMAND)
    {
        int wParam = (int)m.WParam;

        if (wParam == SC_MINIMIZE)
        {
            // Instead of minimizing, just resize and reposition the form
            this.Size = new System.Drawing.Size(400, 40);
            this.Location = new System.Drawing.Point(activeScreen.WorkingArea.Width - this.Width, activeScreen.WorkingArea.Height - this.Height);
            return;
        }
        else if (wParam == SC_MAXIMIZE)
        {
            // Instead of maximizing, resize the form to your desired dimensions
            this.Size = new System.Drawing.Size(400, 530);
            this.Location = new System.Drawing.Point((activeScreen.WorkingArea.Width - this.Width) / 2, (activeScreen.WorkingArea.Height - this.Height) / 2);
            return;
        }
    }

    base.WndProc(ref m);
}

private void Form1_Resize(object sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        Screen activeScreen = Screen.FromControl(this);
        // Resize and reposition the form when it is minimized
        this.Size = new System.Drawing.Size(400, 40);
        this.Location = new System.Drawing.Point(activeScreen.WorkingArea.Width - this.Width, activeScreen.WorkingArea.Height - this.Height);
    }
}

 

 

These two methods control how the windows minimize and maximize buttons operate. Using the standard buttons with no modifications, if you try and minimize the API window while docked, the window will simply disappear into the abyss. This code ensures that the window is properly minimized neatly into the corner of the session. Once you hit the maximize button, the window will return to the center of the window with the specified dimensions.

 

Hopefully this helps out a few people that are trying to find an alternative way to connect to a PowerMill session without using a formal plugin or a dropdown process selection box.

 

If I made any mistakes, or you are having trouble understanding this, please let me know so that I can adjust the post and the code properly.

 

Thank you

7 REPLIES 7
Message 2 of 8

Will  we take long time for this check instance?

Message 3 of 8

The optimal approach would be if we could get the current Powermill instance ProcessId in the macro and consequently pass that token to the API app via start argument. This way, the API will directly know which Powermill to connect to from the list based on the ProcessId.

Message 4 of 8

If you boot the API on session load with the pmuser macro it only takes about 3 seconds to load. If the session has quite a bit of information, I've seen it take up to 30 seconds to parse through all the data.

Message 5 of 8

I agree 100%. Unfortunately I have yet to find a way to get the ProcessID instead of the name AND be able to connect via the ID. I'm finding it difficult to get a handle on C#'s ability to handle ProcessID's and differentiate them between the names of the processes.

Message 6 of 8
icse
in reply to: barr.jarryd

I found a quite clean solution for you

Create a c# application:

 

 

 

using System.Runtime.InteropServices;

internal class Program
{
        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();
        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    private static void Main(string[] args)
    {
        var window = GetForegroundWindow();
        uint pid;
        GetWindowThreadProcessId(window,out pid);

        string path = @"C:\myFolder";

        if(Directory.Exists(path))
            File.WriteAllText(path + @"\current_pid.txt",pid.ToString());
    }
}

 

 

 

make sure you change the outputtype to WinExe so you dont chatch the pid from your console application by accident:

 

 

 

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

 

 

 

 

then run it trough powermill:

 

 

 

ole fileaction 'open' 'C:\myexe'

 

 

 

 

this stores the pid of the current powermill in the text file.

 

I'm pretty sure you can do this in a wpf application aswell just make sure to get the process id before raising any window.

 

hope this helps

Message 7 of 8
barr.jarryd
in reply to: icse

I will give this a try as soon as I get into the office thank you!

That is for sure a method I did not think of.
Message 8 of 8

At the moment I already wrote some API but need to select window dropdown.

So if it's work, please share a video for reference.

I'm curious to see it running.

 

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

Post to forums  

Technology Administrators