Server.py
1 # -*- coding: utf-8 -*-
2 
3 # ***************************************************************************
4 # * *
5 # * Animate workbench - FreeCAD Workbench for lightweight animation *
6 # * Copyright (c) 2019 Jiří Valášek jirka362@gmail.com *
7 # * *
8 # * This file is part of the Animate workbench. *
9 # * *
10 # * This program is free software; you can redistribute it and/or modify *
11 # * it under the terms of the GNU Lesser General Public License (LGPL) *
12 # * as published by the Free Software Foundation; either version 2 of *
13 # * the License, or (at your option) any later version. *
14 # * for detail see the LICENCE text file. *
15 # * *
16 # * Animate workbench is distributed in the hope that it will be useful, *
17 # * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 # * GNU Lesser General Public License for more details. *
20 # * *
21 # * You should have received a copy of the GNU Library General Public *
22 # * License along with Animate workbench; if not, write to the Free *
23 # * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
24 # * MA 02111-1307 USA *
25 # * *
26 # ***************************************************************************/
27 
28 
35 
36 import FreeCAD
37 import FreeCADGui
38 import communication as com
39 
40 from PySide2.QtWidgets import QMessageBox
41 from os import path
42 
43 
44 PATH_TO_ICONS = path.join(FreeCAD.getHomePath(), "Mod", "Animate", "Resources",
45  "Icons")
46 
47 
48 
70 
71 class ServerProxy(object):
72 
73 
75 
76  cmd_server = None
77 
78 
87 
88  def __init__(self, fp):
89  self.setProperties(fp)
90  fp.Proxy = self
91 
92 
102 
103  def onDocumentRestored(self, fp):
104  self.setProperties(fp)
105  fp.ViewObject.Proxy.setProperties(fp.ViewObject)
106 
107  #Method called by `AnimateDocumentObserver` when document with this class
108  #instance is closing.
109  #
110  #This method is to be called from ServersDocumentObserver so that
111  #the `Port` is freed when document is closed.
112  #
113 
114  def onDocumentClosed(self):
115  # Check there is a cmd_server to close and close it
116  if isinstance(self.cmd_server, com.CommandServer):
117  FreeCAD.Console.PrintMessage("Closing server with it's document\n")
118  self.cmd_server.close()
119 
120 
130 
131  def setProperties(self, fp):
132  # Check properties are present and create them if not
133  if not hasattr(fp, "Address"):
134  fp.addProperty("App::PropertyString", "Address", "Server settings",
135  "IP address where the server will listen for "
136  + "connection.\nValid values are IPv4 and "
137  + "IPv6 addresses or 'localhost'string."
138  ).Address = "localhost"
139  if not hasattr(fp, "Port"):
140  fp.addProperty("App::PropertyIntegerConstraint", "Port",
141  "Server settings", "Port where the server will "
142  + "listen for connections.\n" +
143  "Valid port numbers are in range <0 | 65535>,\n"
144  + "but some may be already taken!"
145  ).Port = (54321, 0, 65535, 1)
146  else:
147  fp.Port = (fp.Port, 0, 65535, 1)
148  if not hasattr(fp, "Running"):
149  fp.addProperty("App::PropertyBool", "Running", "Server settings",
150  "If Server Running is true, then Server listens "
151  + "for new connections."
152  ).Running = False
153 
154  # hide Placement property as there is nothing to display/move
155  fp.setEditorMode("Placement", 2)
156  # make Running property read-only as it's set from context menu/
157  # by double clicking
158  fp.setEditorMode("Running", 1)
159 
160  # try to start cmd_server, if it was running before closing
161  if fp.Running:
162  self.cmd_server = com.startServer(fp.Address, fp.Port)
163  if self.cmd_server == com.SERVER_ERROR_INVALID_ADDRESS:
164  fp.ViewObject.Proxy._icon = path.join(PATH_TO_ICONS,
165  "Server.png")
166  QMessageBox.warning(None, 'Error while starting server',
167  "The address was not in supported format.")
168  fp.Running = False
169  elif self.cmd_server == com.SERVER_ERROR_PORT_OCCUPIED:
170  fp.ViewObject.Proxy._icon = path.join(PATH_TO_ICONS,
171  "Server.png")
172  QMessageBox.warning(None, 'Error while starting server',
173  "The port requested is already occupied.")
174  fp.Running = False
175  else:
176  fp.setEditorMode("Address", 1)
177  fp.setEditorMode("Port", 1)
178  fp.Running = True
179  fp.ViewObject.Proxy._icon = path.join(PATH_TO_ICONS,
180  "ServerRunning.png")
181 
182  # Make an document observer to be notified when document will be closed
183  import AnimateDocumentObserver
185  FreeCAD.animate_observer.addServerToNotify(self,
186  FreeCAD.ActiveDocument.Name)
187 
188 
200 
201  def __getstate__(self):
202  return None
203 
204 
213 
214  def __setstate__(self, state):
215  return None
216 
217 
218 
235 
237 
238 
240 
241 
244  _icon = path.join(PATH_TO_ICONS, "Server.png")
245 
246 
256 
257  def __init__(self, vp):
258  self.setProperties(vp)
259  vp.Proxy = self
260 
261 
273 
274  def onDelete(self, vp, subelements):
275  if vp.Object.Running:
276  FreeCAD.Console.PrintMessage("Deleting server safely.\n")
277  vp.Object.Proxy.cmd_server.close()
278  return True
279 
280 
294 
295  def doubleClicked(self, vp):
296  if not vp.Object.Running:
297  vp.Object.Proxy.cmd_server = com.startServer(vp.Object.Address,
298  vp.Object.Port)
299  if isinstance(vp.Object.Proxy.cmd_server, int):
300  if vp.Object.Proxy.cmd_server == \
301  com.SERVER_ERROR_INVALID_ADDRESS:
302  QMessageBox.warning(None, 'Error while starting server',
303  "The address was not in supported "
304  + "format.")
305  elif vp.Object.Proxy.cmd_server == \
306  com.SERVER_ERROR_PORT_OCCUPIED:
307  QMessageBox.warning(None, 'Error while starting server',
308  "The port requested is already "
309  + "occupied.")
310  else:
311  vp.Object.setEditorMode("Address", 1)
312  vp.Object.setEditorMode("Port", 1)
313  vp.Object.Running = True
314  self._icon = path.join(PATH_TO_ICONS, "ServerRunning.png")
315  elif vp.Object.Running:
316  vp.Object.Proxy.cmd_server.close()
317  vp.Object.setEditorMode("Address", 0)
318  vp.Object.setEditorMode("Port", 0)
319  vp.Object.Running = False
320  self._icon = path.join(PATH_TO_ICONS, "Server.png")
321  return True
322 
323 
333 
334  def setupContextMenu(self, vp, menu):
335  menu.clear()
336  if vp.Object.Running:
337  action = menu.addAction("Disconnect Server")
338  action.triggered.connect(lambda f=self.doubleClicked,
339  arg=vp: f(arg))
340  else:
341  action = menu.addAction("Connect Server")
342  action.triggered.connect(lambda f=self.doubleClicked,
343  arg=vp: f(arg))
344 
345 
350 
351  def getIcon(self):
352  return self._icon
353 
354 
363 
364  def setProperties(self, vp):
365  vp.setEditorMode("AngularDeflection", 2)
366  vp.setEditorMode("BoundingBox", 2)
367  vp.setEditorMode("Deviation", 2)
368  vp.setEditorMode("DisplayMode", 2)
369  vp.setEditorMode("DrawStyle", 2)
370  vp.setEditorMode("Lighting", 2)
371  vp.setEditorMode("LineColor", 2)
372  vp.setEditorMode("LineWidth", 2)
373  vp.setEditorMode("PointColor", 2)
374  vp.setEditorMode("PointSize", 2)
375  vp.setEditorMode("Selectable", 2)
376  vp.setEditorMode("SelectionStyle", 2)
377  vp.setEditorMode("ShapeColor", 2)
378  vp.setEditorMode("Transparency", 2)
379  vp.setEditorMode("Visibility", 2)
380 
381  if vp.Object.Running:
382  self._icon = path.join(PATH_TO_ICONS, "ServerRunning.png")
383  else:
384  self._icon = path.join(PATH_TO_ICONS, "Server.png")
385 
386 
387 
393 
394 class ServerCommand(object):
395 
396 
403 
404  def GetResources(self):
405  return {'Pixmap': path.join(PATH_TO_ICONS, "ServerCmd.png"),
406  'MenuText': "Server",
407  'ToolTip': "Create Server instance."}
408 
409 
416 
417  def Activated(self):
418  doc = FreeCAD.ActiveDocument
419  a = doc.addObject("Part::FeaturePython", "Server")
420  ServerProxy(a)
421  if FreeCAD.GuiUp:
422  ViewProviderServerProxy(a.ViewObject)
423  doc.recompute()
424 
425 
434 
435  def IsActive(self):
436  if FreeCAD.ActiveDocument is None:
437  return False
438  else:
439  return True
440 
441 
442 if FreeCAD.GuiUp:
443  # Add command to FreeCAD Gui when importing this module in InitGui
444  FreeCADGui.addCommand('ServerCommand', ServerCommand())
def __init__(self, vp)
Initialization method for ViewProviderServerProxy.
Definition: Server.py:257
Proxy class for a Gui.ViewProviderDocumentObject Server.ViewObject.
Definition: Server.py:236
def getIcon(self)
Method used to get a path to an icon which will appear in the tree view.
Definition: Server.py:351
def setupContextMenu(self, vp, menu)
Method called by the FreeCAD to customize a context menu for a Server.
Definition: Server.py:334
def doubleClicked(self, vp)
Method called when FeaturePython Server is double-clicked in the Tree View.
Definition: Server.py:295
def __init__(self, fp)
Initialization method for ServerProxy.
Definition: Server.py:88
def addObserver()
Adds an AnimateDocumentObserver between FreeCAD's document observers safely.
def onDelete(self, vp, subelements)
Method called when FeaturePython Server is about to be deleted.
Definition: Server.py:274
def onDocumentRestored(self, fp)
Method called when document is restored to make sure everything is as it was.
Definition: Server.py:103
def GetResources(self)
Method used by FreeCAD to retrieve resources to use for this command.
Definition: Server.py:404
def Activated(self)
Method used as a callback when the toolbar button or the menu item is clicked.
Definition: Server.py:417
cmd_server
A CommandServer instance to handle external commands.
Definition: Server.py:76
Proxy class for a FeaturePython Server instance.
Definition: Server.py:71
ServerCommand class specifying Animate workbench's Server button/command.
Definition: Server.py:394
def setProperties(self, fp)
Method to set properties during initialization or document restoration.
Definition: Server.py:131
def __setstate__(self, state)
Necessary method to avoid errors when trying to restore unserializable objects.
Definition: Server.py:214
_icon
A path to the icon image to be displayed in the Tree View.
Definition: Server.py:244
def __getstate__(self)
Necessary method to avoid errors when trying to save unserializable objects.
Definition: Server.py:201
def IsActive(self)
Method to specify when the toolbar button and the menu item are enabled.
Definition: Server.py:435
def setProperties(self, vp)
Method to hide properties and select appropriate icon to show it the Tree View.
Definition: Server.py:364