Initial Commit of the PDM project (ready for DWS migration)
This commit is contained in:
184
helpers/test_batch_api.py
Normal file
184
helpers/test_batch_api.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user