I've developed a PDM add-in that works perfectly when parts/assemblies are closed, but throws an error when the same files are open in SOLIDWORKS.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EPDM.Interop.epdm;
namespace *********SerializationAddin
{
[Guid("****************************************************")] // Generate new GUID in production
[ComVisible(true)]
public class SerializationAddIn : IEdmAddIn5
{
private IEdmVault5 _vault;
// Hardcoded connection string - to be moved to Credentials table later
private const string CONNECTION_STRING =
"Server="*****";Database="*****";User Id="*****";Password="*****";" + "Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;";
public void GetAddInInfo(ref EdmAddInInfo poInfo, IEdmVault5 poVault, IEdmCmdMgr5 poCmdMgr)
{
_vault = poVault;
poInfo.mbsAddInName = "***** Serialization";
poInfo.mbsCompany = "*****";
poInfo.mbsDescription = "Sets MajorGroup, ItemGroup, and Serialization from database.";
poInfo.mlAddInVersion = 1;
poInfo.mlRequiredVersionMajor = 31; // PDM 2023
poInfo.mlRequiredVersionMinor = 0;
poCmdMgr.AddHook(EdmCmdType.EdmCmd_CardButton);
}
public void OnCmd(ref EdmCmd poCmd, ref EdmCmdData[] ppoData)
{
if (poCmd.meCmdType != EdmCmdType.EdmCmd_CardButton || ppoData == null || ppoData.Length == 0)
return;
// Filter only the "***** Serialization" button
if (!string.Equals(poCmd.mbsComment, "***** Serialization", StringComparison.OrdinalIgnoreCase))
return;
try
{
foreach (var row in ppoData)
{
if (_vault == null)
{
MessageBox.Show("Vault object is null.", "Serialization Add-in",
MessageBoxButtons.OK, MessageBoxIcon.Error);
continue;
}
var obj = _vault.GetObject(EdmObjectType.EdmObject_File, row.mlObjectID1);
var file = obj as IEdmFile5;
if (file == null)
{
MessageBox.Show("File object is null.", "Serialization Add-in",
MessageBoxButtons.OK, MessageBoxIcon.Error);
continue;
}
var name = file.Name ?? "";
// Only process SOLIDWORKS parts and assemblies
if (!name.EndsWith(".sldprt", StringComparison.OrdinalIgnoreCase) &&
!name.EndsWith(".sldasm", StringComparison.OrdinalIgnoreCase))
{
MessageBox.Show("This add-in only works with SOLIDWORKS parts (.sldprt) and assemblies (.sldasm).",
"Serialization Add-in", MessageBoxButtons.OK, MessageBoxIcon.Information);
continue;
}
// Show the serialization form
using (var dlg = new SerializationForm(file))
{
dlg.TopMost = true;
dlg.StartPosition = FormStartPosition.CenterScreen;
var wrapper = new Win32WindowWrapper(poCmd.mlParentWnd);
if (dlg.ShowDialog(wrapper) == DialogResult.OK)
{
// User clicked OK - values are already written to file
// Force card refresh by setting the refresh flag in EdmCmd
poCmd.mbsComment = "RefreshCard";
MessageBox.Show("Serialization values updated successfully!",
"Serialization Add-in", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
catch (SqlException ex)
{
MessageBox.Show(\\\$"Database error: {ex.Message}\\n\\nPlease contact your system administrator.",
"Serialization Add-in", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (COMException ex)
{
MessageBox.Show(\\\$"PDM COM error 0x{ex.ErrorCode:X}: {ex.Message}",
"Serialization Add-in", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show(\\\$"Unexpected error: {ex.Message}\\n\\n{ex.StackTrace}",
"Serialization Add-in", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#region Helper Classes
private sealed class Win32WindowWrapper : IWin32Window
{
private readonly IntPtr _handle;
public Win32WindowWrapper(int handle) { _handle = new IntPtr(handle); }
public IntPtr Handle => _handle;
}
#endregion
#region Serialization Form
private sealed class SerializationForm : Form
{
private readonly IEdmFile5 _file;
private ComboBox _cboMajorGroup;
private ComboBox _cboItemGroup;
private ComboBox _cboSerialization;
private Button _btnOk;
private Button _btnCancel;
private List
private List
private List
private DataTable _junctionTable;
public SerializationForm(IEdmFile5 file)
{
_file = file;
InitializeForm();
LoadDataFromDatabase();
PopulateMajorGroups();
}
private void InitializeForm()
{
Text = "Set Item Classification Properties";
Size = new Size(500, 300);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterScreen;
// Labels and ComboBoxes
var lblMajorGroup = new Label
{
Text = "Major Group:",
Location = new Point(30, 30),
Size = new Size(120, 23),
TextAlign = ContentAlignment.MiddleRight
};
_cboMajorGroup = new ComboBox
{
Location = new Point(160, 30),
Size = new Size(280, 23),
DropDownStyle = ComboBoxStyle.DropDownList
};
_cboMajorGroup.SelectedIndexChanged += CboMajorGroup_SelectedIndexChanged;
var lblItemGroup = new Label
{
Text = "Item Group:",
Location = new Point(30, 80),
Size = new Size(120, 23),
TextAlign = ContentAlignment.MiddleRight
};
_cboItemGroup = new ComboBox
{
Location = new Point(160, 80),
Size = new Size(280, 23),
DropDownStyle = ComboBoxStyle.DropDownList,
Enabled = false
};
_cboItemGroup.SelectedIndexChanged += CboItemGroup_SelectedIndexChanged;
var lblSerialization = new Label
{
Text = "Serialization:",
Location = new Point(30, 130),
Size = new Size(120, 23),
TextAlign = ContentAlignment.MiddleRight
};
_cboSerialization = new ComboBox
{
Location = new Point(160, 130),
Size = new Size(280, 23),
DropDownStyle = ComboBoxStyle.DropDownList,
Enabled = false
};
// Buttons
_btnOk = new Button
{
Text = "OK",
Location = new Point(250, 200),
Size = new Size(90, 30),
Enabled = false
};
_btnOk.Click += BtnOk_Click;
_btnCancel = new Button
{
Text = "Cancel",
Location = new Point(350, 200),
Size = new Size(90, 30),
DialogResult = DialogResult.Cancel
};
// Add controls to form
Controls.AddRange(new Control[]
{
lblMajorGroup, _cboMajorGroup,
lblItemGroup, _cboItemGroup,
lblSerialization, _cboSerialization,
_btnOk, _btnCancel
});
AcceptButton = _btnOk;
CancelButton = _btnCancel;
}
private void LoadDataFromDatabase()
{
try
{
using (var conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
// Load MajorGroups
using (var cmd = new SqlCommand(
"SELECT MajorGroupID, MajorGroupName FROM [MajorGroup] WHERE Deleted = 0 ORDER BY MajorGroupName", conn))
using (var reader = cmd.ExecuteReader())
{
_majorGroups = new List
while (reader.Read())
{
_majorGroups.Add(new MajorGroupItem
{
ID = reader.GetInt32(0),
Name = reader.GetString(1)
});
}
}
// Load ItemGroups
using (var cmd = new SqlCommand(
"SELECT ItemGroupID, ItemGroupName FROM [ItemGroup] WHERE Deleted = 0 ORDER BY ItemGroupName", conn))
using (var reader = cmd.ExecuteReader())
{
_allItemGroups = new List
while (reader.Read())
{
_allItemGroups.Add(new ItemGroupItem
{
ID = reader.GetInt32(0),
Name = reader.GetString(1)
});
}
}
// Load Serializations
using (var cmd = new SqlCommand(
"SELECT SerializationID, SerializationName FROM [Serialization] WHERE Deleted = 0 ORDER BY SerializationName", conn))
using (var reader = cmd.ExecuteReader())
{
_allSerializations = new List
while (reader.Read())
{
_allSerializations.Add(new SerializationItem
{
ID = reader.GetInt32(0),
Name = reader.GetString(1)
});
}
}
// Load junction table
using (var cmd = new SqlCommand(
"SELECT MajorGroupID, ItemGroupID, SerializationID FROM [MajorGroupItemSerial] WHERE Deleted = 0", conn))
using (var adapter = new SqlDataAdapter(cmd))
{
_junctionTable = new DataTable();
adapter.Fill(_junctionTable);
}
}
}
catch (Exception ex)
{
MessageBox.Show(\\\$"Error loading data from database:\\n{ex.Message}",
"Database Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw;
}
}
private void PopulateMajorGroups()
{
_cboMajorGroup.DisplayMember = "Name";
_cboMajorGroup.ValueMember = "ID";
_cboMajorGroup.DataSource = _majorGroups;
_cboMajorGroup.SelectedIndex = -1;
}
private void CboMajorGroup_SelectedIndexChanged(object sender, EventArgs e)
{
_cboItemGroup.DataSource = null;
_cboItemGroup.Enabled = false;
_cboSerialization.DataSource = null;
_cboSerialization.Enabled = false;
_btnOk.Enabled = false;
if (_cboMajorGroup.SelectedValue == null)
return;
int majorGroupId = (int)_cboMajorGroup.SelectedValue;
// Get valid ItemGroupIDs for this MajorGroup
var validItemGroupIds = _junctionTable.AsEnumerable()
.Where(row => row.Field
.Select(row => row.Field
.Distinct()
.ToList();
// Filter ItemGroups
var filteredItemGroups = _allItemGroups
.Where(ig => validItemGroupIds.Contains(ig.ID))
.ToList();
if (filteredItemGroups.Count > 0)
{
_cboItemGroup.DisplayMember = "Name";
_cboItemGroup.ValueMember = "ID";
_cboItemGroup.DataSource = filteredItemGroups;
_cboItemGroup.SelectedIndex = -1;
_cboItemGroup.Enabled = true;
}
}
private void CboItemGroup_SelectedIndexChanged(object sender, EventArgs e)
{
_cboSerialization.DataSource = null;
_cboSerialization.Enabled = false;
_btnOk.Enabled = false;
if (_cboMajorGroup.SelectedValue == null || _cboItemGroup.SelectedValue == null)
return;
int majorGroupId = (int)_cboMajorGroup.SelectedValue;
int itemGroupId = (int)_cboItemGroup.SelectedValue;
// Get valid SerializationIDs for this combination
var validSerializationIds = _junctionTable.AsEnumerable()
.Where(row => row.Field
row.Field
.Select(row => row.Field
.Distinct()
.ToList();
// Filter Serializations
var filteredSerializations = _allSerializations
.Where(s => validSerializationIds.Contains(s.ID))
.ToList();
if (filteredSerializations.Count > 0)
{
_cboSerialization.DisplayMember = "Name";
_cboSerialization.ValueMember = "ID";
_cboSerialization.DataSource = filteredSerializations;
_cboSerialization.SelectedIndex = -1;
_cboSerialization.Enabled = true;
// Enable OK button when serialization is selected
_cboSerialization.SelectedIndexChanged += (s, ev) =>
{
_btnOk.Enabled = _cboSerialization.SelectedValue != null;
};
}
}
private void BtnOk_Click(object sender, EventArgs e)
{
try
{
if (_cboMajorGroup.SelectedValue == null ||
_cboItemGroup.SelectedValue == null ||
_cboSerialization.SelectedValue == null)
{
MessageBox.Show("Please select all three values.", "Validation",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string majorGroupName = ((MajorGroupItem)_cboMajorGroup.SelectedItem).Name;
string itemGroupName = ((ItemGroupItem)_cboItemGroup.SelectedItem).Name;
string serializationName = ((SerializationItem)_cboSerialization.SelectedItem).Name;
// Get variable enumerator once for all writes
dynamic varEnum = _file.GetEnumeratorVariable();
if (varEnum == null)
{
throw new Exception("Could not get variable enumerator.");
}
try
{
// Write all three variables at @ configuration
varEnum.SetVar("MajorGroup", "@", majorGroupName);
varEnum.SetVar("ItemGroup", "@", itemGroupName);
varEnum.SetVar("Serialization", "@", serializationName);
}
catch
{
// If @ doesn't work, try empty string for file-level
varEnum.SetVar("MajorGroup", "", majorGroupName);
varEnum.SetVar("ItemGroup", "", itemGroupName);
varEnum.SetVar("Serialization", "", serializationName);
}
// Close and save all changes at once
varEnum.CloseFile(true);
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show(\\\$"Error writing to PDM:\\n{ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#region Data Classes
private class MajorGroupItem
{
public int ID { get; set; }
public string Name { get; set; }
}
private class ItemGroupItem
{
public int ID { get; set; }
public string Name { get; set; }
}
private class SerializationItem
{
public int ID { get; set; }
public string Name { get; set; }
}
#endregion
}
#endregion
}
}
This is the error message.
Also my datacard doesn't refresh automatically. Is there any specific method to do that?
