def __onAddScene(self): # request user for a template if there are multiple options templates = list(Templates(self.__subFolder)) if not templates: QMessageBox.critical(self, 'Could not create scene', 'Can not add scenes to this project until a template has been made to base them off.') return if len(templates) == 1: templateDir = TemplateSourceFolderFromName(templates[0], self.__subFolder) templatePath = TemplateFileFromName(templates[0], self.__subFolder) else: template = QInputDialog.getItem(self, 'Create scene', 'Select template', templates, 0, False) if not template[1] or not template[0] in templates: return templateDir = TemplateSourceFolderFromName(template[0], self.__subFolder) templatePath = TemplateFileFromName(template[0], self.__subFolder) name = QInputDialog.getText(self, 'Create scene', 'Scene name') if not name[1]: return scenesPath = ScenesPath(self.__subFolder) outFile = os.path.join(scenesPath, name[0] + SCENE_EXT) outDir = os.path.join(scenesPath, name[0]) if os.path.exists(outFile): QMessageBox.critical(self, 'Could not create scene', 'A scene with name "%s" already exists. No scene was created.' % name[0]) return if os.path.exists(outDir): if QMessageBox.warning(self, 'Scene not empty', 'A folder with name "%s" already exists. Create scene anyways?' % name[0], QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Cancel: return else: os.makedirs(outDir) with fileutil.edit(outFile) as fh: fh.write('<scene camera="0,1,-10,0,0,0" template="%s"/>' % os.path.relpath(templatePath, scenesPath)) # find required template inputs (sections) xTemplate = ParseXMLWithIncludes(templatePath) for xPass in xTemplate: for xElement in xPass: if xElement.tag.lower() != 'section': continue # given a section make a stub file so the scene is complete on disk resource = os.path.join(templateDir, xElement.attrib['path']) text = '' # copy template data if there is any if os.path.exists(resource): with open(resource) as fh: text = fh.read() with fileutil.edit(os.path.join(outDir, xElement.attrib['path'])) as fh: fh.write(text) self.appendSceneItem(name[0])
def _saveSceneShots(sceneName, shots): sceneFile = os.path.join(ScenesPath(), sceneName + SCENE_EXT) xScene = ParseXMLWithIncludes(sceneFile) # save user camera position per scene userFile = ProjectFile() + '.user' if fileutil.exists(userFile): xUser = ParseXMLWithIncludes(userFile) else: xUser = cElementTree.Element('user') if sceneFile in Scene.cache: cameraData = Scene.cache[sceneFile].cameraData() if cameraData: for xSub in xUser: if xSub.tag == 'scene' and xSub.attrib['name'] == sceneName: xSub.attrib['camera'] = ','.join([str(x) for x in cameraData]) break else: cElementTree.SubElement(xUser, 'scene', {'name': sceneName, 'camera': ','.join([str(x) for x in cameraData])}) with fileutil.edit(userFile) as fh: fh.write(toPrettyXml(xUser)) # remove old shots r = [] for xShot in xScene: r.append(xShot) for s in r: xScene.remove(s) targets = [] for shot in shots: if shot.sceneName == sceneName: targets.append(shot) for shot in targets: xShot = cElementTree.SubElement(xScene, 'Shot', {'name': shot.name, 'scene': sceneName, 'start': str(shot.start), 'end': str(shot.end), 'enabled': str(shot.enabled), 'speed': str(shot.speed), 'preroll': str(shot.preroll)}) for curveName in shot.curves: xChannel = cElementTree.SubElement(xShot, 'Channel', {'name': curveName, 'mode': 'hermite'}) data = [] for key in shot.curves[curveName]: data.append(str(key.inTangent.x)) data.append(str(key.inTangent.y)) data.append(str(key.point().x)) data.append(str(key.point().y)) data.append(str(key.outTangent.x)) data.append(str(key.outTangent.y)) data.append(str(int(key.tangentBroken))) data.append(str(key.tangentMode)) xChannel.text = ','.join(data) for texName in shot.textures: cElementTree.SubElement(xShot, 'Texture', {'name': texName, 'path': shot.textures[texName]}) with fileutil.edit(sceneFile) as fh: fh.write(toPrettyXml(xScene))
def saveState(self): gSettings.setValue('TimerStartTime', self.__start) gSettings.setValue('TimerEndTime', self.__end) gSettings.setValue('TimerTime', self.__time) project = ProjectFile() if not project or not fileutil.exists(project): # legacy project or no project open gSettings.setValue('TimerMinTime', self.__minTime) gSettings.setValue('TimerMaxTime', self.__maxTime) gSettings.setValue('TimerBPS', self.__BPS) return root = cElementTree.Element('Project') root.attrib['TimerMinTime'] = str(self.__minTime) root.attrib['TimerMaxTime'] = str(self.__maxTime) root.attrib['TimerBPS'] = str(self.__BPS) with fileutil.edit(project, 'w') as fh: fh.write(toPrettyXml(root))
def save(self, filePath, ch=None): if filePath.endswith('.r32'): import struct # heightfield export pixels = self._width * self._height buffer = (ctypes.c_float * pixels)() glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, buffer) with fileutil.edit(filePath, 'wb') as fh: fh.write(struct.pack('%sf' % pixels, *buffer)) return from PyQt4.QtGui import QImage pixels = self._width * self._height buffer = (ctypes.c_ubyte * (pixels * 4))() mirror = (ctypes.c_ubyte * (pixels * 4))() glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer) for i in xrange(0, pixels * 4, 4): if ch is None: mirror[i:i + 4] = (buffer[i + 2], buffer[i + 1], buffer[i], buffer[i + 3]) else: mirror[i:i + 4] = (buffer[i + ch], buffer[i + ch], buffer[i + ch], 255) QImage(mirror, self._width, self._height, QImage.Format_ARGB32).save(filePath)
def __record(self): diag = QDialog() fId = gSettings.value('RecordFPS', 2) rId = gSettings.value('RecordResolution', 3) layout = QGridLayout() diag.setLayout(layout) layout.addWidget(QLabel('FPS: '), 0, 0) fps = QComboBox() fps.addItems(['12', '24', '30', '48', '60', '120']) fps.setCurrentIndex(fId) layout.addWidget(fps, 0, 1) layout.addWidget(QLabel('Vertical resolution: '), 1, 0) resolution = QComboBox() resolution.addItems(['144', '288', '360', '720', '1080', '2160']) resolution.setCurrentIndex(rId) layout.addWidget(resolution, 1, 1) ok = QPushButton('Ok') ok.clicked.connect(diag.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(diag.reject) layout.addWidget(ok, 2, 0) layout.addWidget(cancel, 2, 1) diag.exec_() if diag.result() != QDialog.Accepted: return gSettings.setValue('RecordFPS', fps.currentIndex()) gSettings.setValue('RecordResolution', resolution.currentIndex()) FPS = int(fps.currentText()) HEIGHT = int(resolution.currentText()) WIDTH = (HEIGHT * 16) / 9 FMT = 'jpg' data = (ctypes.c_ubyte * (WIDTH * HEIGHT * 3))() # alloc buffer once flooredStart = self._timer.secondsToBeats(int(self._timer.beatsToSeconds(self._timer.start) * FPS) / float(FPS)) duration = self._timer.beatsToSeconds(self._timer.end - flooredStart) if not fileutil.exists('capture'): os.makedirs('capture') progress = QProgressDialog(self) progress.setMaximum(int(duration * FPS)) prevFrame = 0 for frame in xrange(int(duration * FPS)): deltaTime = (frame - prevFrame) / float(FPS) prevFrame = frame progress.setValue(frame) QApplication.processEvents() if progress.wasCanceled(): break beats = flooredStart + self._timer.secondsToBeats(frame / float(FPS)) shot = self.__shotsManager.shotAtTime(beats) if shot is None: continue sceneFile = os.path.join(ScenesPath(), shot.sceneName + SCENE_EXT) scene = Scene.getScene(sceneFile) scene.setSize(WIDTH, HEIGHT) uniforms = self.__shotsManager.evaluate(beats) textureUniforms = self.__shotsManager.additionalTextures(beats) self.__sceneView._cameraInput.setData(*(uniforms['uOrigin'] + uniforms['uAngles'])) # feed animation into camera so animationprocessor can read it again cameraData = self.__sceneView._cameraInput.data() modifier = os.path.join(ProjectDir(), 'animationprocessor.py') if fileutil.exists(modifier): execfile(modifier, globals(), locals()) for name in self.__sceneView._textures: uniforms[name] = self.__sceneView._textures[name]._id scene.drawToScreen(self._timer.beatsToSeconds(beats), beats, uniforms, (0, 0, WIDTH, HEIGHT), textureUniforms) scene.colorBuffers[-1][0].use() from OpenGL.GL import glGetTexImage, GL_TEXTURE_2D, GL_RGB, GL_UNSIGNED_BYTE glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data) QImage(data, WIDTH, HEIGHT, QImage.Format_RGB888).mirrored(False, True).save('capture/dump_%s_%05d.%s' % (FPS, int(self._timer.beatsToSeconds(self._timer.start) * FPS) + frame, FMT)) progress.close() if not fileutil.exists('convertcapture'): os.makedirs('convertcapture') with fileutil.edit('convertcapture/convert.bat') as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format(int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format(int(self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write('cd "../capture"\n"../convertcapture/ffmpeg.exe" -framerate {} {}-i dump_{}_%%05d.{} {}-c:v libx264 -r {} -pix_fmt yuv420p "../convertcapture/output.mp4"'.format(FPS, start, FPS, FMT, start2, FPS)) with fileutil.edit('convertcapture/convertGif.bat') as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format(int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format(int(self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write('cd "../capture"\n"../convertcapture/ffmpeg.exe" -framerate {} {}-i dump_{}_%%05d.{} {}-r {} "../convertcapture/output.gif"'.format(FPS, start, FPS, FMT, start2, FPS)) sound = self.timeSlider.soundtrackPath() if not sound: return with fileutil.edit('convertcapture/merge.bat') as fh: startSeconds = self._timer.beatsToSeconds(self._timer.start) fh.write('ffmpeg -i output.mp4 -itsoffset {} -i "{}" -vcodec copy -shortest merged.mp4'.format(-startSeconds, sound))
def run(): shots = [] scenes = [] scenesDir = ScenesPath() for scene in os.listdir(scenesDir): if not scene.endswith(SCENE_EXT): continue scenePath = os.path.join(scenesDir, scene) sceneDir = os.path.splitext(scenePath)[0] xScene = ParseXMLWithIncludes(scenePath) templatePath = os.path.join(scenesDir, xScene.attrib['template']) templateDir = os.path.splitext(templatePath)[0] xTemplate = Template(templatePath) scene = [] for xPass in xTemplate: stitchIds = [] uniforms = {} for xSection in xPass: baseDir = sceneDir if xSection.tag in ('global', 'shared'): baseDir = templateDir shaderFile = os.path.join(baseDir, xSection.attrib['path']) stitchIds.append(text.addFile(shaderFile)) for xUniform in xSection: name = xUniform.attrib['name'] values = [ float(x.strip()) for x in xUniform.attrib['value'].split(',') ] uniforms[text.addString(name)] = len( values), floats.addFloats(values, name) programId = shaders.fromStitches(stitchIds) buffer = int(xPass.attrib.get('buffer', -1)) outputs = int(xPass.attrib.get('outputs', 1)) size = int(xPass.attrib.get('size', 0)) width = int(xPass.attrib.get('width', size)) height = int(xPass.attrib.get('height', size)) factor = int(xPass.attrib.get('factor', 1)) static = int(xPass.attrib.get('static', 0)) is3d = int(xPass.attrib.get('is3d', 0)) if buffer != -1: buffer = framebuffers.add(buffer, outputs, width, height, factor, static, is3d) i = 0 key = 'input%s' % i inputs = [] while key in xPass.attrib: v = xPass.attrib[key] if '.' in v: a, b = v.split('.') else: a, b = v, 0 inputs.append((int(a), int(b))) i += 1 key = 'input%s' % i scene.append(passes.add(programId, buffer, inputs, uniforms)) sceneIndex = len(scenes) scenes.append(len(scene)) scenes += scene for xShot in xScene: if xShot.attrib.get('enabled', 'True') == 'False': continue animations = {} for xChannel in xShot: uname = xChannel.attrib['name'] n = uname x = 0 if '.' in uname: n, x = uname.rsplit('.', 1) x = 'xyzw'.index(x) n = text.addString(n) if n not in animations: animations[n] = [] if not xChannel.text: keyframes = [] else: keyframes = [] for i, v in enumerate( float(v.strip()) for v in xChannel.text.split(',')): j = i % 8 if j == 0 or j == 4 or j > 5: continue if j == 5: # out tangent y if v == float( 'inf' ): # stepped tangents are implemented as out tangentY = positive infinity v = 'FLT_MAX' keyframes.append(v) assert len(keyframes) / 4.0 == int(len(keyframes) / 4), len(keyframes) while len(animations[n]) <= x: animations[n].append(None) assert animations[n][x] is None animations[n][x] = floats.addFloats(keyframes), len(keyframes) for channelStack in animations.values(): # TODO we can not / do not check if the channelStack length matches the uniform dimensions inside the shader (e.g. are we sure we're not gonna call glUniform2f for a vec3?) assert None not in channelStack, 'Animation provided for multiple channels but there is one missing (Y if a vec3 or also Z if a vec4).' shots.append((float(xShot.attrib['start']), float(xShot.attrib['end']), sceneIndex, animations)) # sort shots by start time def _serializeShots(shots): shots.sort(key=lambda x: x[0]) shotTimesStart = floats.addFloats( [x for shot in shots for x in (shot[0], shot[1])]) yield '\n\n__forceinline int shotAtBeats(float beats, float& localBeats)\n{\n' if len(shots) == 1: yield '\tlocalBeats = beats - gFloatData[%s];\n' % shotTimesStart yield '\treturn 0;\n' else: yield '\tint shotTimeCursor = 0;\n' yield '\tdo\n\t{\n' yield '\t\tif(beats < gFloatData[shotTimeCursor * 2 + %s])\n\t\t{\n' % ( shotTimesStart + 1) yield '\t\t\tlocalBeats = beats - gFloatData[shotTimeCursor * 2 + %s];\n' % shotTimesStart yield '\t\t\treturn shotTimeCursor;\n' yield '\t\t}\n' yield '\t}\n\twhile(++shotTimeCursor < %s);\n' % len(shots) yield '\treturn -1;\n' yield '}\n' global gShotScene gShotScene = ints.addInts([shot[2] for shot in shots]) flatAnimationData = [] animationDataPtrs = [] for shot in shots: animationDataPtrs += [len(flatAnimationData), len(shot[3].keys())] global gAnimEntriesMax gAnimEntriesMax = max(gAnimEntriesMax, len(shot[3].keys())) for uniformStringId in shot[3]: animationData = shot[3][uniformStringId] flatAnimationData += [uniformStringId, len(animationData)] for pair in animationData: flatAnimationData += pair flatAnimationData += [0] * (2 * (4 - len(animationData))) global gShotAnimationDataIds gShotAnimationDataIds = ints.addInts(animationDataPtrs) global gShotUniformData gShotUniformData = ints.addInts(flatAnimationData) def _serializeAll(scenes, shots): buffer = list(_serializeShots(shots)) for serializable in (text, floats): for ln in serializable.serialize(): yield ln buffer2 = [] for serializable in (shaders, framebuffers, passes): buffer2 += list(serializable.serialize()) global gScenePassIds gScenePassIds = ints.addInts(scenes) for ln in ints.serialize(): yield ln for ln in buffer2: yield ln for ln in buffer: yield ln data = [''.join(_serializeAll(scenes, shots))] data.append( """\n\n__forceinline float evalCurve(const float* data, int numFloats, float beats) { \tif(numFloats == 4 || beats <= data[1]) // 1 key or evaluating before first frame \t\treturn data[2]; \t// Find index of first key that has a bigger time than our current time \t// if none, this will be the index of the last key. \tint keyValueCount = numFloats; \tint rightKeyIndex = 4; \twhile (rightKeyIndex < keyValueCount - 4 && data[rightKeyIndex + 1] < beats) \t\trightKeyIndex += 4; \t// Clamp our sampling time to our range \tfloat sampleTime = (beats > data[rightKeyIndex + 1]) ? data[rightKeyIndex + 1] : beats; \t// Retrieve our spline points \tfloat y0 = data[rightKeyIndex - 2]; \tfloat y1 = data[rightKeyIndex - 1]; \t// handle stepped tangents \tif(y1 == FLT_MAX) return y0; \tfloat y2 = data[rightKeyIndex]; \tfloat y3 = data[rightKeyIndex + 2]; \tfloat dy = y3 - y0; \tfloat c0 = y1 + y2 - dy - dy; \tfloat c1 = dy + dy + dy - y1 - y1 - y2; \tfloat c2 = y1; \tfloat c3 = y0; \t// Determine factor \tfloat dt = data[rightKeyIndex + 1] - data[rightKeyIndex - 3]; \tfloat t = (sampleTime - data[rightKeyIndex - 3]) / dt; \treturn t * (t * (t * c0 + c1) + c2) + c3; } #define gAnimEntriesMax %s #define gShotAnimationDataIds %s #define gShotScene %s #define gScenePassIds %s #define gPassProgramsAndTargets %s #define gShotUniformData %s #define gFrameBufferData %s #define gFrameBufferBlockSize %s #define gProgramCount %s """ % (gAnimEntriesMax, gShotAnimationDataIds, gShotScene, gScenePassIds, gPassProgramsAndTargets, gShotUniformData, gFrameBufferData, FrameBufferPool.BLOCK_SIZE, len(shaders.offsets))) dst = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Player', 'generated.hpp') with fileutil.edit(dst, 'w') as fh: fh.write(''.join(data))