185 lines
7.0 KiB
Python
185 lines
7.0 KiB
Python
"""
|
|
IEdmFile13::ChangeState3 via comtypes vtable direct call.
|
|
|
|
Confirmed from gen_py IEdmFile13_vtables_:
|
|
- IEdmFile13 IID : {DB0646C9-9E3F-4EA2-93AA-EB6584D268E2}
|
|
- ChangeState3 oVft = 432 → vtable slot 54
|
|
- Slot layout:
|
|
0-2 IUnknown (handled by comtypes base)
|
|
3-6 IDispatch (handled by comtypes base)
|
|
7-53 47 methods from IEdmObject5 … IEdmFile12 (placeholders)
|
|
54 ChangeState3
|
|
55 GetThumbnail
|
|
"""
|
|
import ctypes
|
|
import getpass
|
|
import pythoncom
|
|
import win32com.client
|
|
import win32com.client.gencache as gencache
|
|
import comtypes
|
|
import comtypes.automation
|
|
from comtypes import COMMETHOD, GUID, HRESULT
|
|
from comtypes.automation import IDispatch as CT_IDispatch
|
|
|
|
VAULT_NAME = "Drilling_Test"
|
|
TEST_PATH = r"C:\PDM\Drilling_Test\DWS\PileDRIVER\825 PileDRIVER\MFG\800-TT-001.SLDPRT"
|
|
FOLDER_PATH = r"C:\PDM\Drilling_Test\DWS\PileDRIVER\825 PileDRIVER\MFG"
|
|
TRANSITION_ID = 268
|
|
TO_STATE_ID = 9
|
|
EdmObject_File = 1
|
|
IID_IEdmFile13 = "{DB0646C9-9E3F-4EA2-93AA-EB6584D268E2}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# comtypes interface — 47 placeholders put ChangeState3 at slot 54 (offset 432)
|
|
# ---------------------------------------------------------------------------
|
|
_phs = [COMMETHOD([], HRESULT, f"_ph{i}") for i in range(47)]
|
|
|
|
VARIANT_p = ctypes.POINTER(comtypes.automation.VARIANT)
|
|
|
|
class IEdmFile13_CT(CT_IDispatch):
|
|
_iid_ = GUID(IID_IEdmFile13)
|
|
_idlflags_ = ["dual", "oleautomation"]
|
|
_methods_ = _phs + [
|
|
COMMETHOD(
|
|
[], HRESULT, "ChangeState3",
|
|
(["in"], VARIANT_p, "poStateIdOrName"),
|
|
(["in"], VARIANT_p, "poTransitionIdOrName"),
|
|
(["in"], ctypes.c_long, "lFolderID"),
|
|
(["in"], ctypes.c_wchar_p, "bsComment"),
|
|
(["in"], ctypes.c_long, "lParentWnd"),
|
|
(["in"], ctypes.c_long, "lEdmStateFlags"),
|
|
(["in"], ctypes.c_wchar_p, "bsPasswd"),
|
|
),
|
|
COMMETHOD(
|
|
[], HRESULT, "GetThumbnail",
|
|
(["out", "retval"], ctypes.POINTER(ctypes.c_void_p), "pBitmap"),
|
|
),
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def make_i4_variant(val: int) -> comtypes.automation.VARIANT:
|
|
"""Return a 16-byte VARIANT with vt=VT_I4 and the given integer value."""
|
|
v = comtypes.automation.VARIANT()
|
|
v.vt = 3 # VT_I4
|
|
# The union value starts at byte offset 8 inside VARIANT
|
|
ctypes.cast(ctypes.byref(v, 8), ctypes.POINTER(ctypes.c_int))[0] = val
|
|
return v
|
|
|
|
|
|
def raw_ptr_from_pycom(py_com_obj) -> int:
|
|
"""
|
|
Read the IUnknown*/IDispatch* stored inside a pythoncom COM wrapper.
|
|
CPython 64-bit layout: ob_refcnt(8) | ob_type(8) | punk(8) → offset 16.
|
|
"""
|
|
return ctypes.c_uint64.from_address(id(py_com_obj) + 16).value
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main():
|
|
username = input("PDM Username: ")
|
|
password = getpass.getpass("PDM Password: ") # reused for bsPasswd below
|
|
|
|
print("\n=== Connect ===")
|
|
vault = gencache.EnsureDispatch("ConisioLib.EdmVault")
|
|
vault.Login(username, password, VAULT_NAME)
|
|
print(f"Logged in to {VAULT_NAME}")
|
|
|
|
folder_obj = vault.GetFolderFromPath(FOLDER_PATH)
|
|
result = vault.GetFileFromPath(TEST_PATH, folder_obj)
|
|
raw = result[0] if isinstance(result, tuple) else result
|
|
file_id = raw.ID
|
|
fold_id = folder_obj.ID
|
|
print(f"File ID: {file_id} Folder ID: {fold_id}")
|
|
|
|
file_obj = vault.GetObject(EdmObject_File, file_id)
|
|
file13_w32 = win32com.client.CastTo(file_obj, "IEdmFile13")
|
|
|
|
# -----------------------------------------------------------------------
|
|
print("\n=== Extract raw IEdmFile13* from pythoncom ===")
|
|
# -----------------------------------------------------------------------
|
|
py_disp = file13_w32._oleobj_ # PyIDispatch wrapping IEdmFile13
|
|
raw_ptr = raw_ptr_from_pycom(py_disp)
|
|
print(f"IEdmFile13* = {raw_ptr:#x}")
|
|
|
|
# Sanity: read the vtable pointer (first 8 bytes of the COM object)
|
|
vtbl_ptr = ctypes.c_uint64.from_address(raw_ptr).value
|
|
print(f"Vtable ptr = {vtbl_ptr:#x}")
|
|
|
|
if not raw_ptr or not vtbl_ptr:
|
|
print("ERROR: Could not read a valid COM pointer — aborting.")
|
|
return
|
|
|
|
# -----------------------------------------------------------------------
|
|
print("\n=== QI to IEdmFile13_CT via comtypes ===")
|
|
# -----------------------------------------------------------------------
|
|
# Cast to IUnknown so comtypes can call QueryInterface properly
|
|
ct_unk = ctypes.cast(raw_ptr, ctypes.POINTER(comtypes.IUnknown))
|
|
# py_disp must stay alive while ct_unk is in use (ct_unk is a borrowed ref)
|
|
try:
|
|
file13_ct = ct_unk.QueryInterface(IEdmFile13_CT)
|
|
print(f"QI succeeded: {file13_ct}")
|
|
except Exception as e:
|
|
print(f"QI failed: {e}")
|
|
import traceback; traceback.print_exc()
|
|
return
|
|
|
|
# -----------------------------------------------------------------------
|
|
print("\n=== Build VARIANTs ===")
|
|
# -----------------------------------------------------------------------
|
|
v_state = make_i4_variant(TO_STATE_ID)
|
|
v_trans = make_i4_variant(TRANSITION_ID)
|
|
print(f"v_state vt={v_state.vt} val={TO_STATE_ID}")
|
|
print(f"v_trans vt={v_trans.vt} val={TRANSITION_ID}")
|
|
|
|
# -----------------------------------------------------------------------
|
|
print(f"\n=== ChangeState3(state={TO_STATE_ID}, trans={TRANSITION_ID}, folder={fold_id}) ===")
|
|
# -----------------------------------------------------------------------
|
|
try:
|
|
hr = file13_ct.ChangeState3(
|
|
ctypes.byref(v_state),
|
|
ctypes.byref(v_trans),
|
|
ctypes.c_long(fold_id),
|
|
"Batch transition test",
|
|
ctypes.c_long(0),
|
|
ctypes.c_long(0),
|
|
password, # PDM password (required by this transition)
|
|
)
|
|
print(f"ChangeState3 returned HRESULT {hr:#010x}")
|
|
except Exception as e:
|
|
print(f"ChangeState3 raised: {e}")
|
|
import traceback; traceback.print_exc()
|
|
# Keep py_disp alive
|
|
del py_disp
|
|
return
|
|
|
|
# keep py_disp alive until after the call
|
|
del py_disp
|
|
|
|
# -----------------------------------------------------------------------
|
|
print("\n=== Verify state ===")
|
|
# -----------------------------------------------------------------------
|
|
fresh = vault.GetObject(EdmObject_File, file_id)
|
|
try:
|
|
state = fresh.CurrentState
|
|
if callable(state):
|
|
state = state()
|
|
name = state.Name if hasattr(state, "Name") else str(state)
|
|
print(f"New state: {name}")
|
|
if name in ("Approved", "AA"):
|
|
print("\n*** SUCCESS! ***")
|
|
else:
|
|
print("State did not reach Approved.")
|
|
except Exception as e:
|
|
print(f"Could not read new state: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|