Exemple #1
0
 def _(self, params):
     old_project = self.project
     project_name = params.get("project")
     cwd = params.get("cwd")
     cwf = params.get("cwf")
     project = None
     if project_name:
         project = Project.get(project_name)
     if not project and cwd:
         project = Project.find(cwd)
     if not project and cwf:
         project = Project.find(cwf)
     if not project and 0:
         if os.path.isfile(cwf):
             cwd = os.path.dirname(cwf)
             while True:
                 p = os.path.join(cwd, ".git")
                 print(1, cwd, p)
                 if os.path.isdir(p):
                     name = os.path.basename(cwd)
                     log.info("new proejct %s", name)
                     project = Project.new(name, cwd)
                     break
                 new = os.path.dirname(cwd)
                 if new == cwd:
                     break
                 cwd = new
     if project is old_project:
         return
     self.project = project
     project.init_or_load()
     log.info("%s set project old %s new %s", self, old_project, project)
Exemple #2
0
class WallGcodeGeneratorToolUsageTest(GcodeParserBaseTests):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    self.project.set_tool(Tool(cutter_diameter = 4))
    
  def test_is_output_correct(self):
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(10, 10) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)
    code = parser.next_code()

    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = -2), code)
    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = 12), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 12), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = -2), parser.next_code())
Exemple #3
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.undoStack = QUndoStack(self)

        self.project = Project()

        self._initUI()
        self._initMenuBar()

        self.show()
Exemple #4
0
class BoxWithWoodenJointsTest(unittest.TestCase):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    box = BoxBuilder \
            .new_box(self.project) \
            .start_at(0, 0) \
            .with_size(40, 60, 80) \
            .with_wooden_joints() \
            .build()
    
  def test_has_this_box_4_walls(self):
    pass
Exemple #5
0
class BoxTest(unittest.TestCase):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    
  def test_has_this_box_4_walls(self):
    box = BoxBuilder \
            .new_box(self.project) \
            .start_at(0, 0) \
            .with_size(40, 60, 80) \
            .build()

    self.assertEquals(4, len(box.walls))
Exemple #6
0
  def test_is_start_eq_end_simple_wall(self):
    project = Project()
    project.set_configuration(Configuration())

    wall = WallBuilder \
            .new_wall(project) \
            .start_at(0, 0) \
            .with_size(10, 10) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)
    first_code = parser.get_first_code()
    last_code = parser.get_last_code()

    self.assertAreEqualGcodes(first_code, last_code)
Exemple #7
0
 def proj_save(self):
     self.name = self.lineEdit.text()
     self.manager = self.lineEdit_2.text()
     self.credit_amount = int(self.lineEdit_3.text())
     proj = Project(name=self.name,
                    credit_amount=self.credit_amount,
                    manager=self.manager)
     win.ProjPool_obj.add_project(proj)
     win.proj_show_in_table()
Exemple #8
0
 def setUp(self):
   self.project = Project()
   self.project.set_configuration(Configuration())
   box = BoxBuilder \
           .new_box(self.project) \
           .start_at(0, 0) \
           .with_size(40, 60, 80) \
           .with_wooden_joints() \
           .build()
Exemple #9
0
class BoxGcodeGeneratorSimpleTest(unittest.TestCase):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    
  def test_should_return_non_empty_string(self):
    box = BoxBuilder \
            .new_box(self.project) \
            .start_at(0, 0) \
            .with_size(40, 60, 80) \
            .build()

    self.project.add_element(box);
    gcode_generator = box.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    self.assertTrue(output != None and len(output) > 0)
Exemple #10
0
    def open(self):
        filename = QFileDialog.getOpenFileName(self, "Open Project",
                                               "/Users/espennordahl/Desktop",
                                               "Projects (*.sg)")[0]
        with open(filename) as infile:
            data = json.load(infile)

        self.clearProject()
        self.project = Project.deserialize(data)
        self.shotBrowser.setProject(self.project)
        self.updateWindowTitle()
Exemple #11
0
def main():
  project = Project()
  project.set_configuration(Configuration())
#  project.set_tool(Tool(cutter_diameter = 4))

  box = BoxBuilder \
          .new_box(project) \
          .start_at(0, 0) \
          .with_size(50, 60, 80) \
          .with_wooden_joints() \
          .build()

  project.add_element(box);

  print project.generate_gcode()
Exemple #12
0
class ProjectGcodeGeneratorsOutputTest(unittest.TestCase):
  project = None
  output = None

  def setUp(self):
    self.project = Project()
    self.output = self.project.generate_gcode()

  def test_has_on_first_line_percentage_sign(self):
    self.assertEquals('%', self.output[0].strip())

  def test_has_on_last_line_percentage_sign(self):
    self.assertEquals('%', self.output[-1].strip())

  def test_should_have_defined_speed(self):
    self.assertTrue('G1 F' in self.output)

  def test_should_have_defined_speed(self):
    self.assertTrue('G21 ' in self.output)

  def test_should_go_back_to_home_in_last_steps(self):
    self.assertTrue('G0 Z3.000000' in self.output[:-5])
    self.assertTrue('G0 X0.000000 Y0.000' in self.output[:-5])
Exemple #13
0
    def _(self, params):
        old_project = self.project
        project = None

        root_path = params.get("rootPath")
        if root_path:
            pass
        root_uri = params.get("rootUri")
        if root_uri:
            pass
        folders = params.get("wordspaceFolders")
        if folders:
            pass

        uri = params.get("textDocument", {}).get("uri")
        if uri:
            path = uri_to_path(uri)
            project = Project.find(path)

        if project is old_project:
            return
        self.project = project
        project.init_or_load()
        log.info("%s set project old %s new %s", self, old_project, project)
Exemple #14
0
 def setUp(self):
   self.project = Project()
   self.output = self.project.generate_gcode()
Exemple #15
0
 def setUp(self):
   self.project = Project()
   self.project.set_configuration(Configuration())
   self.project.set_tool(Tool(cutter_diameter = 4))
Exemple #16
0
class WallGcodeGeneratorWithWoodenJointsTogetherTest(GcodeParserBaseTests):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    self.project.set_tool(Tool(cutter_diameter = 0))
    
  def test_is_output_correct(self):
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(15, 20) \
            .with_wooden_joints(on_left = True, on_bottom = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 5), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 20), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 20), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 25), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 25), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 5), parser.next_code())

  def test_is_output_correct_for_3_sides_with_tits(self):
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(15, 15) \
            .with_wooden_joints(on_left = True, on_bottom = True, on_right = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 5), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 20), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 20), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 20), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 0), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 5), parser.next_code())
Exemple #17
0
sourceTutorial = '/home/rocketman/OFProject/cavity/'
blockMeshTemplatePath = '/home/rocketman/OFProject/cavity/system/blockMeshDict.template'
UtemplatePath = '/home/rocketman/OFProject/cavity/0/U.template'

Uvars = ['Ux', 'Uy', 'Uz']
Uvals = [[1, 0 , 0], [5, 0, 0], [10, 0, 0]]
varList = ['xElem', 'yElem']
valList = [[20, 50], [20, 50]]

controlDictPath = '/home/rocketman/OFProject/cavity/system/controlDict'

paramStudy = ParameterVariation(templateFile=UtemplatePath, templateCase=sourceTutorial, variables=Uvars, values=Uvals) 
blockMesh = BlockMesh( blockMeshFilePath)
probe = Probe( controlDictPath, 10, 'p', [0.025, 0.025, 0.005] )
solver = Solver( sourceTutorial )
paraFoam = ParaFoam()

graph.add_vertex(blockMesh)
graph.add_vertex(probe)
graph.add_vertex(solver)
#graph.add_vertex(paraFoam)
graph.add_vertex(paramStudy)

graph.connect(blockMesh, probe, w1)
graph.connect(probe, solver, w1)
graph.connect(solver, paramStudy, w2)
graph.connect(paramStudy, blockMesh, w3)

project = Project(name='cavityProj', graph=graph, clearPath=True)
project.run(3)
Exemple #18
0
class WallGcodeGeneratorWithWoodenJointsRightSideTest(GcodeParserBaseTests):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    self.project.set_tool(Tool(cutter_diameter = 0))
    
  def test_is_output_correct(self):
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(10, 20) \
            .with_wooden_joints(on_right = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 0), parser.next_code())

    #tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 20), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 20), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 15), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 10), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 5), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 0), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 0), parser.next_code())

  def test_is_output_for_right_side_with_non_zero_tool_length_correct(self):
    self.project.set_tool(Tool(cutter_diameter = 4))
    
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(10, 20) \
            .with_wooden_joints(on_right = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = -2), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = 22), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 17, y = 22), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 17, y = 13), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 13), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 12), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 17, y = 12), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 17, y = 3), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 3), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = -2), parser.next_code())

    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = -2), parser.next_code())
Exemple #19
0
class WallGcodeGeneratorWithWoodenJointsBottomSideTest(GcodeParserBaseTests):
  project = None

  def setUp(self):
    self.project = Project()
    self.project.set_configuration(Configuration())
    self.project.set_tool(Tool(cutter_diameter = 0))
    
  def test_is_output_for_bottom_side_correct(self):
    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(30, 10) \
            .with_wooden_joints(on_bottom = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 30, y = 15), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 30, y = 5), parser.next_code())

    # now bottom side
    #first tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 30, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 25, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 5), parser.next_code())

    #second tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 20, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 15, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 5), parser.next_code())

    #third tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 10, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 0), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 5, y = 5), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 0, y = 5), parser.next_code())

  def test_is_output_for_bottom_side_with_non_zero_tool_length_correct(self):
    self.project.set_tool(Tool(cutter_diameter = 4))

    wall = WallBuilder \
            .new_wall(self.project) \
            .start_at(0, 0) \
            .with_size(30, 10) \
            .with_wooden_joints(on_bottom = True) \
            .build()

    gcode_generator = wall.get_gcode_generator()
    output = gcode_generator.generate_gcode()

    parser = GcodeParser(output)

    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = 3), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = -2, y = 17), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 32, y = 17), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 32, y = 3), parser.next_code())

    # now bottom side
    #first tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 32, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 23, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 23, y = 3), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 22, y = 3), parser.next_code())

    #second tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 22, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 13, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 13, y = 3), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = 3), parser.next_code())

    #third tit
    self.assertAreEqualGcodes(GcodeG1Command(x = 12, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 3, y = -2), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 3, y = 3), parser.next_code())
    self.assertAreEqualGcodes(GcodeG1Command(x = 2, y = 3), parser.next_code())
Exemple #20
0
 def setUp(self):
   self.project = Project()
   self.project.set_configuration(Configuration())
Exemple #21
0
meshVar = ParameterVariation(templateFile=topoTemplatePath,
                             templateCase=sourceTutorial,
                             parFile=lhsNorms)
#solver = Solver( sourceTutorial )
solver = SolverParallel(2)
paraFoam = ParaFoam()
fResults = ForcesCollector()
faceSelection = TopoSet()
patchCreation = CreatePatch()

graph.add_vertex(solver)
graph.add_vertex(connector)
graph.add_vertex(paramStudy)
graph.add_vertex(meshVar)
graph.add_vertex(fResults)
graph.add_vertex(faceSelection)
graph.add_vertex(patchCreation)

graph.connect(fResults, paramStudy, w2)
graph.connect(paramStudy, connector, w2)
graph.connect(connector, meshVar, w2)
graph.connect(meshVar, faceSelection, w2)
graph.connect(faceSelection, patchCreation, w2)
graph.connect(patchCreation, solver, w2)
graph.connect(solver, fResults, w3)

print "launch project"

project = Project(name='canopyProj', graph=graph, clearPath=True)
project.run(2)
Exemple #22
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.undoStack = QUndoStack(self)

        self.project = Project()

        self._initUI()
        self._initMenuBar()

        self.show()

    def _initUI(self):
        self.updateWindowTitle()

        # Status Bar
        self.statusBar().showMessage("Ready")

        # Window Size
        self.setMinimumSize(400, 400)
        self.setGeometry(0, 0, 1920, 1080)

        # NodeGraph
        self.centralWidget = QFrame()
        self.setCentralWidget(self.centralWidget)

        self.centralLayout = QVBoxLayout(self.centralWidget)

        self.graphLabel = QLabel("No shot selected")
        self.centralLayout.addWidget(self.graphLabel, alignment=Qt.AlignCenter)

        self.nodeGraph = NodeGraphView(self)
        self.nodeGraph.mainWindow = self
        self.centralLayout.addWidget(self.nodeGraph)

        # Attribute Editor
        self.attributeEditor = AttributeEditor(self)
        self.addDockWidget(Qt.RightDockWidgetArea, self.attributeEditor)
        self.nodeGraph.selectionChanged.connect(self.attributeEditor.setNodes)
        self.attributeEditor.attributeChanged.connect(
            self.nodeGraph.scene().update)

        # Shot Browser
        self.shotBrowser = ShotBrowser(self.project, self)
        self.shotBrowser.shotChanged.connect(self.shotSelectionChanged)

        self.addDockWidget(Qt.LeftDockWidgetArea, self.shotBrowser)

    def _initMenuBar(self):
        menuBar = self.menuBar()

        ## File menu
        self.fileMenu = menuBar.addMenu("File")

        ## Open
        openAction = QAction("Open", self)
        openAction.setShortcut("Ctrl+O")
        openAction.setStatusTip("Open File")
        openAction.triggered.connect(self.open)
        self.fileMenu.addAction(openAction)

        ## Save
        saveAction = QAction("Save", self)
        saveAction.setShortcut("Ctrl+s")
        saveAction.setStatusTip("Save File")
        saveAction.triggered.connect(self.save)
        self.fileMenu.addAction(saveAction)

        ## Save As
        saveAsAction = QAction("Save As", self)
        saveAsAction.setShortcut("Ctrl+S")
        saveAsAction.setStatusTip("Save File As")
        saveAsAction.triggered.connect(self.saveas)
        self.fileMenu.addAction(saveAsAction)

        ## Exit
        exitAction = QAction(" Exit", self)
        exitAction.setShortcut("Ctrl+Q")
        exitAction.setStatusTip("Exit application")
        exitAction.triggered.connect(self.close)
        self.fileMenu.addAction(exitAction)

        ## Edit Menu
        self.editMenu = menuBar.addMenu("Edit")

        ## Undo
        undoAction = self.undoStack.createUndoAction(self, "Undo")
        undoAction.setShortcut("Ctrl+z")
        undoAction.setStatusTip("Undo")
        self.editMenu.addAction(undoAction)

        ## Redo
        redoAction = self.undoStack.createRedoAction(self, "Redo")
        redoAction.setShortcuts(["Ctrl+y", "Ctrl+Z"])
        redoAction.setStatusTip("Redo")
        self.editMenu.addAction(redoAction)

        ## Copy
        copyAction = QAction("Copy", self)
        copyAction.setShortcut("Ctrl+c")
        copyAction.setStatusTip("Copy selection")
        self.editMenu.addAction(copyAction)

        ## Cut
        cutAction = QAction("Cut", self)
        cutAction.setShortcut("Ctrl+x")
        cutAction.setStatusTip("Cut selection")
        self.editMenu.addAction(cutAction)

        ## Paste
        pasteAction = QAction("Paste", self)
        pasteAction.setShortcut("Ctrl+v")
        pasteAction.setStatusTip("Paste")
        self.editMenu.addAction(pasteAction)

        ## Shots menu
        self.shotMenu = menuBar.addMenu("Shots")

        ## Import shots
        importSGAction = QAction("Import shots from Shotgun", self)
        importSGAction.setStatusTip("Import shots from Shotgun")
        importSGAction.triggered.connect(self.importShots)
        self.shotMenu.addAction(importSGAction)

        ## Node Menu
        self.nodeMenu = menuBar.addMenu("Nodes")
        self.buildCreateNodeMenu(self.nodeMenu)

        # Tractor Menu
        self.tractorMenu = menuBar.addMenu("Tractor")

        tractorScenefilesAction = QAction("Create Scenefile Templates", self)
        self.tractorMenu.addAction(tractorScenefilesAction)

        tractorActionsAction = QAction("Run Actions", self)
        self.tractorMenu.addAction(tractorActionsAction)

        tractorDataAction = QAction("Generate Data", self)
        self.tractorMenu.addAction(tractorDataAction)

    def buildCreateNodeMenu(self, menu):

        ## Actions
        actionsMenu = menu.addMenu("Actions")
        actions = [
            "Render", "Comprender", "PublishGeo", "PublishLookdev",
            "PublishGeocache"
        ]
        for actionname in actions:
            action = QAction(actionname, self)
            action.setStatusTip("Create {node}".format(node=actionname))
            action.triggered.connect(lambda checked, name=actionname +
                                     "Action": self.createNode(name))
            actionsMenu.addAction(action)

        sceneMenu = menu.addMenu("Scenes")
        scenes = ["Maya", "Houdini", "Nuke"]
        for scenename in scenes:
            action = QAction(scenename, self)
            action.setStatusTip("Create {node}".format(node=scenename))
            action.triggered.connect(
                lambda checked, name=scenename + "File": self.createNode(name))
            sceneMenu.addAction(action)

        dataMenu = menu.addMenu("Data")
        datatypes = [
            "Geo", "Geocache", "Lookdev", "Plate", "Render", "Comprender"
        ]
        for dataname in datatypes:
            action = QAction(dataname, self)
            action.setStatusTip("Create {node}".format(node=dataname))
            action.triggered.connect(
                lambda checked, name=dataname + "Data": self.createNode(name))
            dataMenu.addAction(action)

    def createNode(self, classname):
        createCommand = CreateNodeCommand(
            classname, self.nodeGraph.shot.graph,
            self.nodeGraph.mapToScene(self.nodeGraph.mousePosLocal))
        self.undoStack.push(createCommand)

    def importShots(self):
        tempshots = [
            "fx010", "fx020", "fx030", "fx040", "fx100", "fx120", "vg040",
            "vg045", "vg080"
        ]
        for shotname in tempshots:
            shot = Shot(shotname)
            shot.graph = Graph()
            self.shotBrowser.addShot(shot)

    def shotSelectionChanged(self, shotname):
        switchCommand = SwitchShotCommand(self.nodeGraph.shot,
                                          self.project.shots[shotname],
                                          self.graphLabel, self.nodeGraph,
                                          self.shotBrowser)
        self.undoStack.push(switchCommand)

    def save(self):
        if not self.project.filename:
            logger.debug("Project doesn't have filename. Asking for one")
            self.saveas()
            return

        logger.debug("Serializing data")
        data = self.project.serialize()
        logger.debug("Serialization successfull")

        ## TODO: File safety checks
        logger.info("Writing File {}".format(self.project.filename))
        with open(self.project.filename, "w") as outfile:
            json.dump(data, outfile, indent=4)
        logger.info("File saved successfully")
        self.undoStack.setClean()
        self.updateWindowTitle()

    def saveas(self):
        filename = QFileDialog.getSaveFileName(self, "Save Project",
                                               "/Users/espennordahl/Desktop",
                                               "Projects (*.sg)")[0]

        self.project.filename = filename
        self.project.name = filename.split("/")[-1]

        self.save()

    def updateWindowTitle(self):
        if self.project.name:
            self.setWindowTitle("StuffGrapher - {}".format(self.project.name))
        else:
            self.setWindowTitle("StuffGrapher - <Untitled Project>")

    def open(self):
        filename = QFileDialog.getOpenFileName(self, "Open Project",
                                               "/Users/espennordahl/Desktop",
                                               "Projects (*.sg)")[0]
        with open(filename) as infile:
            data = json.load(infile)

        self.clearProject()
        self.project = Project.deserialize(data)
        self.shotBrowser.setProject(self.project)
        self.updateWindowTitle()

    def closeEvent(self, event):
        if self.undoStack.isClean():
            event.accept()
        else:
            msgBox = QMessageBox()
            msgBox.setText("The document has been modified.")
            msgBox.setInformativeText("Do you want to save your changes?")
            msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                      | QMessageBox.Cancel)
            msgBox.setDefaultButton(QMessageBox.Save)
            ret = msgBox.exec_()
            if ret == QMessageBox.Save:
                self.save()
                event.accept()
            elif ret == QMessageBox.Discard:
                event.accept()
            else:
                event.ignore()

    def clearProject(self):
        return