From: Sebastian Schmidt Date: Sat, 15 Dec 2018 18:57:23 +0000 (+0100) Subject: Initial python support X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=1f9ff6307712d98d9d36e288c9134219407e3690;p=xonotic%2Fnetradiant.git Initial python support --- diff --git a/.gitmodules b/.gitmodules index d20f6e35..7b14d4d8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/crunch"] path = libs/crunch url = https://github.com/DaemonEngine/crunch.git +[submodule "libs/pybind11"] + path = libs/pybind11 + url = https://github.com/pybind/pybind11 diff --git a/CMakeLists.txt b/CMakeLists.txt index e920fd62..cb56420e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(NetRadiant C CXX) option(BUILD_RADIANT "Build the GUI" ON) option(BUILD_CRUNCH "Build Crunch image support" OFF) +option(EMBED_PYTHON "Embed python" ON) option(USE_WERROR "Build with -Werror -pedantic-errors" OFF) if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) @@ -65,7 +66,8 @@ endif () # Flags #----------------------------------------------------------------------- -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") +# TODO: check why these CXX flags were set +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") macro(addflags_c args) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${args}") endmacro() @@ -145,6 +147,9 @@ else () -DPOSIX=1 ) endif () +if (EMBED_PYTHON) + add_definitions(-D_EMBED_PYTHON) +endif () if (XWINDOWS) find_package(X11 REQUIRED) diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 633b965f..49227a23 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -19,6 +19,9 @@ add_subdirectory(modulesystem) add_subdirectory(os) add_subdirectory(picomodel) add_subdirectory(profile) +if (EMBED_PYTHON) + add_subdirectory(pybind11) +endif () add_subdirectory(script) add_subdirectory(signal) add_subdirectory(splines) diff --git a/libs/pybind11 b/libs/pybind11 new file mode 160000 index 00000000..e2b884c3 --- /dev/null +++ b/libs/pybind11 @@ -0,0 +1 @@ +Subproject commit e2b884c33bcde70b2ea562ffa52dd7ebee276d50 diff --git a/radiant/CMakeLists.txt b/radiant/CMakeLists.txt index c8672d49..ea809891 100644 --- a/radiant/CMakeLists.txt +++ b/radiant/CMakeLists.txt @@ -93,6 +93,11 @@ set(RADIANTLIST if (WIN32) list(APPEND RADIANTLIST multimon.cpp multimon.h) endif () +if (EMBED_PYTHON) + list(APPEND RADIANTLIST + pybindconnector.cpp pybindconnector.h + pythonconsole.cpp pythonconsole.h) +endif () radiant_tool(radiant WIN32 radiant.rc ${RADIANTLIST}) add_dependencies(radiant modules) @@ -129,5 +134,8 @@ target_link_libraries(radiant if (X11_LIBRARIES) target_link_libraries(radiant ${X11_LIBRARIES}) endif () +if (EMBED_PYTHON) + target_link_libraries(radiant pybind11::embed) +endif () copy_dlls(radiant) diff --git a/radiant/main.cpp b/radiant/main.cpp index 006842a9..55205359 100644 --- a/radiant/main.cpp +++ b/radiant/main.cpp @@ -94,6 +94,10 @@ #include #endif +#ifdef _EMBED_PYTHON +#include "pybindconnector.h" +#endif // _EMBED_PYTHON + void show_splash(); void hide_splash(); @@ -645,6 +649,9 @@ int main(int argc, char *argv[]) g_GamesDialog.m_bForceLogConsole = false; } +#ifdef _EMBED_PYTHON + PYBIND_initializeInterpreter(); +#endif // _EMBED_PYTHON Radiant_Initialise(); @@ -690,6 +697,10 @@ int main(int argc, char *argv[]) Radiant_Shutdown(); +#ifdef _EMBED_PYTHON + PYBIND_finalizeInterpreter(); +#endif // _EMBED_PYTHON + // close the log file if any Sys_LogFile(false); diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 11fc3784..c4712aa1 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -86,6 +86,9 @@ #include "pluginmenu.h" #include "plugintoolbar.h" #include "preferences.h" +#ifdef _EMBED_PYTHON + #include "pythonconsole.h" +#endif // _EMBED_PYTHON #include "qe3.h" #include "qgl.h" #include "select.h" @@ -1210,6 +1213,15 @@ void Console_ToggleShow() GroupDialog_showPage(g_page_console); } +#ifdef _EMBED_PYTHON +ui::Widget g_page_pythonconsole{ui::null}; + +void PythonConsole_ToggleShow() +{ + GroupDialog_showPage(g_page_pythonconsole); +} +#endif // _EMBED_PYTHON + ui::Widget g_page_entity{ui::null}; void EntityInspector_ToggleShow() @@ -3115,6 +3127,13 @@ void MainFrame::Create() RawStringExportCaller("Console")); } +#ifdef _EMBED_PYTHON + if (FloatingGroupDialog()) { + g_page_pythonconsole = GroupDialog_addPage("Python", PythonConsole_constructWindow(GroupDialog_getWindow()), + RawStringExportCaller("Python")); + } +#endif // _EMBED_PYTHON + #if GDEF_OS_WINDOWS if ( g_multimon_globals.m_bStartOnPrimMon ) { PositionWindowOnPrimaryScreen( g_layout_globals.m_position ); diff --git a/radiant/pybindconnector.cpp b/radiant/pybindconnector.cpp new file mode 100644 index 00000000..ce36c6be --- /dev/null +++ b/radiant/pybindconnector.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2018, Sebastian Schmidt + All Rights Reserved. + + This file is part of NetRadiant. + + NetRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + NetRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NetRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include + +#include "pybindconnector.h" +#include "../libs/pybind11/include/pybind11/embed.h" +#include <../libs/pybind11/include/pybind11/eval.h> + + +namespace py = pybind11; + +static py::dict scope; + + + +// // TODO: embedded modules +// PYBIND11_EMBEDDED_MODULE(radiant, m) { +// m.def("TODO", [](){}); +// } + + +void PYBIND_initializeInterpreter() { + py::initialize_interpreter(); + scope = py::module::import("__main__").attr("__dict__"); +} + +std::string PYBIND_callfunction(std::string modulename,std::string functionname) { + py::object module = py::module::import(modulename.c_str()); + py::object f = module.attr(functionname.c_str()); + py::str result = f(); + return result.cast(); +} + +void PYBIND_exec(std::string code) +{ + py::exec(code.c_str(), scope); +} + +std::string PYBIND_scope_as_string() +{ + return py::str(scope).cast(); +} + +void PYBIND_finalizeInterpreter() { + scope.release(); + py::finalize_interpreter(); +} + diff --git a/radiant/pybindconnector.h b/radiant/pybindconnector.h new file mode 100644 index 00000000..50868c88 --- /dev/null +++ b/radiant/pybindconnector.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2018, Sebastian Schmidt + All Rights Reserved. + + This file is part of NetRadiant. + + NetRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + NetRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NetRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#if !defined(PYBIND_CONNECTOR_H) +#define PYBIND_CONNECTOR_H + + +void PYBIND_initializeInterpreter(); +std::string PYBIND_callfunction(std::string modulename, std::string functionname); // TODO: support for arguments +void PYBIND_exec(std::string code); +std::string PYBIND_scope_as_string(); +void PYBIND_finalizeInterpreter(); + +#endif // PYBIND_CONNECTOR_H + diff --git a/radiant/pythonconsole.cpp b/radiant/pythonconsole.cpp new file mode 100644 index 00000000..097c8ae8 --- /dev/null +++ b/radiant/pythonconsole.cpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2018, Sebastian Schmidt + All Rights Reserved. + + This file is part of NetRadiant. + + NetRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + NetRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NetRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "pythonconsole.h" + +#include +#include +#include + +#include "gtkutil/accelerator.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/container.h" +#include "gtkutil/menu.h" +#include "gtkutil/nonmodal.h" + +#include "pybindconnector.h" + + +ui::TextView g_pythonconsole_output{ui::null}; + +static gint Input_keypress(ui::Entry widget, GdkEventKey *event, gpointer data) +{ + if (event->keyval == GDK_KEY_Return) { + try + { +// auto result = PYBIND_callfunction("os", std::string(widget.text())); + PYBIND_exec(std::string(widget.text())); + std::string result = PYBIND_scope_as_string(); + g_pythonconsole_output.text(result.c_str()); + } + catch (const std::exception& e) + { + g_pythonconsole_output.text(e.what()); + } + widget.text(""); + return TRUE; + } + if (event->keyval == GDK_KEY_Escape) { + gtk_window_set_focus(widget.window(), NULL); + return TRUE; + } + + return FALSE; +} + +ui::Widget PythonConsole_constructWindow(ui::Window toplevel) +{ + auto vBox = ui::VBox(FALSE, 2); + vBox.show(); + gtk_container_set_border_width(GTK_CONTAINER(vBox), 2); + auto scr = ui::ScrolledWindow(ui::New); + scr.overflow(ui::Policy::AUTOMATIC, ui::Policy::AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + scr.show(); + + { + auto text = ui::TextView(ui::New); + text.dimensions(0, -1); // allow shrinking + gtk_text_view_set_wrap_mode(text, GTK_WRAP_WORD); + gtk_text_view_set_editable(text, FALSE); + scr.add(text); + text.show(); + g_pythonconsole_output = text; + + } + + auto input = ui::Entry(ui::New); + input.show(); + gtk_widget_set_events(input, GDK_KEY_PRESS_MASK); + input.connect("key_press_event", G_CALLBACK(Input_keypress), 0); + + vBox.pack_start(scr, TRUE, TRUE, 0); + vBox.pack_end(input, FALSE, FALSE, 0); + + gtk_container_set_focus_chain(GTK_CONTAINER(vBox), NULL); + + return vBox; +} diff --git a/radiant/pythonconsole.h b/radiant/pythonconsole.h new file mode 100644 index 00000000..5574afe2 --- /dev/null +++ b/radiant/pythonconsole.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 2018, Sebastian Schmidt + All Rights Reserved. + + This file is part of NetRadiant. + + NetRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + NetRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NetRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_PYTHONCONSOLE_H ) +#define INCLUDED_PYTHONCONSOLE_H + +#include + + +ui::Widget PythonConsole_constructWindow(ui::Window toplevel); + +#endif // INCLUDED_PYTHONCONSOLE_H