Skip to content

Commit 320c5f4

Browse files
authored
Merge pull request #1 from nfa-vfxim/dev
Initial commit
2 parents 37930cd + de61fc3 commit 320c5f4

File tree

11 files changed

+1511
-2
lines changed

11 files changed

+1511
-2
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1-
# nfa-shotgrid-project-creator
2-
Standalone application for creating ShotGrid projects according to our pipeline specifications.
1+
# The NFA ShotGrid Project Creator
2+
This standalone application is a GUI rewrite of our [ShotGrid Project Creation script](https://github.com/nfa-vfxim/nfa-shotgrid-project-creation). It is a completely custom tool for properly creating ShotGrid projects so they are correctly configured for use in our ShotGrid pipeline. It was designed to simplify several settings, like:
3+
- Setting the right [ShotGrid configuration](https://github.com/nfa-vfxim/nfa-shotgun-configuration) branch based on the year a student is in.
4+
- Validating project names and codes so no duplicates are created.
5+
- Specific Netherlands Filmacademy settings, like whether we are dealing with a fiction or a documentary project.
6+
7+
![project_creator](https://github.com/nfa-vfxim/nfa-shotgrid-project-creator/assets/63094424/bd8e9849-1542-4401-8465-2185456f49e4)
8+
9+
## Installation instructions
10+
This is a highly specific tool for use at our school, so you will have to customize it to suit your needs. Nevertheless, you can use it like this:
11+
- Use a recent version of Python 3, making sure it supports the correct packages.
12+
- Install the following packages: `PySide2`, `shotgun_api3`.
13+
- Create an enviroment variable called `SHOTGRID_API_KEY` and set the value to your ShotGrid API key. The script name associated with the key should be `project_creation_V2`.
14+
- Run the program with `python main.py`. We recommend packaging it as an executable for installation on artist computers.

controller.py

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
"""Controller for the NFA ShotGrid Project Creator, written by Mervin van Brakel (2024)"""
2+
3+
from __future__ import annotations
4+
5+
from model import ProjectCreatorModel, ValidationError
6+
from view import ProjectCreatorView
7+
8+
9+
class ProjectCreatorController:
10+
"""Controller for the ShotGrid Project Creator.
11+
12+
This controller handles all interactions between the view and the model.
13+
"""
14+
15+
def __init__(self):
16+
"""Initializes the controller class and creates the view and model."""
17+
self.view = ProjectCreatorView()
18+
self.view.show()
19+
self.view.start_button.clicked.connect(self.connect_to_shotgrid)
20+
21+
self.model = ProjectCreatorModel()
22+
23+
def connect_to_shotgrid(self) -> None:
24+
"""Starts the ShotGrid model connection."""
25+
self.view.start_widget.hide()
26+
self.view.layout.addWidget(self.view.get_loading_widget())
27+
28+
self.model.connect_to_shotgrid(
29+
self.shotgrid_connection_successful,
30+
self.shotgrid_connection_failed,
31+
)
32+
33+
def shotgrid_connection_successful(self) -> None:
34+
"""Runs when model is connected to ShotGrid. Triggers the username search."""
35+
self.find_username()
36+
37+
def shotgrid_connection_failed(self, error: str) -> None:
38+
"""Show an error message when the ShotGrid connection failed.
39+
40+
Args:
41+
error: Python error message in string format.
42+
"""
43+
self.view.loading_widget.hide()
44+
self.view.layout.addWidget(self.view.get_error_widget())
45+
self.view.error_text.setText(
46+
f"ShotGrid connection error: {error}. Please contact a pipeline TD if problem persist."
47+
)
48+
49+
def find_username(self) -> None:
50+
"""Checks the username with the model. If we can't find a ShotGrid username,
51+
we show the username selection screen to the user.
52+
"""
53+
self.view.loading_widget.hide()
54+
55+
shotgrid_user = self.model.get_shotgrid_user_from_computer_username()
56+
57+
if shotgrid_user:
58+
self.model.set_user_information(shotgrid_user)
59+
self.view.layout.addWidget(
60+
self.view.get_main_widget(
61+
shotgrid_user.get("name"), self.model.usernames
62+
)
63+
)
64+
self.connect_view_functions()
65+
66+
else:
67+
self.view.layout.addWidget(
68+
self.view.get_username_widget(self.model.usernames)
69+
)
70+
self.view.continue_button.clicked.connect(self.validate_username)
71+
72+
def validate_username(self) -> None:
73+
"""Checks if user submitted username is in ShotGrid. Moves on to next
74+
step if username exists."""
75+
shotgrid_user = self.model.get_shotgrid_user(
76+
self.view.username_lineedit.text()
77+
)
78+
79+
if not shotgrid_user:
80+
self.view.username_validation_text.setStyleSheet(
81+
"color: '#FF3E3E'; font-size: 12px;"
82+
)
83+
self.view.username_validation_text.setText(
84+
"Could not find user in ShotGrid database."
85+
)
86+
self.view.username_validation_text.show()
87+
88+
else:
89+
self.view.username_widget.hide()
90+
self.view.layout.addWidget(
91+
self.view.get_main_widget(
92+
shotgrid_user.get("name"), self.model.usernames
93+
)
94+
)
95+
self.connect_view_functions()
96+
self.model.set_user_information(shotgrid_user)
97+
98+
def connect_view_functions(self) -> None:
99+
"""Connects all our view buttons and text changes to corresponding
100+
functions in this controller."""
101+
self.view.project_name_lineedit.textChanged.connect(
102+
self.validate_project_name
103+
)
104+
self.view.production_code_yes_button.clicked.connect(
105+
self.set_production_code_yes
106+
)
107+
self.view.production_code_no_button.clicked.connect(
108+
self.set_production_code_no
109+
)
110+
self.view.project_code_lineedit.textChanged.connect(
111+
self.validate_project_code
112+
)
113+
self.view.supervisor_add_button.clicked.connect(self.add_supervisor)
114+
self.view.supervisor_remove_button.clicked.connect(
115+
self.remove_supervisor
116+
)
117+
self.view.render_engine_list.currentTextChanged.connect(
118+
self.set_render_engine
119+
)
120+
self.view.project_type_fiction_button.clicked.connect(
121+
self.set_project_type_fiction
122+
)
123+
self.view.project_type_documentary_button.clicked.connect(
124+
self.set_project_type_documentary
125+
)
126+
self.view.fps_spinbox.valueChanged.connect(self.set_fps)
127+
self.view.create_project_button.clicked.connect(self.create_project)
128+
129+
def validate_project_name(self, project_name: str) -> None:
130+
"""Validates project name and updates view.
131+
132+
Args:
133+
project_name: Name of project
134+
"""
135+
project_name_validation_text = self.view.project_name_validation_text
136+
137+
try:
138+
self.model.validate_project_name(project_name)
139+
project_name_validation_text.setText("Project name available!")
140+
project_name_validation_text.setStyleSheet(
141+
"color: '#8BFF3E'; font-size: 12px;"
142+
)
143+
144+
except ValidationError as validation_message:
145+
project_name_validation_text.setText(str(validation_message))
146+
project_name_validation_text.setStyleSheet(
147+
"color: '#FF3E3E'; font-size: 12px;"
148+
)
149+
150+
project_name_validation_text.show()
151+
152+
def set_production_code_yes(self) -> None:
153+
"""Switches production code to yes and informs the model."""
154+
self.view.production_code_yes_button.setChecked(True)
155+
self.view.production_code_no_button.setChecked(False)
156+
self.model.set_has_production_code(True)
157+
self.view.production_code_enter_text.setText(
158+
"Enter the production code below."
159+
)
160+
161+
self.validate_project_code(self.view.project_code_lineedit.text())
162+
163+
def set_production_code_no(self) -> None:
164+
"""Switches production code to no and informs the model."""
165+
self.view.production_code_no_button.setChecked(True)
166+
self.view.production_code_yes_button.setChecked(False)
167+
self.model.set_has_production_code(False)
168+
self.view.production_code_enter_text.setText(
169+
"Come up with a three-letter code for your project. (e.g. ABC)"
170+
)
171+
172+
self.validate_project_code(self.view.project_code_lineedit.text())
173+
174+
def validate_project_code(self, project_code: str) -> None:
175+
"""Validates project name and updates view.
176+
177+
Args:
178+
project_code: String project code, either P#### or ABC.
179+
"""
180+
production_code_validation_text = (
181+
self.view.production_code_validation_text
182+
)
183+
184+
try:
185+
self.model.validate_project_code(project_code)
186+
production_code_validation_text.setText("Project code available!")
187+
production_code_validation_text.setStyleSheet(
188+
"color: '#8BFF3E'; font-size: 12px;"
189+
)
190+
191+
except ValidationError as validation_message:
192+
production_code_validation_text.setText(str(validation_message))
193+
production_code_validation_text.setStyleSheet(
194+
"color: '#FF3E3E'; font-size: 12px;"
195+
)
196+
197+
production_code_validation_text.show()
198+
199+
def add_supervisor(self) -> None:
200+
"""Tries to add the supervisor from the LineEdit to the list of supervisors."""
201+
supervisors_validation_text = self.view.supervisors_validation_text
202+
203+
try:
204+
username = self.model.add_supervisor(
205+
self.view.supervisors_lineedit.text()
206+
)
207+
208+
supervisors_validation_text.setText("Added supervisor to list!")
209+
supervisors_validation_text.setStyleSheet(
210+
"color: '#8BFF3E'; font-size: 12px;"
211+
)
212+
213+
self.view.supervisors_list.insertItem(0, username)
214+
self.view.supervisors_list.setCurrentIndex(0)
215+
self.view.supervisors_lineedit.setText("")
216+
217+
except ValidationError as validation_message:
218+
supervisors_validation_text.setText(str(validation_message))
219+
supervisors_validation_text.setStyleSheet(
220+
"color: '#FF3E3E'; font-size: 12px;"
221+
)
222+
223+
supervisors_validation_text.show()
224+
225+
def remove_supervisor(self) -> None:
226+
"""Tries to remove a supervisor from the list."""
227+
supervisors_validation_text = self.view.supervisors_validation_text
228+
229+
try:
230+
self.model.remove_supervisor(
231+
self.view.supervisors_list.currentText()
232+
)
233+
234+
self.view.supervisors_list.removeItem(
235+
self.view.supervisors_list.findText(
236+
self.view.supervisors_list.currentText()
237+
)
238+
)
239+
240+
supervisors_validation_text.setText(
241+
"Removed supervisor from list!"
242+
)
243+
supervisors_validation_text.setStyleSheet(
244+
"color: '#8BFF3E'; font-size: 12px;"
245+
)
246+
247+
except ValidationError as validation_message:
248+
supervisors_validation_text.setText(str(validation_message))
249+
supervisors_validation_text.setStyleSheet(
250+
"color: '#FF3E3E'; font-size: 12px;"
251+
)
252+
253+
supervisors_validation_text.show()
254+
255+
def set_render_engine(self, render_engine: str) -> None:
256+
"""Informs the model of the new render engine choice."""
257+
self.model.set_render_engine(render_engine)
258+
259+
def set_project_type_fiction(self) -> None:
260+
"""Sets the project type to fiction and informs the model."""
261+
self.view.project_type_fiction_button.setChecked(True)
262+
self.view.project_type_documentary_button.setChecked(False)
263+
self.model.set_project_type("Fiction")
264+
265+
def set_project_type_documentary(self) -> None:
266+
"""Sets the project type to documentary and informs the model."""
267+
self.view.project_type_documentary_button.setChecked(True)
268+
self.view.project_type_fiction_button.setChecked(False)
269+
self.model.set_project_type("Documentary")
270+
271+
def set_fps(self, fps: int) -> None:
272+
"""Informs the model of the new FPS.
273+
274+
Args:
275+
fps: New project FPS"""
276+
self.model.set_fps(fps)
277+
278+
def create_project(self) -> None:
279+
"""Validates the project, then starts project creation on a separate thread."""
280+
try:
281+
self.model.validate_project()
282+
283+
except ValidationError as validation_message:
284+
self.view.project_validation_text.setText(str(validation_message))
285+
self.view.project_validation_text.setStyleSheet(
286+
"color: '#FF3E3E'; font-size: 12px;"
287+
)
288+
self.view.project_validation_text.show()
289+
return
290+
291+
self.view.main_widget.hide()
292+
self.view.loading_text.setText("Creating project...")
293+
self.view.loading_widget.show()
294+
295+
self.model.start_project_creation(
296+
self.project_creation_successful, self.project_creation_failed
297+
)
298+
299+
def project_creation_successful(self, project_url: str) -> None:
300+
"""Runs when project creation has successfully finished.
301+
302+
Args:
303+
project_url: Link to ShotGrid site for project.
304+
"""
305+
self.view.loading_widget.hide()
306+
307+
self.view.layout.addWidget(
308+
self.view.get_project_creation_successful_widget(project_url)
309+
)
310+
311+
def project_creation_failed(self, error: str) -> None:
312+
"""Runs when project creation has failed.
313+
314+
Args:
315+
error: Python error message in string format.
316+
"""
317+
self.view.loading_widget.hide()
318+
self.view.layout.addWidget(self.view.get_error_widget())
319+
self.view.error_text.setText(
320+
f"Project creation error: {error}. Please contact a pipeline TD if problem persist."
321+
)

main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Main start file for the NFA ShotGrid Project Creator, written by Mervin van Brakel (2024)"""
2+
3+
import sys
4+
5+
from PySide2 import QtWidgets
6+
7+
from controller import ProjectCreatorController
8+
9+
if __name__ == "__main__":
10+
app = QtWidgets.QApplication(sys.argv)
11+
12+
controller = ProjectCreatorController()
13+
14+
sys.exit(app.exec_())

0 commit comments

Comments
 (0)