Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qtwebengine #409

Merged
merged 13 commits into from
Jun 10, 2024
44 changes: 26 additions & 18 deletions Render/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

import FreeCAD as App
import FreeCADGui as Gui
from ArchMaterial import _CommandArchMaterial

from Render.constants import ICONDIR, VALID_RENDERERS, PARAMS
from Render.utils import translate
Expand Down Expand Up @@ -342,30 +341,33 @@ def Activated(self): # pylint: disable=no-self-use
DistantLight.create()


class MaterialCreatorCommand(_CommandArchMaterial):
"""GUI command to create a material.

This class is partially based on Arch 'ArchMaterial' command.
"""
class MaterialCreatorCommand:
"""GUI command to create a material."""

def __init__(self, newname=False):
"""Init command."""
self._newname = bool(newname)

def GetResources(self):
"""Get command's resources (callback)."""
res = super().GetResources()
res["MenuText"] = (
QT_TRANSLATE_NOOP(
"MaterialCreatorCommand", "Internal Material Library"
)
if self._newname
else QT_TRANSLATE_NOOP("MaterialCreatorCommand", "Create Material")
)
res["ToolTip"] = QT_TRANSLATE_NOOP(
"MaterialCreatorCommand",
"Create a new Material in current document from internal library",
)
res = {
"Pixmap": "Arch_Material_Group",
"MenuText": (
QT_TRANSLATE_NOOP(
"MaterialCreatorCommand", "Internal Material Library"
)
if self._newname
else QT_TRANSLATE_NOOP(
"MaterialCreatorCommand", "Create Material"
)
),
"ToolTip": (
QT_TRANSLATE_NOOP(
"MaterialCreatorCommand",
"Create a new Material in current document from internal library",
)
),
}
return res

def Activated(self): # pylint: disable=no-self-use
Expand All @@ -385,6 +387,12 @@ def Activated(self): # pylint: disable=no-self-use
Gui.doCommand(cmd)
App.ActiveDocument.commitTransaction()

def IsActive(self):
v = hasattr(
FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph"
)
return v


class MaterialMaterialXImportCommand:
"""GUI command to import a MaterialX material."""
Expand Down
55 changes: 32 additions & 23 deletions Render/help/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,35 @@
from multiprocessing.connection import Client, wait
from threading import Thread, Event

try:
if __name__ == "__main__":
# Get arguments
parser = argparse.ArgumentParser(
prog="Render help",
description="Open a help browser for Render Workbench",
)
parser.add_argument(
"path_to_workbench",
help="the path to the workbench",
type=pathlib.Path,
)
parser.add_argument(
"--server",
help="the communication server name",
type=str,
)
parser.add_argument(
"--pyside",
help="pyside version",
type=str,
choices=("PySide2", "PySide6"),
)
ARGS = parser.parse_args()
PYSIDE = ARGS.pyside
else:
PYSIDE = 6

# Imports
if PYSIDE == "PySide6":
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWebEngineCore import QWebEngineScript, QWebEnginePage
from PySide6.QtCore import QUrl, Qt, QTimer, Slot, QObject, Signal
Expand All @@ -42,8 +70,7 @@
QMainWindow,
)

PYSIDE6 = True
except ModuleNotFoundError:
if PYSIDE == "PySide2":
from PySide2.QtWebEngineWidgets import (
QWebEngineView,
QWebEngineScript,
Expand All @@ -58,8 +85,6 @@
QMainWindow,
)

PYSIDE6 = False


THISDIR = os.path.dirname(__file__)

Expand Down Expand Up @@ -228,32 +253,16 @@ def add_viewer(self):

def exec(self):
"""Execute application (start event loop)."""
if PYSIDE6:
if PYSIDE == "PySide6":
return self.app.exec()
else:
return self.app.exec_()


def main():
"""The entry point."""
# Get workbench path from command line arguments
parser = argparse.ArgumentParser(
prog="Render help",
description="Open a help browser for Render Workbench",
)
parser.add_argument(
"path_to_workbench",
help="the path to the workbench",
type=pathlib.Path,
)
parser.add_argument(
"--server",
help="the communication server name",
type=str,
)
args = parser.parse_args()

application = HelpApplication(args.path_to_workbench, args.server)
application = HelpApplication(ARGS.path_to_workbench, ARGS.server)
sys.exit(application.exec())


Expand Down
24 changes: 22 additions & 2 deletions Render/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
from ArchMaterial import (
_ArchMaterial,
_ViewProviderArchMaterial,
getMaterialContainer,
_ArchMaterialContainer,
_ViewProviderArchMaterialContainer,
)

from Render.texture import Texture
Expand All @@ -59,7 +60,7 @@ def make_material(name="Material", color=None, transparency=None, doc=None):
Material(obj)
if App.GuiUp:
ViewProviderMaterial(obj.ViewObject)
getMaterialContainer().addObject(obj)
get_material_container().addObject(obj)
if color:
obj.Color = color[:3]
if len(color) > 3:
Expand All @@ -69,6 +70,25 @@ def make_material(name="Material", color=None, transparency=None, doc=None):
return obj


def get_material_container():
"""Returns a group object to put materials in."""
doc = App.ActiveDocument
containers = (
obj for obj in doc.Objects if obj.Name == "MaterialContainer"
)
try:
container = next(containers)
except StopIteration:
container = doc.addObject(
"App::DocumentObjectGroupPython", "MaterialContainer"
)
container.Label = "Materials"
_ArchMaterialContainer(obj)
if App.GuiUp:
_ViewProviderArchMaterialContainer(container.ViewObject)
return container


def strtobool(val):
"""Convert a string representation of truth to True or False.

Expand Down
5 changes: 2 additions & 3 deletions Render/rdrexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,8 @@ def join(self):
should not be of much use in GUI context.
"""
loop = QEventLoop()
self.thread.finished.connect(loop.quit)
if not self.thread.isFinished():
loop.exec_(flags=QEventLoop.ExcludeUserInputEvents)
self.thread.finished.connect(loop.quit, Qt.QueuedConnection)
loop.exec_(flags=QEventLoop.ExcludeUserInputEvents)


class RendererExecutorCli(threading.Thread):
Expand Down
99 changes: 63 additions & 36 deletions Render/subcontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,20 @@
import sys
import re
from multiprocessing.connection import Client, Listener, Connection, wait
from threading import Thread
from threading import Thread, Event


import FreeCADGui as Gui
import FreeCAD as App

from Render.constants import WBDIR, PKGDIR, FCDVERSION
from Render.virtualenv import get_venv_python
from Render.virtualenv import get_venv_python, get_venv_pyside_version
from PySide import __version_info__ as pyside_version_info

if FCDVERSION > (0, 19):
from PySide.QtCore import (
QProcess,
QProcessEnvironment,
QObject,
Signal,
Slot,
Expand All @@ -68,6 +70,7 @@
else:
from PySide.QtCore import (
QProcess,
QProcessEnvironment,
QObject,
Signal,
Slot,
Expand Down Expand Up @@ -108,25 +111,35 @@ def last_accepted(self):

def _do_listen(self):
"""Listen to incoming conn requests (worker)."""
# TODO Exit gracefully
while conn := self.listener.accept():
self.connections.append(conn)
self.new_connection.emit(conn)

def close(self):
"""Close listener."""
self.listener.close()


class PythonSubprocess(QProcess):
"""A helper to run a Python script as a subprocess.

This object provides:
- echoing of subprocess stdout/stderr
- a communication server to interact with process (based on QLocalServer)
- a communication server to interact with process
"""

winid_available = Signal(int)

def __init__(self, python, args, parent=None):
super().__init__(parent)

# Set environment
environment = QProcessEnvironment.systemEnvironment()
# environment.remove("PYTHONHOME")
# environment.remove("PYTHONPATH")
environment.remove("LD_LIBRARY_PATH")
self.setProcessEnvironment(environment)

# Set stdout/stderr echoing
self.setReadChannel(QProcess.StandardOutput)
self.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
Expand All @@ -138,55 +151,69 @@ def __init__(self, python, args, parent=None):
self.server.start_listening()

server_name = self.server.address
args = args + ["--server", server_name]
self.connection = None
self.connection_listener = None
subcontainer_args = [
"--server",
server_name,
"--pyside",
get_venv_pyside_version(),
]
args = ["-I"] + args + subcontainer_args
self.connections = []
self.connections_listener = Thread(target=self.child_recv)
self.connections_active = Event()
self.connections_active.set()

# Set program and arguments
self.setProgram(python)
self.setArguments(args)

# Log
statement = " ".join([python] + args)
App.Console.PrintLog(statement + "\n")

@Slot(Connection)
def _new_connection(self, connection):
"""Handle new connection.

Nota: only one connection is allowed.
"""
if self.connection:
raise RuntimeError(
"New incoming connection, but connection already set"
)
self.connection = connection
self.connection_listener = Thread(target=self.child_recv)
self.connection_listener.start()
"""Handle new connection."""
self.connections.append(connection)
if not self.connections_listener.is_alive():
self.connections_listener.start()

def child_recv(self):
"""Receive messages from subprocess."""
# TODO exit gracefully
while True:
try:
obj = self.connection.recv()
except EOFError:
break
verb, argument = obj

# Handle
if verb == "WINID":
argument = int(argument)
self.winid_available.emit(argument)
else:
App.Console.PrintError(
"[Render][Sub] Unknown verb/argument: "
f"'{verb}' '{argument}')"
)
while self.connections_active.is_set:
# We use wait to get a timeout parameter
for conn in wait(self.connections, timeout=1):
try:
message = conn.recv()
except EOFError:
self.connections.remove(conn)
if not self.connections:
self.connections_active.clear()
else:
verb, argument = message

# Handle
if verb == "WINID":
argument = int(argument)
self.winid_available.emit(argument)
else:
App.Console.PrintError(
"[Render][Sub] Unknown verb/argument: "
f"'{verb}' '{argument}')"
)

@Slot()
def stop_listening(self):
"""Stop listening to parent messages."""
self.connections_active.clear()
self.connections_listener.join()

@Slot()
def child_send(self, verb, argument=None):
"""Write a message to subprocess."""
message = (verb, argument)
self.connection.send(message)
for conn in self.connections:
conn.send(message)

@Slot()
def _echo_stdout(self):
Expand Down
Loading
Loading