I render a huge SVG file with a lot of elements with Cairo, OpenGL and rsvg. I draw svg on cairo surface via rsvg and create an OpenGL texture to draw it. Everything is fine. And now I have to interact with elements from SVG. For example, I want to guess an element by coordinates. And I want to change the background of some path in SVG. In the case of changing background I think, I can change SVG DOM and somehow re-render a part of SVG. But in the case of hit testing elements I'm totally embarrassed.
So, is there some python library to interact with SVG? Is it possible to stay with cairo and rsvg and how can I implement it myself? Or is there a better way to render SVG in OpenGL and interact with it in python? All I want is load SVG, manipulate its DOM and render it
I don't know much about librsvg, but it does not appear to have been updated since 2005, and so I would be inclined to recommend using a different implementation.
If you don't have dependencies on any Python libraries outside of the standard library, then you could use Jython together with Batik. This allows you to add event handlers, as well as change the DOM after rendering.
For an example of how to do this with Java, see this link.
Here's a quick port to Jython 2.2.1 (runs, but not thoroughly tested):
from java.awt.event import WindowAdapter;
from java.awt.event import WindowEvent;
from javax.swing import JFrame;
from org.apache.batik.swing import JSVGCanvas;
from org.apache.batik.swing.svg import SVGLoadEventDispatcherAdapter;
from org.apache.batik.swing.svg import SVGLoadEventDispatcherEvent;
from org.apache.batik.script import Window;
from org.w3c.dom import Document;
from org.w3c.dom import Element;
from org.w3c.dom.events import Event;
from org.w3c.dom.events import EventListener;
from org.w3c.dom.events import EventTarget;
class SVGApplication :
def __init__(self) :
class MySVGLoadEventDispatcherAdapter(SVGLoadEventDispatcherAdapter):
def svgLoadEventDispatcherListener(e):
# At this time the document is available...
self.document = self.canvas.getSVGDocument();
# ...and the window object too.
self.window = self.canvas.getUpdateManager().getScriptingEnvironment().createWindow();
# Registers the listeners on the document
# just before the SVGLoad event is
# dispatched.
registerListeners();
# It is time to pack the frame.
self.frame.pack();
def windowAdapter(e):
# The canvas is ready to load the base document
# now, from the AWT thread.
self.canvas.setURI("doc.svg");
self.frame = JFrame(windowOpened = windowAdapter, size=(800, 600));
self.canvas = JSVGCanvas();
# Forces the canvas to always be dynamic even if the current
# document does not contain scripting or animation.
self.canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
self.canvas.addSVGLoadEventDispatcherListener(MySVGLoadEventDispatcherAdapter()) ;
self.frame.getContentPane().add(self.canvas);
self.frame.show()
def registerListeners(self) :
# Gets an element from the loaded document.
elt = self.document.getElementById("elt-id");
t = elt;
def eventHandler(e):
print e, type(e)
self.window.setTimeout(500,run = lambda : self.window.alert("Delayed Action invoked!"));
#window.setInterval(Animation(), 50);
# Adds a 'onload' listener
t.addEventListener("SVGLoad", false, handleEvent = eventHandler );
# Adds a 'onclick' listener
t.addEventListener("click", false, handleEvent = eventHandler );
if __name__ == "__main__":
SVGApplication();
Run with:
jython -Dpython.path=/usr/share/java/batik-all.jar:/home/jacob/apps/batik-1.7/lib/xml-apis-ext.jar test.py
An alternative approach would be is to use blender. It supports svg import and interaction using python. I don't think it will allow you to edit the dom after import.
I had to do the same (changing element color for instance), and had to modify rsvg library because all those nice features exist but they are hidden. You have to make a new interface to link to the nice features.
Related
I've made a button in Maya that imports an .fbx file when pressed. When I press the button again to import the same .fbx it overwrites the one already in the scene, when the goal is to make a duplicate.
Example of what I'm trying to achieve: I press the button and it imports "sphere.fbx" into the scene as "sphere". I press the button again and it imports "sphere.fbx" into the scene as "sphere1".
This is all I'm using to import the .fbx:
cmds.file(shapePath + shapeList[value], i = True, mnc = True, ns = ":")
I've tried looking around for some solutions but I've had no luck.
Turns out you can't import the fbx file twice from what I can tell. What you can do as a workaround:
Attempt to import file
If it doesn't work, import the fbx file in as a reference
Make that reference part of the scene.
# Attempt to import fbx into the scene
# returnNewNodes(rnn) flag will tell us if it added anything
new_nodes = cmds.file(file_path, i=True, rnn=True)
print("Nodes added: {}".format(new_nodes))
# If nothing was added to the scene, import as reference
if not new_nodes:
cmds.file(file_path, mnc=True, reference=True)
# Make reference part of the scene
cmds.file(file_path, importReference=True)
Can anyone help me? Is it possible to make a script with Python to automatically select every object in Maya's viewport?
Is it possible?
It's very possible though you have to use Maya's api to do it. You can use OpenMayaUI.MDrawTraversal to collect all objects within a camera's frustum.
This may seem more long winded than using OpenMaya.MGlobal.selectFromScreen, but it gives you a few benefits:
You can do it on any camera, despite it not being used as an active view.
You can perform whatever you need to do all in memory without selecting and forcing a redraw.
OpenMaya.MGlobal.selectFromScreen will be interface dependent, meaning that it's not possible to execute it on Maya batch jobs. This will work on either case.
That being said, here's an example that will create a bunch of random boxes, create a camera looking at them, then select all boxes that are within the camera's view:
import random
import maya.cmds as cmds
import maya.OpenMaya as OpenMaya
import maya.OpenMayaUI as OpenMayaUI
# Create a new camera.
cam, cam_shape = cmds.camera()
cmds.move(15, 10, 15, cam)
cmds.rotate(-25, 45, 0, cam)
cmds.setAttr("{}.focalLength".format(cam), 70)
cmds.setAttr("{}.displayCameraFrustum".format(cam), True)
# Create a bunch of boxes at random positions.
val = 10
for i in range(50):
new_cube, _ = cmds.polyCube()
cmds.move(random.uniform(-val, val), random.uniform(-val, val), random.uniform(-val, val), new_cube)
# Add camera to MDagPath.
mdag_path = OpenMaya.MDagPath()
sel = OpenMaya.MSelectionList()
sel.add(cam)
sel.getDagPath(0, mdag_path)
# Create frustum object with camera.
draw_traversal = OpenMayaUI.MDrawTraversal()
draw_traversal.setFrustum(mdag_path, cmds.getAttr("defaultResolution.width"), cmds.getAttr("defaultResolution.height")) # Use render's resolution.
draw_traversal.traverse() # Traverse scene to get all objects in the camera's view.
frustum_objs = []
# Loop through objects within frustum.
for i in range(draw_traversal.numberOfItems()):
# It will return shapes at first, so we need to fetch its transform.
shape_dag_path = OpenMaya.MDagPath()
draw_traversal.itemPath(i, shape_dag_path)
transform_dag_path = OpenMaya.MDagPath()
OpenMaya.MDagPath.getAPathTo(shape_dag_path.transform(), transform_dag_path)
# Get object's long name and make sure it's a valid transform.
obj = transform_dag_path.fullPathName()
if cmds.objExists(obj):
frustum_objs.append(obj)
# At this point we have a list of objects that we can filter by type and do whatever we want.
# In this case just select them.
cmds.select(frustum_objs)
Hope that gives you a better direction.
you can try the following script
import maya.OpenMaya as om
import maya.OpenMayaUI as omUI
view = omUI.M3dView.active3dView()
om.MGlobal.selectFromScreen( 0, 0, view.portWidth(), view.portHeight(),om.MGlobal.kReplaceList)
I found this snippet on https://forums.cgsociety.org/t/list-objects-in-viewport/1463426, and it seems to do the trick. You can read through the discussion for more information
I am trying to make a simple texteditor with basic syntax highlighting, code completion and clickable functions & variables in PyQt5. My best hope to achieve this is using the QScintilla port
for PyQt5.
I have found the following QScintilla-based texteditor example on the Eli Bendersky website (http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt, Victor S. has adapted it to PyQt5). I think this example is a good starting point:
#-------------------------------------------------------------------------
# qsci_simple_pythoneditor.pyw
#
# QScintilla sample with PyQt
#
# Eli Bendersky (eliben#gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------
import sys
import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
ARROW_MARKER_NUM = 8
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
# Set the default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(font)
self.setMarginsFont(font)
self.setMarginWidth(0, fontmetrics.width("00000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Clickable margin 1 for showing markers
self.setMarginSensitivity(1, True)
# self.connect(self,
# SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
# self.on_margin_clicked)
self.markerDefine(QsciScintilla.RightArrow,
self.ARROW_MARKER_NUM)
self.setMarkerBackgroundColor(QColor("#ee1111"),
self.ARROW_MARKER_NUM)
# Brace matching: enable for a brace immediately before or after
# the current position
#
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
# Set Python lexer
# Set style for Python comments (style number 1) to a fixed-width
# courier.
#
lexer = QsciLexerPython()
lexer.setDefaultFont(font)
self.setLexer(lexer)
text = bytearray(str.encode("Arial"))
# 32, "Courier New"
self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)
# Don't want to see the horizontal scrollbar at all
# Use raw message to Scintilla here (all messages are documented
# here: http://www.scintilla.org/ScintillaDoc.html)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
# not too small
self.setMinimumSize(600, 450)
def on_margin_clicked(self, nmargin, nline, modifiers):
# Toggle marker for the line the margin was clicked on
if self.markersAtLine(nline) != 0:
self.markerDelete(nline, self.ARROW_MARKER_NUM)
else:
self.markerAdd(nline, self.ARROW_MARKER_NUM)
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = SimplePythonEditor()
editor.show()
editor.setText(open(sys.argv[0]).read())
app.exec_()
Just copy-paste this code into an empty .py file, and run it. You should get the following simple texteditor appearing on your display:
Notice how perfect the syntax highlighting is! QScintilla certainly did some parsing on the background to achieve that.
Is it possible to make clickable functions & variables for this texteditor? Every self-respecting IDE has it. You click on a function, and the IDE jumps to the function definition. The same for variables. I would like to know:
Does QScintilla support clickable functions & variables?
If not, is it possible to import another python module that implements this feature in the QScintilla texteditor?
EDIT :
λuser noted the following:
Clickable function names require full parsing with a much deeper knowledge of a programming language [..]This is way beyond the scope of Scintilla/QScintilla. Scintilla provides a way to react when the mouse clicks somewhere on the text, but the logic of "where is the definition of a function" is not in Scintilla and probably never will be.However, some projects are dedicated to this task, like ctags. You could simply write a wrapper around this kind of tool.
I guess that writing such wrapper for ctags is now on my TODO list. The very first step is to get a reaction (Qt signal) when the user clicks on a function or variable. And perhaps the function/variable should turn a bit blueish when you hover with the mouse over it, to notify the user that it is clickable. I already tried to achieve this, but am held back by the shortage of QScintilla documentation.
So let us trim down the question to: How do you make a function or variable in the QScintilla texteditor clickable (with clickable defined as 'something happens')
EDIT :
I just returned to this question now - several months later. I have been cooperating with my friend Matic Kukovec to design a website about QScintilla. It is a beginner-friendly tutorial on how to use it:
https://qscintilla.com/
I hope this initiative fills the gap of lacking documentation.
Syntax highlighting is just a matter of running a lexer on the source file to find tokens, then attribute styles to it. A lexer has a very basic understanding of a programming language, it only understands what is a number literal, a keyword, an operator, a comment, a few others and that's all. This is a somewhat simple job that can be performed with just regular expressions.
On the other hand, clickable function names requires requires full parsing with a much deeper knowledge of a programming language, e.g. is this a declaration of a variable or a use, etc. Furthermore, this may require parsing other source files not opened by current editor.
This is way beyond the scope of Scintilla/QScintilla. Scintilla provides a way to react when the mouse clicks somewhere on the text, but the logic of "where is the definition of a function" is not in Scintilla and probably never will be.
However, some projects are dedicated to this task, like ctags. You could simply write a wrapper around this kind of tool.
A way to use Pyqt5 with option with clickable functions and variables.
Your script that have the clickable part Quoted out, would look like this in PyQt5 with a custom signal.
PyQt4 SIGNAL
self.connect(self,SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
self.on_margin_clicked)
PyQt5 SIGNAL
self.marginClicked.connect(self.on_margin_clicked)
PyQt5
import sys
import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
ARROW_MARKER_NUM = 8
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
# Set the default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(font)
self.setMarginsFont(font)
self.setMarginWidth(0, fontmetrics.width("00000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Clickable margin 1 for showing markers
self.setMarginSensitivity(1, True)
self.marginClicked.connect(self.on_margin_clicked)
self.markerDefine(QsciScintilla.RightArrow,
self.ARROW_MARKER_NUM)
self.setMarkerBackgroundColor(QColor("#ee1111"),
self.ARROW_MARKER_NUM)
# Brace matching: enable for a brace immediately before or after
# the current position
#
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
# Set Python lexer
# Set style for Python comments (style number 1) to a fixed-width
# courier.
#
lexer = QsciLexerPython()
lexer.setDefaultFont(font)
self.setLexer(lexer)
text = bytearray(str.encode("Arial"))
# 32, "Courier New"
self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)
# Don't want to see the horizontal scrollbar at all
# Use raw message to Scintilla here (all messages are documented
# here: http://www.scintilla.org/ScintillaDoc.html)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
# not too small
self.setMinimumSize(600, 450)
def on_margin_clicked(self, nmargin, nline, modifiers):
# Toggle marker for the line the margin was clicked on
if self.markersAtLine(nline) != 0:
self.markerDelete(nline, self.ARROW_MARKER_NUM)
else:
self.markerAdd(nline, self.ARROW_MARKER_NUM)
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = SimplePythonEditor()
editor.show()
editor.setText(open(sys.argv[0]).read())
app.exec_()
I got a helpful answer from Matic Kukovec through mail, that I would like to share here. Matic Kukovec made an incredible IDE based on QScintilla: https://github.com/matkuki/ExCo. Maybe it will inspire more people to dig deeper into QScintilla (and clickable variables and functions).
Hotspots make text clickable. You have to style it manualy using the QScintilla.SendScintilla function.
Example function I used in my editor Ex.Co. ( https://github.com/matkuki/ExCo ):
def style_hotspot(self, index_from, length, color=0xff0000):
"""Style the text from/to with a hotspot"""
send_scintilla =
#Use the scintilla low level messaging system to set the hotspot
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STYLESETHOTSPOT, 2, True)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEFORE, True, color)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEUNDERLINE, True)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STARTSTYLING, index_from, 2)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETSTYLING, length, 2)
This makes text in the QScintilla editor clickable when you hover the mouse over it.
The number 2 in the above functions is the hotspot style number.
To catch the event that fires when you click the hotspot, connect to these signals:
QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
For more details look at Scintilla hotspot documentation:
http://www.scintilla.org/ScintillaDoc.html#SCI_STYLESETHOTSPOT
and QScintilla hotspot events:
http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html#a5eff383e6fa96cbbaba6a2558b076c0b
First of all, a big thank you to Mr. Kukovec! I have a few questions regarding your answer:
(1) There are a couple of things I don't understand in your example function.
def style_hotspot(self, index_from, length, color=0xff0000):
"""Style the text from/to with a hotspot"""
send_scintilla = # you undefine send_scintilla?
#Use the scintilla low level messaging system to set the hotspot
self.SendScintilla(..) # What object does 'self' refer to in this
self.SendScintilla(..) # context?
self.SendScintilla(..)
(2) You say "To catch the event that fires when you click the hotspot, connect to these signals:"
QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
How do you actually connect to those signals? Could you give one example? I'm used to the PyQt signal-slot mechanism, but I never used it on QScintilla. It would be a big help to see an example :-)
(3) Maybe I missed something, but I don't see where you define in QScintilla that functions and variables (and not other things) are clickable in the source code?
Thank you so much for your kind help :-)
Have a look at the following documentation:
https://qscintilla.com/#clickable_text
There are two ways to make things clickable in Qscintilla - you can use hotspots or indicators. hotspots require you to override the default behavior of underlying lexer, but indicators are more convenient for your use case, I think.
I suggest you have a look at indicators which can help you to make text clickable and you can define event handlers that get executed when it gets clicked.
https://qscintilla.com/#clickable_text/indicators
I want to show all attributes and tags in auto completion list of a html file if auto completion threshold is set to 1. I have tried this code to use APIs i set this code after the file is loaded in new mdi child(sub window) but it is not working:
lexer=Qsci.QsciLexerHTML()
api = Qsci.QsciAPIs(lexer)
## Add autocompletion strings
api.add("aLongString")
api.add("aLongerString")
api.add("aDifferentString")
api.add("sOmethingElse")
## Compile the api for use in the lexer
api.prepare()
self.activeMdiChild().setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.activeMdiChild().setLexer(lexer)
and my horizontal scroll bar is visible all the time i want to set it as scrollbarasneeded. please tell how to do these two tasks.
Other than failing to set the auto-completion threshold, there doesn't seem to be anything wrong with your example code. Here's a minimal working example:
from PyQt4 import QtGui, Qsci
class Window(Qsci.QsciScintilla):
def __init__(self):
Qsci.QsciScintilla.__init__(self)
lexer = Qsci.QsciLexerHTML(self)
api = Qsci.QsciAPIs(lexer)
api.add('aLongString')
api.add('aLongerString')
api.add('aDifferentString')
api.add('sOmethingElse')
api.prepare()
self.setAutoCompletionThreshold(1)
self.setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.setLexer(lexer)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
The scrollbar-as-needed feature cannot really be solved, unless you are willing to reimplement everything yourself (which would not be easy). The underlying Scintilla control doesn't directly support automatic horizontal scrollbar hiding, because it involves a potentially very expensive calculation (i.e. determining the longest line). Most people who use Scintilla/Qscintilla just learn to put up with the ever-present horizontal scrollbar.
First of all, it is important to mention that I'm learning Python and Gtk+ 3, so I'm not an advanced programmer in these languages.
I'm trying to make a graphical interface in Gtk3 for a Python script that creates a png image, and I'd like to display it, but the PyGobject documentation is so scarce that I haven't found a way to do that. So far, my interface looks like this:
The buttons and text entries are arranged in a grid, and I'd like to keep empty the big space (represented by the big button) to the right until the script finishes building the image, and then show it in that area. The code is here.
Is there a way to do that using Python in Gtk3?
Thanks in advance,
Germán.
EDIT
Taking a look at the demos pointed out by #gpoo I discovered the Frame widget, and I implemented it in my GUI. This is how it looks like:
Inside the window class, I add the Frame to the grid:
self.frame_rgb = Gtk.Frame(label='RGB image')
self.frame_rgb.set_label_align(0.5, 0.5)
self.frame_rgb.set_shadow_type(Gtk.ShadowType.IN)
self.grid.attach_next_to(self.frame_rgb, self.label_img_name,
Gtk.PositionType.RIGHT, 3, 8)
I also connect the Run button to a callback function, so that when I click on it, my script creates and then displays the png image:
self.button_run = Gtk.Button(stock=Gtk.STOCK_EXECUTE)
self.button_run.connect('clicked', self.on_button_run_clicked)
self.grid.attach_next_to(self.button_run, self.entry_b_img,
Gtk.PositionType.BOTTOM, 1, 1)
Finally, my callback function is (no calculations yet, only render the image to the Frame for testing purposes):
def on_button_run_clicked(self, widget):
self.img = Gtk.Image.new_from_file('astro-tux.png')
self.frame_rgb.add(self.img)
but I got the following error when I click the Run button:
(makeRGB.py:2613): Gtk-WARNING **: Attempting to add a widget with
type GtkImage to a GtkFrame, but as a GtkBin subclass a GtkFrame can
only contain one widget at a time; it already contains a widget of
type GtkImage
Any help is appreciated!
You can use Gtk.Image. If you generate a file, you could use:
img = Gtk.Image.new_from_file('/path/to/my_file.png')
and add img to the container (GtkGrid in your case). Or, if you already have the Gtk.Image there, you can use:
img.set_from_file('/path/to/my_file.png')
Instead of ...from_file you can use from_pixbuf, and you can create a Gdk.Pixbuf from a stream.
In general, you can use the documentation for C and change the idiom to Python. Also, you can check the demos available in PyGObject, in particular, the demo for handling images.