Possible to control the view using python?

Post here for help on using FreeCAD's graphical user interface (GUI).
Forum rules
and Helpful information
IMPORTANT: Please click here and read this first, before asking for help

Also, be nice to others! Read the FreeCAD code of conduct!
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Possible to control the view using python?

Postby j-dowsett » Sun Oct 16, 2011 9:00 pm

I was wondering if it's possible to rotate the view using python commands, to achieve something equivalent to the 'Angle' slider on the 'View Turntable' dialog - though primarily I'd be interested in rotating about an axis coming out of the page.

An example would be that sometimes the 'front view' etc results are upside down or 90* around from where I'd like to view it, or eg that I want to view at a specific angle to one of the orthogonal views. I found the view turntable code in the source, but it's all way over my head.



While I'm here ... I remember seeing a reference to a log file but can't remember any details, where would I find that file? I've been experiencing numerous crashes while viewing a point cloud, though I suspect it's due to the integrated graphics on the laptop as it's not happened on my desktop.


Thanks,
Joe
User avatar
NormandC
Posts: 18534
Joined: Sat Feb 06, 2010 9:52 pm
Location: Québec, Canada

Re: Possible to control the view using python?

Postby NormandC » Sun Oct 16, 2011 9:59 pm

Hi,

Have you taken a look at the Macro recipes page? There's one for rotating a view.

https://sourceforge.net/apps/mediawiki/ ... os_recipes
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Re: Possible to control the view using python?

Postby j-dowsett » Sun Oct 16, 2011 11:03 pm

Excellent, thanks for that. Will give me somewhere to start.
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Re: Possible to control the view using python?

Postby j-dowsett » Fri Oct 28, 2011 4:25 pm

Success! I've made myself a lovely little GUI to allow precise rotation of the view. Need to tweak one or two things and then I'll add it to the Macros Recipes if anyone's interested?
rotate.jpg
rotate.jpg (29.76 KiB) Viewed 1648 times
(Yes, I know, I shouldn't start drawing icons for a living)
User avatar
NormandC
Posts: 18534
Joined: Sat Feb 06, 2010 9:52 pm
Location: Québec, Canada

Re: Possible to control the view using python?

Postby NormandC » Sat Oct 29, 2011 4:16 am

Definitely! Nice work! :)
jmaustpc
Posts: 9630
Joined: Tue Jul 26, 2011 6:28 am
Location: Australia

Re: Possible to control the view using python?

Postby jmaustpc » Sat Oct 29, 2011 5:11 am

Well done, this looks great!

I have a track ball, so it is in effect a 2 button mouse with NO scroll wheel. I could find something like this useful.

I was thinking if we had something like this in a small always on top floating window, it could be a really convenient.

It could even be extended with a zoom slide bar, and ability to create and save custom view/camera/zoom positions?

Jim
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Re: Possible to control the view using python?

Postby j-dowsett » Sat Oct 29, 2011 10:35 am

Seems to work well, at the moment it centres the bounding box centre of the active object in order to rotate about that point, but I think it really wants the centre of the bounding box for everything in the active document - shouldn't be too hard to cycle through objects and find a complete bounding box. The other thing I need is to find the screen resolution - it's currently hardwired to initialise to the top right of my screen so likely be annoyingly positioned on different monitors.

A zoom slide won't be difficult, though choosing end-points for the slider might!

Custom views wouldn't be difficult in principle but I'd have thought you'd want to save them in the part/project file so that they were retrieved whenever the project was opened? I assume that would require doing something with the file spec? Hmm, maybe that wouldn't be so tough since the fcstd file is just a zip of associated files, just add a views file into that. Would be beyond me though.
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Re: Possible to control the view using python?

Postby j-dowsett » Sat Oct 29, 2011 6:19 pm

Sweet. I'm happy with myself.

It now looks through objects in ActiveDocument to find an all encompassing bounding box, using the centre of this for its point of rotation. Note, it only uses objects of type 'Mesh', 'Points', or 'Part', there may be others it should include but the point was to exclude Sketches.

It also rotates about that centre point without repositioning this to the centre of the screen - this behaviour felt most natural to me.

It should also initially position itself to the top right.

Anyway, see what you think.

I have a track ball, so it is in effect a 2 button mouse with NO scroll wheel. I could find something like this useful.
Somehow this had escaped me, despite sometimes finding myself on a laptop without a mouse and struggling....


NOTE - I'm not a programmer so I'd be surprised if there's not some bad practice going on, feel free to point out flaws.
Also, I had to use

Code: Select all

	self.view = self.cam.orientation.getValue()	
	self.view = coin.SbRotation(self.view.getValue())
to isolate self.view from self.cam.orientation, if I exclude the second line self.view remains connected to self.cam.orientation. Is there a better way to achieve this?

Not sure the Help forum is the right place for this any more...

Code: Select all

from PyQt4 import QtGui, QtCore
from pivy import coin
from math import pi


def find_centre():
	xmax = xmin = ymax = ymin = zmax = zmin = 0	
	for obj in App.ActiveDocument.Objects:
		if obj.Type[:4] == "Mesh":
			box = obj.Mesh.BoundBox
		elif obj.Type[:6] == "Points":
			box = obj.Points.BoundBox
		elif obj.Type[:4] == "Part":
			box = obj.Shape.BoundBox
		else:
			continue
		xmax = max(xmax, box.XMax)
		xmin = min(xmin, box.XMin)
		ymax = max(ymax, box.YMax)
		ymin = min(ymin, box.YMin)
		zmax = max(zmax, box.ZMax)
		zmin = min(zmin, box.ZMin)
			
	centre = FreeCAD.Vector((xmax+xmin)/2.0, (ymax+ymin)/2.0, (zmax+zmin)/2.0)
	return centre

	

class rotate_gui(QtGui.QWidget):  
	def __init__(self):
		super(rotate_gui, self).__init__()      
		self.initUI()
		self.initRotate()
   
        
	def initUI(self):
		self.sld = [0,1,2]
		self.tbox = [0,1,2]
		icon = [0,1,2]
		icons = ('/home/joe/.FreeCAD/right.png', '/home/joe/.FreeCAD/up.png', '/home/joe/.FreeCAD/out.png')
	
		for i in range(3): 
			self.sld[i] = QtGui.QSlider(QtCore.Qt.Horizontal, self)
			self.sld[i].setFocusPolicy(QtCore.Qt.NoFocus)
			self.sld[i].setSingleStep(5)
			self.sld[i].setPageStep(15)
			self.sld[i].setValue(0)
			self.sld[i].setMaximum(180)
			self.sld[i].setMinimum(-180)
			self.tbox[i] = QtGui.QLineEdit(self)
			self.tbox[i].setText("0")
			self.tbox[i].setAlignment(QtCore.Qt.AlignRight)
			icon[i] = QtGui.QLabel(self)
			icon[i].setPixmap(QtGui.QPixmap(icons[i]))
			self.sld[i].valueChanged[int].connect(self.valueChange)
			self.tbox[i].returnPressed.connect(self.valueEntered)
        
		resetButton = QtGui.QPushButton("Reset")
		resetButton.clicked.connect(self.reset)
       
		okButton = QtGui.QPushButton("OK")
		okButton.clicked.connect(self.close)
       
		cancelButton = QtGui.QPushButton("Cancel")
		cancelButton.clicked.connect(self.cancel)
       
		hbox = [0,1,2,3]
		vbox = QtGui.QVBoxLayout()
	
		for i in range(3):
			hbox[i] = QtGui.QHBoxLayout()
			hbox[i].addWidget(icon[i],1)
			hbox[i].addWidget(self.sld[i],4)
			hbox[i].addWidget(self.tbox[i],1)
			vbox.addLayout(hbox[i])

		hbox[3] = QtGui.QHBoxLayout()
		hbox[3].addWidget(resetButton,1)
		hbox[3].addWidget(okButton,1)
		hbox[3].addWidget(cancelButton,1)
		vbox.addStretch(1)
		vbox.addLayout(hbox[3])
	
		self.setLayout(vbox)
        
		a = QtGui.QDesktopWidget()
		right = a.availableGeometry().width()
                   
		self.setGeometry(right-300, 0, 300, 150)
		self.setWindowTitle('Rotate view...')
		self.show()
  
  
	def initRotate(self):
		self.internal = False
		self.current = 0
    
		self.cam = Gui.ActiveDocument.ActiveView.getCameraNode()	
		self.centre = coin.SbVec3f(find_centre())		
		self.view = self.cam.orientation.getValue()
		self.pos = self.cam.position.getValue()
	
		#store a copy of the original view to be restored in the case of user selecting Reset or Cancel
		self.original_view = coin.SbRotation(self.view.getValue())
		self.original_pos = coin.SbVec3f(self.pos.getValue())
	
		self.config_direction(0)


	def reset(self):
		#reset the view to the original one
		self.cam.orientation = self.original_view
		self.cam.position = self.original_pos
		for sld in self.sld:
			sld.setValue(0)
		for tbox in self.tbox:
			tbox.setText("0")
    	

	def cancel(self):
		self.reset()
		self.close()
    	
   	
	def config_direction(self, i):
		#evaluate the vectors corresponding to the three directions for the current view, and assign the i-th one to self.direction
		self.view = self.cam.orientation.getValue()	
		self.view = coin.SbRotation(self.view.getValue())
		self.pos = self.cam.position.getValue()
		self.pos = coin.SbVec3f(self.pos.getValue())
		
		up = coin.SbVec3f(0,1,0)
		self.up = self.view.multVec(up)
		out = coin.SbVec3f(0,0,1)
		self.out = self.view.multVec(out)
		u = self.up.getValue()
		o = self.out.getValue()
		r = (u[1]*o[2]-u[2]*o[1], u[2]*o[0]-u[0]*o[2], u[0]*o[1]-u[1]*o[0])
		self.right = coin.SbVec3f(r)
			
		self.direction = [self.right, self.up, self.out][i]


	def check(self, i):
		#check if the direction of rotation has changed, if so then set previous slider & textbox to zero, and setup the new direction
		if i <> self.current:
			self.internal = True
			self.sld[self.current].setValue(0)
			self.tbox[self.current].setText("0")
			self.internal = False
			self.current = i
			self.config_direction(i)


	def rotate(self, value):
		#carry out the desired rotation about self.direction
		val = value*pi/180.0
		rot = coin.SbRotation(self.direction, -val)		
		nrot = self.view*rot
		prot = rot.multVec(self.pos - self.centre) + self.centre
		self.cam.orientation = nrot
		self.cam.position = prot

    
	def valueChange(self, value):
		#respond to the change in value of a slider, update the corresponding text box, check for a direction change then rotate
		#if the value was changed internally, ignore event.
		if self.internal:
			return
    		
		sender = self.sender()
		for i in range(3):
			if sender == self.sld[i]:
				break
		self.tbox[i].setText(str(value))
		self.check(i)
		self.rotate(value)

 
	def valueEntered(self):
		#respond to a value being entered in a text box, updating the corresponding slider, check for direction change then rotate
		sender = self.sender()
		for i in range(3):
			if sender == self.tbox[i]:
				break
		value = int(self.tbox[i].text())
		self.internal = True
		self.sld[i].setValue(value)
		self.internal = False
		self.check(i)
		self.rotate(value)


rotate = rotate_gui()
<edited for inconsistent indents>
Last edited by j-dowsett on Sat Oct 29, 2011 8:09 pm, edited 3 times in total.
j-dowsett
Posts: 204
Joined: Wed Sep 07, 2011 9:37 am

Re: Possible to control the view using python?

Postby j-dowsett » Sat Oct 29, 2011 6:24 pm

Oh, have some dodgy icons ;)

You'll need to edit the script (fourth line in initUI) to change the path to wherever you put them.
Attachments
icons.zip
(2.2 KiB) Downloaded 753 times
Last edited by j-dowsett on Sun Oct 30, 2011 10:20 am, edited 3 times in total.
User avatar
yorik
Site Admin
Posts: 11700
Joined: Tue Feb 17, 2009 9:16 pm
Location: São Paulo, Brazil
Contact:

Re: Possible to control the view using python?

Postby yorik » Sat Oct 29, 2011 10:50 pm

Well done, j-dowsett, excellent job!! Can you put in in the macro receipes? I can do that too if you prefer.
I would suggest using the Macro path for the icons, so it is easy for users to install, they just copy everything to the same place.
In python, you can get the macro path like this:

Code: Select all

path = FreeCAD.ConfigGet("UserAppData")
About self.cam.orientation, yes, coin has all its own structure, you often need to dig quite deep inside things to retrieve the value you want...
And yes, this could definitely go in a more advanced forum, but there is no way to change, so...