source: main/branches/3D/plugins/freecad/OpenPLM/openplm.py @ 597

Revision 597, 37.4 KB checked in by agalech, 9 years ago (diff)

branch 3D: 3D view: make it possible to show/hide each shape of a stp file

Line 
1import os
2import shutil
3import json
4import urllib
5import webbrowser
6import tempfile
7
8
9# poster makes it possible to send http request with files
10# sudo easy_install poster
11from poster.encode import multipart_encode
12from poster.streaminghttp import StreamingHTTPRedirectHandler, StreamingHTTPHandler
13
14import urllib2
15
16
17import PyQt4.QtGui as qt
18from PyQt4 import QtCore
19
20import FreeCAD, FreeCADGui
21import Part
22
23connect = QtCore.QObject.connect
24
25def main_window():
26    app = qt.qApp
27    for x in app.topLevelWidgets():
28        if type(x) == qt.QMainWindow:
29            return x
30
31def save(gdoc):
32    FreeCADGui.runCommand("Std_Save")
33    gdoc.Label = os.path.splitext(os.path.basename(gdoc.FileName))[0] or gdoc.Label
34   
35def close(gdoc):
36    FreeCADGui.runCommand("Std_CloseActiveWindow")
37    gdoc2 = FreeCAD.ActiveDocument
38    if gdoc == gdoc2:
39        FreeCAD.closeDocument(gdoc.Name)
40
41class OpenPLMPluginInstance(object):
42   
43    #: location of openPLM server
44    SERVER = "http://localhost:8000/"
45    #: OpenPLM main directory
46    OPENPLM_DIR = os.path.expanduser("~/.openplm")
47    #: directory where files are stored
48    PLUGIN_DIR = os.path.join(OPENPLM_DIR, "freecad")
49    #: gedit plugin configuration file
50    CONF_FILE = os.path.join(PLUGIN_DIR, "conf.json")
51
52    def __init__(self):
53       
54        self.opener = urllib2.build_opener(StreamingHTTPHandler(),
55                                           StreamingHTTPRedirectHandler(),
56                                           urllib2.HTTPCookieProcessor())
57        self.opener.addheaders = [('User-agent', 'openplm')]
58        self.username = ""
59        self.connected = False
60        self.documents = {}
61        self.disable_menuitems()
62
63        data = self.get_conf_data()
64        if "server" in data:
65            type(self).SERVER = data["server"]
66
67        try:
68            os.makedirs(self.PLUGIN_DIR, 0700)
69        except os.error:
70            pass
71
72        self.window = main_window()
73
74    def disable_menuitems(self):
75        self.connected = False
76        FreeCADGui.updateGui()
77
78    def enable_menuitems(self):
79        self.connected = True
80        FreeCADGui.updateGui()
81
82    def login(self, username, password):
83        """
84        Open a login dialog and connect the user
85        """
86       
87        self.username = username
88        self.password = password
89
90        data = dict(username=self.username, password=self.password)
91        res = self.get_data("api/login/", data)
92        if res["result"] == "ok":
93            #self._action_group2.set_sensitive(True)
94            self.load_managed_files()
95            self.enable_menuitems()
96        else:
97            self.disable_menuitems()
98            raise ValueError(res["error"])
99
100    def create(self, data, filename, unlock):
101        res = self.get_data("api/create/", data)
102        if not filename:
103            return False, "Bad file name"
104        if res["result"] != "ok":
105            return False, res["error"]
106        else:
107            doc = res["object"]
108            # create a new doc
109            rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
110                               doc["revision"])
111            try:
112                os.makedirs(rep, 0700)
113            except os.error:
114                # directory already exists, just ignores the exception
115                pass
116            gdoc = FreeCAD.ActiveDocument
117            path = os.path.join(rep, filename)
118            fileName, fileExtension = os.path.splitext(filename)
119            path_stp=os.path.join(rep, (fileName+".stp"))
120            #create temporal file stp 
121            Part.export(gdoc.Objects,path_stp)
122            gdoc.FileName = path
123            save(gdoc)
124           
125            #upload stp and freecad object
126            doc_step_file=self.upload_file(doc, path_stp) # XXX
127            doc_file = self.upload_file(doc, path) # XXX
128           
129            #remove temporal file stp 
130            os.remove(path_stp)
131           
132           
133            self.add_managed_file(doc, doc_file, path)
134            self.load_file(doc, doc_file["id"], path, gdoc)
135            if not unlock:
136                self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_file["id"])) # XXX
137                self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_step_file["id"])) # XXX
138            else:
139                self.send_thumbnail(gdoc)
140                self.forget(gdoc)
141            return True, ""
142
143    def get_data(self, url, data=None, show_errors=True, reraise=False):
144        data_enc = urllib.urlencode(data) if data else None
145        try:
146            return json.load(self.opener.open(self.SERVER + url, data_enc))
147        except urllib2.URLError as e:
148            if show_errors:
149                message = e.reason if hasattr(e, "reason") else ""
150                if not isinstance(message, basestring):
151                    message = str(e)
152                show_error(u"Can not open '%s':>\n\t%s" % \
153                           (url, unicode(message, "utf-8")), self.window)
154            if reraise:
155                raise
156            else:
157                return {"result" : "error", "error" : ""}
158
159    def upload_file(self, doc, path):
160        url = self.SERVER + "api/object/%s/add_file/" % doc["id"]
161        datagen, headers = multipart_encode({"filename": open(path, "rb")})
162        # Create the Request object
163        request = urllib2.Request(url, datagen, headers)
164        res = json.load(self.opener.open(request))
165        return res["doc_file"]
166       
167       
168
169       
170
171    def download(self, doc, doc_file):
172   
173   
174   
175        f = self.opener.open(self.SERVER + "file/%s/" % doc_file["id"])
176        rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
177                           doc["revision"])
178        try:
179            os.makedirs(rep, 0700)
180        except os.error:
181            # directory already exists, just ignores the exception
182            pass
183        dst_name = os.path.join(rep, doc_file["filename"])
184        dst = open(dst_name, "wb")
185        shutil.copyfileobj(f, dst)
186        f.close()
187        dst.close()
188        self.add_managed_file(doc, doc_file, dst_name)
189        self.load_file(doc, doc_file["id"], dst_name)
190
191    def attach_to_part(self, doc, part_id):
192        res = self.get_data("api/object/%s/attach_to_part/%s/" % (doc["id"], part_id))
193        if res["result"] == "ok":
194            url = self.SERVER + "object/%s/%s/%s/parts" % (doc["type"],
195                    doc["reference"], doc["revision"])
196            webbrowser.open_new_tab(url)
197        else:
198            show_error("Can not attach\n%s" % res.get('error', ''), self.window)
199
200    def check_out(self, doc, doc_file):
201        self.get_data("api/object/%s/checkout/%s/" % (doc["id"], doc_file["id"]))
202        self.download(doc, doc_file)
203       
204
205        #on va locker fichier step associe
206        url= "api/object/%s/files/all/" % doc["id"]
207        res = PLUGIN.get_data(url)
208        fileName, fileExtension = os.path.splitext(doc_file["filename"])
209        doc_step = [obj for obj in res["files"] if obj["filename"] == fileName+".stp"]
210        if not len(doc_step)==0:   
211            self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_step[0]["id"]))
212        #end locker   
213
214    def add_managed_file(self, document, doc_file, path):
215        data = self.get_conf_data()
216        documents = data.get("documents", {})
217        doc = documents.get(str(document["id"]), dict(document))
218        files = doc.get("files", {})
219        files[doc_file["id"]] = path
220        doc["files"] = files
221        documents[doc["id"]] = doc
222        data["documents"] = documents
223        self.save_conf(data)
224
225    def save_conf(self, data):
226        f = open(self.CONF_FILE, "w")
227        json.dump(data, f)
228        f.close()
229   
230    def get_conf_data(self):
231        try:
232            with open(self.CONF_FILE, "r") as f:
233                try:
234                    return json.load(f)
235                except ValueError:
236                    # empty/bad config file
237                    return {}
238        except IOError:
239            # file does not exist
240            return {}
241   
242    def set_server(self, url):
243        if not url.endswith("/"):
244            url += "/"
245        data = self.get_conf_data()
246        data["server"] = url
247        type(self).SERVER = url
248        self.save_conf(data)
249        if self.username:
250            try:
251                self.login(self.username, self.password)
252            except ValueError:
253                pass
254
255    def get_managed_files(self):
256        data = self.get_conf_data()
257        files = []
258        for doc in data.get("documents", {}).itervalues():
259            files.extend((d, doc) for d in doc.get("files", {}).items())
260        return files
261   
262    def forget(self, gdoc=None, delete=True, close_doc=False):
263        gdoc = gdoc or FreeCAD.ActiveDocument
264        if gdoc and gdoc in self.documents:
265            doc = self.documents[gdoc]["openplm_doc"]
266            doc_file_id = self.documents[gdoc]["openplm_file_id"]
267            path = self.documents[gdoc]["openplm_path"]
268            del self.documents[gdoc]
269            data = self.get_conf_data()
270            del data["documents"][str(doc["id"])]["files"][str(doc_file_id)]
271            if not  data["documents"][str(doc["id"])]["files"]:
272                del data["documents"][str(doc["id"])]
273            self.save_conf(data)
274            #on va unlocker
275            self.get_data("api/object/%s/unlock/%s/" % (doc["id"], doc_file_id))
276            url= "api/object/%s/files/all/" % doc["id"]
277            res = PLUGIN.get_data(url)
278            root, f_name = os.path.split(path)
279            fileName, fileExtension = os.path.splitext(f_name)
280            doc_step = [obj for obj in res["files"] if obj["filename"] == fileName+".stp"]
281            if not len(doc_step)==0:   
282                self.get_data("api/object/%s/unlock/%s/" % (doc["id"], doc_step[0]["id"]))
283 
284            #end unlocker   
285                         
286            if delete and os.path.exists(path):
287                os.remove(path)
288            if close_doc:
289                close(gdoc)
290
291    def load_managed_files(self):
292        for (doc_file_id, path), doc in self.get_managed_files():
293            self.load_file(doc, doc_file_id, path)
294
295    def load_file(self, doc, doc_file_id, path, gdoc=None):
296        try:
297            document = gdoc or FreeCAD.openDocument(path)
298        except IOError:
299            show_error("Can not load %s" % path, self.window)
300            return
301        self.documents[document] = dict(openplm_doc=doc,
302            openplm_file_id=doc_file_id, openplm_path=path)
303        if " rev. " not in document.Label:
304            document.Label = document.Label + " / %(name)s rev. %(revision)s" % doc
305        return document
306
307    def check_in(self, gdoc, unlock, save_file=True):
308        if gdoc and gdoc in self.documents:
309            doc = self.documents[gdoc]["openplm_doc"]
310            doc_file_id = self.documents[gdoc]["openplm_file_id"]
311            #doc_file_name = self.documents[gdoc]["openplm_file_name"]
312            path = self.documents[gdoc]["openplm_path"]
313            def func():
314                # headers contains the necessary Content-Type and Content-Length>
315                # datagen is a generator object that yields the encoded parameters
316                datagen, headers = multipart_encode({"filename": open(path, "rb")})
317                # Create the Request object
318               
319               
320                #check-in fichier step asscocies if exists
321                #api/doc_id/files/[all/]
322                url= "api/object/%s/files/all/" % doc["id"]
323                res = PLUGIN.get_data(url)
324                root, f_name = os.path.split(path)
325                fileName, fileExtension = os.path.splitext(f_name)
326                doc_step = [obj for obj in res["files"] if obj["filename"] == fileName+".stp"]
327               
328                if len(doc_step)==0:    #il faut generer un nouvelle fichier step
329
330                    fileName, fileExtension = os.path.splitext(path)
331                    path_stp=fileName+".stp"
332                    Part.export(gdoc.Objects,path_stp)
333                    doc_step_file=self.upload_file(doc,path_stp) # XXX
334                    doc_step.append(doc_step_file)
335
336                else:                   #il faut un check-in
337 
338                    fileName, fileExtension = os.path.splitext(path)
339                    Part.export(gdoc.Objects,fileName+".stp")
340                    datagen, headers = multipart_encode({"filename": open(fileName+".stp", "rb")})
341                    url = self.SERVER + "api/object/%s/checkin/%s/" % (doc["id"], doc_step[0]["id"]) # XXX
342                    request = urllib2.Request(url, datagen, headers)
343                    res = self.opener.open(request)
344                    os.remove(fileName+".stp")                 
345               
346               
347               
348                url = self.SERVER + "api/object/%s/checkin/%s/" % (doc["id"], doc_file_id) # XXX
349                request = urllib2.Request(url, datagen, headers)               
350                res = self.opener.open(request)
351               
352
353               
354                if not unlock:
355                    self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_file_id)) # XXX
356                    self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_step[0]["id"])) # XXX
357                else:
358                    self.send_thumbnail(gdoc)
359                    self.forget(gdoc)
360            if save_file:
361                save(gdoc)
362                func()
363            else:
364                func()
365        else:
366            show_error('Can not check in : file not in openPLM', self.window)
367
368    def send_thumbnail(self, gdoc):
369        doc = self.documents[gdoc]["openplm_doc"]
370        doc_file_id = self.documents[gdoc]["openplm_file_id"]
371        view = FreeCADGui.ActiveDocument.ActiveView
372        f = tempfile.NamedTemporaryFile(suffix=".png")
373        view.saveImage(f.name)
374        datagen, headers = multipart_encode({"filename": open(f.name, "rb")})
375        # Create the Request object
376        url = self.SERVER + "api/object/%s/add_thumbnail/%s/" % (doc["id"], doc_file_id)
377        request = urllib2.Request(url, datagen, headers)
378        res = self.opener.open(request)
379        f.close()
380
381    def revise(self, gdoc, revision, unlock):
382        if gdoc and gdoc in self.documents:
383            doc = self.documents[gdoc]["openplm_doc"]
384            doc_file_id = self.documents[gdoc]["openplm_file_id"]
385            path = self.documents[gdoc]["openplm_path"]
386            res = self.get_data("api/object/%s/revise/" % doc["id"],
387                                {"revision" : revision})
388            new_doc = res["doc"]
389            name = os.path.basename(gdoc.FileName)
390            doc_file = None
391            for f in res["files"]:
392                if f["filename"] == name:
393                    doc_file = f
394                    break
395            # create a new doc
396            rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
397                               revision)
398            try:
399                os.makedirs(rep, 0700)
400            except os.error:
401                # directory already exists, just ignores the exception
402                pass
403   
404            self.forget(gdoc, close_doc=False)
405            path = os.path.join(rep, name)
406            gdoc.FileName = path
407            save(gdoc)
408            self.load_file(new_doc, doc_file["id"], path, gdoc)
409            self.add_managed_file(new_doc, doc_file, path)
410            self.check_in(gdoc, unlock, False)
411            self.get_data("api/object/%s/unlock/%s/" % (doc["id"], doc_file_id))       
412        else:
413            show_error("Can not revise : file not in openPLM", self.window)
414
415    def check_is_locked(self, doc_id, file_id, error_dialog=True):
416        """
417        Return True if file which is is *file_id* is locked.
418
419        If it is unlocked and *error_dialog* is True, an ErrorDialog is
420        displayed
421        """
422        locked = self.get_data("api/object/%s/islocked/%s/" % (doc_id, file_id))["locked"]
423        if not locked and error_dialog:
424            show_error("File is not locked, action not allowed", self.window)
425        return locked
426
427
428PLUGIN = OpenPLMPluginInstance()
429
430def show_error(message, parent):
431    dialog = qt.QMessageBox()
432    dialog.setText(message)
433    dialog.setWindowTitle("Error")
434    dialog.setIcon(qt.QMessageBox.Warning)   
435    dialog.exec_()
436
437class Dialog(qt.QDialog):
438
439    TITLE = "..."
440    ACTION_NAME = "..."
441
442    def __init__(self):
443        qt.QDialog.__init__(self)
444        self.setWindowTitle(self.TITLE)
445        self.vbox = qt.QVBoxLayout()
446        self.setLayout(self.vbox)
447        self.instance = PLUGIN
448        self.update_ui()
449
450    def get_value(self, entry, field=None):
451        value = None
452        if isinstance(entry, qt.QLineEdit):
453            value = unicode(entry.text(), "utf-8")
454        elif isinstance(entry, qt.QComboBox):
455            if not field:
456                value = unicode(entry.currentText(), "utf-8")
457            else:
458                value = field["choices"][entry.currentIndex()][0]
459        elif isinstance(entry, qt.QCheckBox):
460            value = entry.isChecked()
461        return value
462
463    def set_value(self, entry, value, field=None):
464        if isinstance(entry, qt.QLineEdit):
465            entry.setText(value or '')
466        elif isinstance(entry, qt.QComboBox):
467            choices = [c[0] for c in field["choices"]]
468            entry.setCurrentIndex(choices.index(value or ''))
469        elif isinstance(entry, qt.QCheckBox):
470            entry.setChecked(value)
471   
472    def field_to_widget(self, field):
473        widget = None
474        attributes = {}
475        if field["type"] in ("text", "int", "decimal", "float"):
476            widget = qt.QLineEdit()
477        elif field["type"] == "boolean":
478            widget = qt.QCheckBox()
479        elif field["type"] == "choice":
480            widget = qt.QComboBox()
481            choices = field["choices"]
482            if [u'', u'---------'] not in choices:
483                choices = ([u'', u'---------'],) + tuple(choices)
484            field["choices"] = choices
485            values = []
486            for _, c in choices:
487                values.append(c)
488            widget.addItems(values)
489        if type == "":
490            raise ValueError()
491        self.set_value(widget, field["initial"], field)
492        return widget
493
494    def update_ui(self):
495        pass
496
497class LoginDialog(Dialog):
498    TITLE = 'Login'
499     
500    def update_ui(self):
501        table = qt.QGridLayout()
502        label = qt.QLabel(self)
503        label.setText('Username:')
504        table.addWidget(label, 0, 0)
505        label = qt.QLabel(self)
506        label.setText('Password:')
507        table.addWidget(label, 1, 0)
508        self.user_entry = qt.QLineEdit(self)
509        table.addWidget(self.user_entry, 0, 1)
510        self.pw_entry = qt.QLineEdit()
511        self.pw_entry.setEchoMode(qt.QLineEdit.Password)
512        table.addWidget(self.pw_entry, 1, 1)
513
514        self.vbox.addLayout(table)
515        buttons = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
516        buttons_box = qt.QDialogButtonBox(buttons, parent=self)
517        connect(buttons_box, QtCore.SIGNAL("accepted()"), self.login)
518        connect(buttons_box, QtCore.SIGNAL("rejected()"), self.reject)
519       
520        self.vbox.addWidget(buttons_box)
521
522    def login(self):
523        username = self.user_entry.text()
524        password = self.pw_entry.text()
525        try:
526            PLUGIN.login(username, password)
527            self.accept()
528        except ValueError, e:
529            self.user_entry.setFocus()
530            show_error("Can not login: %s" % str(e), self)
531
532class ConfigureDialog(Dialog):
533
534    TITLE = "Configure"
535
536    def update_ui(self):
537
538        table = qt.QGridLayout()
539        label = qt.QLabel()
540        label.setText("OpenPLM server's location:")
541        self.url_entry = qt.QLineEdit()
542        self.url_entry.setText(PLUGIN.SERVER)
543        table.addWidget(label, 0, 0)
544        table.addWidget(self.url_entry, 0, 1)
545       
546        self.vbox.addLayout(table)
547        buttons = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
548        buttons_box = qt.QDialogButtonBox(buttons, parent=self)
549        connect(buttons_box, QtCore.SIGNAL("accepted()"), self.action_cb)
550        connect(buttons_box, QtCore.SIGNAL("rejected()"), self.reject)
551        self.vbox.addWidget(buttons_box)
552
553    def action_cb(self):
554        self.accept()
555        url = self.get_value(self.url_entry, None)
556        PLUGIN.set_server(url)
557
558class SearchDialog(Dialog):
559
560    TITLE = "Search"
561    ACTION_NAME = "..."
562    TYPE = "Document"
563    SEARCH_SUFFIX = ""
564    TYPES_URL = "api/docs/"
565    ALL_FILES = False
566    EXPAND_FILES = True
567
568    def update_ui(self):
569       
570        docs = PLUGIN.get_data(self.TYPES_URL)
571        self.types = docs["types"]
572
573        table = qt.QGridLayout()
574        self.vbox.addLayout(table)
575        self.type_entry = qt.QComboBox()
576        self.type_entry.addItems(self.types)
577        self.type_entry.setCurrentIndex(self.types.index(self.TYPE))
578        connect(self.type_entry, QtCore.SIGNAL("activated(const QString&)"),
579                self.type_entry_activate_cb)
580        self.name_entry = qt.QLineEdit()
581        self.rev_entry = qt.QLineEdit()
582        self.fields = [("type", self.type_entry),
583                      ]
584        for i, (text, entry) in enumerate(self.fields):
585            label = qt.QLabel()
586            label.setText(text.capitalize()+":")
587            table.addWidget(label, i, 0)
588            table.addWidget(entry, i, 1)
589       
590        self.advanced_table = qt.QGridLayout()
591        self.advanced_fields = []
592        self.vbox.addLayout(self.advanced_table)
593        self.display_fields(self.TYPE)
594       
595        search_button = qt.QPushButton("Search")
596        connect(search_button, QtCore.SIGNAL("clicked()"), self.search)
597        self.vbox.addWidget(search_button)
598
599        self.results_box = qt.QVBoxLayout()
600        self.vbox.addLayout(self.results_box)
601       
602        self.tree = qt.QTreeWidget()
603        self.tree.setColumnCount(1)
604        self.tree.setHeaderLabel("Results")
605        self.results_box.addWidget(self.tree)
606        connect(self.tree, QtCore.SIGNAL("itemExpanded(QTreeWidgetItem *)"),
607                                         self.expand)
608
609        self.action_button = qt.QPushButton(self.ACTION_NAME)
610        connect(self.action_button, QtCore.SIGNAL("clicked()"), self.action_cb)
611        self.results_box.addWidget(self.action_button)
612
613    def type_entry_activate_cb(self, typename):
614        self.display_fields(typename)
615   
616    def display_fields(self, typename):
617        fields = self.instance.get_data("api/search_fields/%s/" % typename)["fields"]
618        temp = {}
619        for field, entry in self.advanced_fields:
620            temp[field["name"]] = self.get_value(entry, field)
621        while self.advanced_table.count():
622            child = self.advanced_table.itemAt(0).widget()
623            self.advanced_table.removeWidget(child)
624            child.hide()
625            child.destroy()
626        self.advanced_table.invalidate()
627        self.advanced_fields = []
628        for i, field in enumerate(fields):
629            text = field["label"]
630            label = qt.QLabel()
631            label.setText(text.capitalize()+":")
632            self.advanced_table.addWidget(label, i, 0)
633            widget = self.field_to_widget(field)
634            if field["name"] in temp:
635                self.set_value(widget, temp[field["name"]], field)
636            self.advanced_table.addWidget(widget, i, 1)
637            self.advanced_fields.append((field, widget))
638
639    def action_cb(self):
640        node = self.tree.currentItem()
641        if not node:
642            return
643        doc = self.nodes[node.parent()]
644        doc_file = doc["files"][node.parent().indexOfChild(node)]
645        del doc["files"]
646        self.do_action(doc, doc_file)
647
648    def expand(self, item):
649        res = self.nodes[item]
650        suffix = "all/" if self.ALL_FILES else ""
651        url = "api/object/%s/files/%s" % (res["id"], suffix)
652        files = PLUGIN.get_data(url)["files"]
653        if "files" in res:
654            return
655        res["files"] = files
656        item.removeChild(item.child(0))
657        for f in files:
658            node = qt.QTreeWidgetItem([f["filename"]])
659            item.addChild(node)
660        if not files:
661            node = qt.QTreeWidgetItem(["No file attached to document"])
662            item.addChild(node)
663
664    def display_results(self, results):
665        self.nodes = {}
666        self.tree.clear()
667        self.tree.reset()
668        for i, res in enumerate(results):
669            text = "%(reference)s|%(type)s|%(revision)s : %(name)s" % res
670            node = qt.QTreeWidgetItem([text])
671            if self.EXPAND_FILES:
672                child = qt.QTreeWidgetItem(["..."])
673                node.addChild(child)
674            node.setExpanded(False)
675            self.tree.insertTopLevelItem(i, node)
676            self.nodes[node] = res
677
678    def search(self, *args):
679        data = {}
680        for text, entry in self.fields:
681            value = self.get_value(entry, None)
682            if value:
683                data[text] = value
684        for field, entry in self.advanced_fields:
685            value = self.get_value(entry, field)
686            if value:
687                data[field["name"]] = value
688        get = urllib.urlencode(data)
689        self.display_results(PLUGIN.get_data("api/search/%s?%s" % (self.SEARCH_SUFFIX, get))["objects"])
690
691    def do_action(self, doc, doc_file):
692        print doc, doc_file
693
694class CheckOutDialog(SearchDialog):
695    TITLE = "Check-out..."
696    ACTION_NAME = "Check-out"
697
698    def do_action(self, doc, doc_file):
699        PLUGIN.check_out(doc, doc_file)
700        self.accept()
701
702class DownloadDialog(SearchDialog):
703    TITLE = "Download..."
704    ACTION_NAME = "Download"
705    SEARCH_SUFFIX = "false/true/"
706    ALL_FILES = True
707
708    def do_action(self, doc, doc_file):
709        PLUGIN.download(doc, doc_file)
710        self.accept()
711
712class CheckInDialog(Dialog):
713
714    TITLE = "Check-in..."
715    ACTION_NAME = "Check-in"
716
717    def __init__(self, doc, name):
718        self.doc = doc
719        self.name = name
720        Dialog.__init__(self)
721
722    def update_ui(self):
723        text = "%s|%s|%s" % (self.doc["reference"], self.doc["revision"],
724                                       self.doc["type"])
725
726        label = qt.QLabel(text)
727        self.vbox.addWidget(label)
728        self.unlock_button = qt.QCheckBox('Unlock ?')
729        self.vbox.addWidget(self.unlock_button)
730
731        button = qt.QPushButton(self.ACTION_NAME)
732        connect(button, QtCore.SIGNAL("clicked()"), self.action_cb)
733        self.vbox.addWidget(button)
734       
735    def action_cb(self):
736        doc = FreeCAD.ActiveDocument
737        unlock = self.get_value(self.unlock_button, None)
738        PLUGIN.check_in(doc, unlock)
739        self.accept()
740
741class ReviseDialog(Dialog):
742
743    TITLE = "Revise..."
744    ACTION_NAME = "Revise"
745
746    def __init__(self, doc, name, revision):
747        self.doc = doc
748        self.name = name
749        self.revision = revision
750        self.gdoc = None
751        Dialog.__init__(self)
752
753    def update_ui(self):
754        hbox = qt.QHBoxLayout()
755        text = "%s|" % self.doc["reference"]
756        label = qt.QLabel(text)
757        hbox.addWidget(label)
758        self.revision_entry = qt.QLineEdit()
759        self.revision_entry.setText(self.revision)
760        hbox.addWidget(self.revision_entry)
761        text = "|%s" % self.doc["type"]
762        label = qt.QLabel(text)
763        hbox.addWidget(label)
764        self.vbox.addLayout(hbox)
765        self.unlock_button = qt.QCheckBox('Unlock ?')
766        self.vbox.addWidget(self.unlock_button)
767        text = "Warning: old revision file will be automatically unlocked!"
768        label = qt.QLabel(text)
769        self.vbox.addWidget(label)
770
771        button = qt.QPushButton(self.ACTION_NAME)
772        connect(button, QtCore.SIGNAL("clicked()"), self.action_cb)
773        self.vbox.addWidget(button)
774       
775    def action_cb(self):
776        doc = FreeCAD.ActiveDocument
777        self.gdoc = PLUGIN.revise(doc,
778                          self.get_value(self.revision_entry, None),
779                          self.get_value(self.unlock_button, None))
780        self.accept()
781   
782class AttachToPartDialog(SearchDialog):
783    TITLE = "Attach to part"
784    ACTION_NAME = "Attach"
785    SEARCH_SUFFIX = "false/"
786    TYPE = "Part"
787    TYPES_URL = "api/parts/"
788    EXPAND_FILES = False
789
790    def __init__(self, doc):
791        SearchDialog.__init__(self)
792        self.doc = doc
793   
794    def do_action(self, part):
795        PLUGIN.attach_to_part(self.doc, part["id"])
796        self.accept()
797   
798    def action_cb(self):
799        node =  self.tree.currentItem()
800        if not node:
801            return
802        doc = self.nodes[node]
803        self.do_action(doc)
804
805class CreateDialog(SearchDialog):
806
807    TITLE = "Create a document..."
808    ACTION_NAME = "Create"
809    TYPE = "Document"
810    TYPES_URL = "api/docs/"
811
812    def update_ui(self):
813        self.doc_created = None       
814        docs = PLUGIN.get_data(self.TYPES_URL)
815        self.types = docs["types"]
816
817        table = qt.QGridLayout()
818        self.vbox.addLayout(table)
819        self.type_entry = qt.QComboBox()
820        self.type_entry.addItems(self.types)
821        self.type_entry.setCurrentIndex(self.types.index(self.TYPE))
822        connect(self.type_entry, QtCore.SIGNAL("activated(const QString&)"),
823                self.type_entry_activate_cb)
824        self.fields = [("type", self.type_entry),
825                      ]
826        for i, (text, entry) in enumerate(self.fields):
827            label = qt.QLabel()
828            label.setText(text.capitalize()+":")
829            table.addWidget(label, i, 0)
830            table.addWidget(entry, i, 1)
831       
832        self.advanced_table = qt.QGridLayout()
833        self.advanced_fields = []
834        self.vbox.addLayout(self.advanced_table)
835        self.display_fields(self.TYPE)
836       
837        hbox = qt.QHBoxLayout()
838        label = qt.QLabel()
839        label.setText("Filename:")
840        hbox.addWidget(label)
841        self.filename_entry = qt.QLineEdit()
842        hbox.addWidget(self.filename_entry)
843        doc = FreeCAD.ActiveDocument
844        self.filename_entry.setText(os.path.basename(doc.FileName) or "x.fcstd")
845        self.vbox.addLayout(hbox)
846        self.unlock_button = qt.QCheckBox('Unlock ?')
847        self.vbox.addWidget(self.unlock_button)
848               
849        self.action_button = qt.QPushButton(self.ACTION_NAME)
850        connect(self.action_button, QtCore.SIGNAL("clicked()"), self.action_cb)
851        self.vbox.addWidget(self.action_button)
852
853    def type_entry_activate_cb(self, typename):
854        self.display_fields(typename)
855   
856    def display_fields(self, typename):
857        fields = self.instance.get_data("api/creation_fields/%s/" % typename)["fields"]
858        temp = {}
859        for field, entry in self.advanced_fields:
860            temp[field["name"]] = self.get_value(entry, field)
861        while self.advanced_table.count():
862            child = self.advanced_table.itemAt(0).widget()
863            self.advanced_table.removeWidget(child)
864            child.hide()
865            child.destroy()
866        self.advanced_table.invalidate()
867        self.advanced_fields = []
868        for i, field in enumerate(fields):
869            text = field["label"]
870            label = qt.QLabel()
871            label.setText(text.capitalize()+":")
872            self.advanced_table.addWidget(label, i, 0)
873            widget = self.field_to_widget(field)
874            if field["name"] in temp:
875                self.set_value(widget, temp[field["name"]], field)
876            self.advanced_table.addWidget(widget, i, 1)
877            self.advanced_fields.append((field, widget))
878
879    def action_cb(self):
880        b, error = PLUGIN.create(self.get_creation_data(),
881                                 self.get_value(self.filename_entry, None),
882                                 self.get_value(self.unlock_button, None))
883        if b:
884            self.accept()
885        else:
886            show_error(error, self)
887        self.doc_created = b
888
889    def get_creation_data(self):
890        data = {}
891        for field_name, widget in self.fields:
892            data[field_name] = self.get_value(widget, None)
893        for field, widget in self.advanced_fields:
894            data[field["name"]] = self.get_value(widget, field)
895        return data
896
897
898class OpenPLMLogin:
899
900    def Activated(self):
901        dialog = LoginDialog()
902        dialog.exec_()
903
904    def GetResources(self):
905        return {'Pixmap' : 'plop.png', 'MenuText': 'Login', 'ToolTip': 'Login'}
906
907FreeCADGui.addCommand('OpenPLM_Login', OpenPLMLogin())
908
909class OpenPLMConfigure:
910
911    def Activated(self):
912        dialog = ConfigureDialog()
913        dialog.exec_()
914
915    def GetResources(self):
916        return {'Pixmap' : 'plop.png', 'MenuText': 'Configure', 'ToolTip': 'Configure'}
917
918FreeCADGui.addCommand('OpenPLM_Configure', OpenPLMConfigure())
919
920class OpenPLMCheckOut:
921
922    def Activated(self):
923        dialog = CheckOutDialog()
924        dialog.exec_()
925   
926    def GetResources(self):
927        return {'Pixmap' : 'plop.png', 'MenuText': 'Check-out', 'ToolTip': 'Check-out'}
928   
929    def IsActive(self):
930        return PLUGIN.connected
931
932FreeCADGui.addCommand('OpenPLM_CheckOut', OpenPLMCheckOut())
933
934
935class OpenPLMDownload:
936   
937    def Activated(self):
938        dialog = DownloadDialog()
939        dialog.exec_()
940
941    def GetResources(self):
942        return {'Pixmap' : 'plop.png', 'MenuText': 'Download', 'ToolTip': 'Download'}
943   
944    def IsActive(self):
945        return PLUGIN.connected
946
947FreeCADGui.addCommand('OpenPLM_Download', OpenPLMDownload())
948
949class OpenPLMForget:
950
951    def Activated(self):
952        PLUGIN.forget(close_doc=True)
953
954    def GetResources(self):
955        return {'Pixmap' : 'plop.png', 'MenuText': 'Forget current file',
956                'ToolTip': 'Forget and delete current file'}
957
958    def IsActive(self):
959        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
960
961FreeCADGui.addCommand('OpenPLM_Forget', OpenPLMForget())
962
963class OpenPLMCheckIn:
964   
965    def Activated(self):
966        gdoc = FreeCAD.ActiveDocument
967        if gdoc and gdoc in PLUGIN.documents:
968            doc = PLUGIN.documents[gdoc]["openplm_doc"]
969            doc_file_id = PLUGIN.documents[gdoc]["openplm_file_id"]
970            path = PLUGIN.documents[gdoc]["openplm_path"]
971            if not doc or not PLUGIN.check_is_locked(doc["id"], doc_file_id):
972                return
973            name = os.path.basename(path)
974            dialog = CheckInDialog(doc, name)
975            dialog.exec_()
976            if gdoc not in PLUGIN.documents:
977                close(gdoc)
978        else:
979            win = main_window()
980            show_error("Document not stored in OpenPLM", win)
981
982    def GetResources(self):
983        return {'Pixmap' : 'plop.png', 'MenuText': 'Check-in', 'ToolTip': 'Check-in'}
984
985    def IsActive(self):
986        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
987
988FreeCADGui.addCommand('OpenPLM_CheckIn', OpenPLMCheckIn())
989
990class OpenPLMRevise:
991
992    def Activated(self):
993        gdoc = FreeCAD.ActiveDocument
994        if gdoc and gdoc in PLUGIN.documents:
995            doc = PLUGIN.documents[gdoc]["openplm_doc"]
996            doc_file_id = PLUGIN.documents[gdoc]["openplm_file_id"]
997            path = PLUGIN.documents[gdoc]["openplm_path"]
998            if not doc or not PLUGIN.check_is_locked(doc["id"], doc_file_id):
999                return
1000            revisable = PLUGIN.get_data("api/object/%s/isrevisable/" % doc["id"])["revisable"]
1001            if not revisable:
1002                win = main_window()
1003                show_error("Document can not be revised", win)
1004                return
1005            res = PLUGIN.get_data("api/object/%s/nextrevision/" % doc["id"])
1006            revision = res["revision"]
1007            name = os.path.basename(path)
1008            dialog = ReviseDialog(doc, name, revision)
1009            dialog.exec_()
1010            if gdoc not in PLUGIN.documents:
1011                close(gdoc)
1012        else:
1013            win = main_window()
1014            show_error("Document not stored in OpenPLM", win)
1015
1016    def GetResources(self):
1017        return {'Pixmap' : 'plop.png', 'MenuText': 'Revise', 'ToolTip': 'Revise'}
1018   
1019    def IsActive(self):
1020        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
1021
1022FreeCADGui.addCommand('OpenPLM_Revise', OpenPLMRevise())
1023
1024class OpenPLMAttach:
1025
1026    def Activated(self):
1027        gdoc = FreeCAD.ActiveDocument
1028        if gdoc and gdoc in PLUGIN.documents:
1029            doc = PLUGIN.documents[gdoc]["openplm_doc"]
1030            dialog = AttachToPartDialog(doc)
1031            dialog.exec_()
1032        else:
1033            win = main_window()
1034            show_error("Document not stored in OpenPLM", win)
1035
1036    def GetResources(self):
1037        return {'Pixmap' : 'plop.png', 'MenuText': 'Attach to a part',
1038                'ToolTip': 'Attach to a part'}
1039   
1040    def IsActive(self):
1041        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
1042
1043FreeCADGui.addCommand('OpenPLM_AttachToPart', OpenPLMAttach())
1044
1045class OpenPLMCreate:
1046   
1047    def Activated(self):
1048        gdoc = FreeCAD.ActiveDocument
1049        win = main_window()
1050        if not gdoc:
1051            show_error("Need an opened file to create a document", win)
1052            return
1053        if gdoc in PLUGIN.documents:
1054            show_error("Current file already attached to a document", win)
1055            return
1056        dialog = CreateDialog()
1057        resp = dialog.exec_()
1058        if dialog.doc_created and gdoc not in PLUGIN.documents:
1059            close(gdoc)
1060
1061    def GetResources(self):
1062        return {'Pixmap' : 'plop.png', 'MenuText': 'Create a Document',
1063                'ToolTip': 'Create a document'}
1064   
1065    def IsActive(self):
1066        doc = FreeCAD.ActiveDocument
1067        return PLUGIN.connected and doc and doc not in PLUGIN.documents
1068
1069FreeCADGui.addCommand('OpenPLM_Create', OpenPLMCreate())
1070
1071def build_menu():
1072    win = main_window()
1073    mb =  win.menuBar()
1074    menu = mb.addMenu("OpenPLM")
1075    for cls in (OpenPLMLogin, OpenPLMCheckOut):
1076        cmd = cls()
1077        action = qt.QAction(cmd.GetResources()["MenuText"], None)
1078        QtCore.QObject.connect(action, QtCore.SIGNAL("triggered"), cmd.Activated)
1079        menu.addAction(action)
1080    menu.show()
1081
Note: See TracBrowser for help on using the repository browser.