cherry-pick #32: MDI pre-document tab for Silo new item (70118201b0)

This commit is contained in:
forbes
2026-02-13 14:09:44 -06:00
parent 30cc226bf6
commit 54f8006e24
12 changed files with 321 additions and 81 deletions

View File

@@ -67,6 +67,7 @@
#include "WorkbenchManager.h"
#include "WorkbenchManipulatorPython.h"
#include "FileOriginPython.h"
#include "EditingContext.h"
#include "OriginManager.h"
#include "Inventor/MarkerBitmaps.h"
#include "Language/Translator.h"
@@ -596,6 +597,68 @@ PyMethodDef ApplicationPy::Methods[] = {
"Set the active origin by ID.\n"
"\n"
"id : str\n The origin ID to activate."},
{"registerEditingContext",
(PyCFunction)ApplicationPy::sRegisterEditingContext,
METH_VARARGS,
"registerEditingContext(id, label, color, toolbars, match, priority=50) -> None\n"
"\n"
"Register an editing context for dynamic toolbar management.\n"
"\n"
"id : str\n Unique context identifier (e.g. 'fem.analysis').\n"
"label : str\n Display label template. Use {name} for object name.\n"
"color : str\n Hex color for breadcrumb (e.g. '#f38ba8').\n"
"toolbars : list of str\n Toolbar names to show when active.\n"
"match : callable\n Function returning True if context is active.\n"
"priority : int\n Higher values checked first (default 50)."},
{"unregisterEditingContext",
(PyCFunction)ApplicationPy::sUnregisterEditingContext,
METH_VARARGS,
"unregisterEditingContext(id) -> None\n"
"\n"
"Unregister an editing context.\n"
"\n"
"id : str\n Context identifier to remove."},
{"registerEditingOverlay",
(PyCFunction)ApplicationPy::sRegisterEditingOverlay,
METH_VARARGS,
"registerEditingOverlay(id, toolbars, match) -> None\n"
"\n"
"Register an overlay context that adds toolbars to any active context.\n"
"\n"
"id : str\n Unique overlay identifier.\n"
"toolbars : list of str\n Toolbar names to add.\n"
"match : callable\n Function returning True if overlay is active."},
{"unregisterEditingOverlay",
(PyCFunction)ApplicationPy::sUnregisterEditingOverlay,
METH_VARARGS,
"unregisterEditingOverlay(id) -> None\n"
"\n"
"Unregister an editing overlay.\n"
"\n"
"id : str\n Overlay identifier to remove."},
{"injectEditingCommands",
(PyCFunction)ApplicationPy::sInjectEditingCommands,
METH_VARARGS,
"injectEditingCommands(contextId, toolbarName, commands) -> None\n"
"\n"
"Inject additional commands into a context's toolbar.\n"
"\n"
"contextId : str\n Context to inject into.\n"
"toolbarName : str\n Toolbar within that context.\n"
"commands : list of str\n Command names to add."},
{"currentEditingContext",
(PyCFunction)ApplicationPy::sCurrentEditingContext,
METH_VARARGS,
"currentEditingContext() -> dict\n"
"\n"
"Get the current editing context as a dict with keys:\n"
"id, label, color, toolbars, breadcrumb, breadcrumbColors."},
{"refreshEditingContext",
(PyCFunction)ApplicationPy::sRefreshEditingContext,
METH_VARARGS,
"refreshEditingContext() -> None\n"
"\n"
"Force re-resolution of the editing context."},
{nullptr, nullptr, 0, nullptr} /* Sentinel */
};
@@ -2139,3 +2202,185 @@ PyObject* ApplicationPy::sSetActiveOrigin(PyObject* /*self*/, PyObject* args)
}
PY_CATCH;
}
// ---------------------------------------------------------------------------
// Editing Context Python API
// ---------------------------------------------------------------------------
static QStringList pyListToQStringList(PyObject* listObj)
{
QStringList result;
if (!listObj || !PyList_Check(listObj)) {
return result;
}
Py_ssize_t size = PyList_Size(listObj);
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject* item = PyList_GetItem(listObj, i);
if (PyUnicode_Check(item)) {
result << QString::fromUtf8(PyUnicode_AsUTF8(item));
}
}
return result;
}
static PyObject* qStringListToPyList(const QStringList& list)
{
Py::List pyList;
for (const auto& s : list) {
pyList.append(Py::String(s.toUtf8().constData()));
}
return Py::new_reference_to(pyList);
}
PyObject* ApplicationPy::sRegisterEditingContext(PyObject* /*self*/, PyObject* args)
{
const char* id = nullptr;
const char* label = nullptr;
const char* color = nullptr;
PyObject* toolbarsList = nullptr;
PyObject* matchCallable = nullptr;
int priority = 50;
if (!PyArg_ParseTuple(args, "sssOO|i", &id, &label, &color, &toolbarsList, &matchCallable, &priority)) {
return nullptr;
}
if (!PyCallable_Check(matchCallable)) {
PyErr_SetString(PyExc_TypeError, "match must be callable");
return nullptr;
}
QStringList toolbars = pyListToQStringList(toolbarsList);
// Hold a reference to the Python callable
Py::Callable pyMatch(matchCallable);
pyMatch.increment_reference_count();
ContextDefinition def;
def.id = QString::fromUtf8(id);
def.labelTemplate = QString::fromUtf8(label);
def.color = QString::fromUtf8(color);
def.toolbars = toolbars;
def.priority = priority;
def.match = [pyMatch]() -> bool {
Base::PyGILStateLocker lock;
try {
Py::Callable fn(pyMatch);
Py::Object result = fn.apply(Py::TupleN());
return result.isTrue();
}
catch (Py::Exception&) {
Base::PyException e;
e.ReportException();
return false;
}
};
EditingContextResolver::instance()->registerContext(def);
Py_Return;
}
PyObject* ApplicationPy::sUnregisterEditingContext(PyObject* /*self*/, PyObject* args)
{
const char* id = nullptr;
if (!PyArg_ParseTuple(args, "s", &id)) {
return nullptr;
}
EditingContextResolver::instance()->unregisterContext(QString::fromUtf8(id));
Py_Return;
}
PyObject* ApplicationPy::sRegisterEditingOverlay(PyObject* /*self*/, PyObject* args)
{
const char* id = nullptr;
PyObject* toolbarsList = nullptr;
PyObject* matchCallable = nullptr;
if (!PyArg_ParseTuple(args, "sOO", &id, &toolbarsList, &matchCallable)) {
return nullptr;
}
if (!PyCallable_Check(matchCallable)) {
PyErr_SetString(PyExc_TypeError, "match must be callable");
return nullptr;
}
QStringList toolbars = pyListToQStringList(toolbarsList);
Py::Callable pyMatch(matchCallable);
pyMatch.increment_reference_count();
OverlayDefinition def;
def.id = QString::fromUtf8(id);
def.toolbars = toolbars;
def.match = [pyMatch]() -> bool {
Base::PyGILStateLocker lock;
try {
Py::Callable fn(pyMatch);
Py::Object result = fn.apply(Py::TupleN());
return result.isTrue();
}
catch (Py::Exception&) {
Base::PyException e;
e.ReportException();
return false;
}
};
EditingContextResolver::instance()->registerOverlay(def);
Py_Return;
}
PyObject* ApplicationPy::sUnregisterEditingOverlay(PyObject* /*self*/, PyObject* args)
{
const char* id = nullptr;
if (!PyArg_ParseTuple(args, "s", &id)) {
return nullptr;
}
EditingContextResolver::instance()->unregisterOverlay(QString::fromUtf8(id));
Py_Return;
}
PyObject* ApplicationPy::sInjectEditingCommands(PyObject* /*self*/, PyObject* args)
{
const char* contextId = nullptr;
const char* toolbarName = nullptr;
PyObject* commandsList = nullptr;
if (!PyArg_ParseTuple(args, "ssO", &contextId, &toolbarName, &commandsList)) {
return nullptr;
}
QStringList commands = pyListToQStringList(commandsList);
EditingContextResolver::instance()
->injectCommands(QString::fromUtf8(contextId), QString::fromUtf8(toolbarName), commands);
Py_Return;
}
PyObject* ApplicationPy::sCurrentEditingContext(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
EditingContext ctx = EditingContextResolver::instance()->currentContext();
Py::Dict result;
result.setItem("id", Py::String(ctx.id.toUtf8().constData()));
result.setItem("label", Py::String(ctx.label.toUtf8().constData()));
result.setItem("color", Py::String(ctx.color.toUtf8().constData()));
result.setItem("toolbars", Py::Object(qStringListToPyList(ctx.toolbars), true));
result.setItem("breadcrumb", Py::Object(qStringListToPyList(ctx.breadcrumb), true));
result.setItem("breadcrumbColors", Py::Object(qStringListToPyList(ctx.breadcrumbColors), true));
return Py::new_reference_to(result);
}
PyObject* ApplicationPy::sRefreshEditingContext(PyObject* /*self*/, PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
EditingContextResolver::instance()->refresh();
Py_Return;
}