# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import datetime import json from studio import checklist #import checklist def get_legacy(project_location): # This function will read the .bos (Blender-Organizer Story) files. Primarily # to convert them to .vcss (VCStudio Story) files. # The concept is similar. But in order to work with them outside of reading # and writting the file directly. I want to make a dictionary format with in # the python. (Similarly to how I did with checklists and analytics) # In the .bos file there was what I thought was clever at the time but # ultimatly a dumb idea to make EVENTS and not scenes. The idea was that in # a single event could be multiple scenes. But in reality it very rearly used # and creating scenes with in events created a potential user error situation. # So I want to get rid of all the EVENTS and read Scenes directly. Unless # the event is empty of scenes. In which case to create a scene with the # text of that event. data = { "fraction": 0.0, # Percentage of the Scenes finished. "camera" : [0,0], # The position of where the user left "selected": [], # List of selected items in the story editor "active": None, # Active item. "scenes" : {}, # List of scenes. "arrows" : [], # List of connections. (A LIST. NOT DICT.) "links" : [], # List of links to files or assets. "markers": {}, # List of markers. "events" : {} # List of frame like containers. (It will have similar) } # function as events. But will have no text data with in # it. It will be more like Blender's node editor's Frame. # Even tho I want to change radically the idea of events. We still need to # think in the events terms. Because we are parsing and old file. Funny how # I will need to write this thing twice. For both file-types. # I can't read it the way I read most other files. Because it's not line- # based. But more something like HTML file. So it's going to be a little # issue. bos = open(project_location+"/pln/main.bos") bos = bos.read() cx, cy = 1, 1 if "" in bos: camera = bos[bos.find("")+8:] camera = camera[:camera.find("")] camera = camera.split(",") for num, val in enumerate(camera): try: camera[num] = float(val) except: camera[num] = 0.0 try: cx = float(camera[2]) cy = float(camera[3]) except: pass print (cy, cx) # Some stupid me made decision early in a story editor's life to use # per-pixel X coordinates and per line Y coordinates. Which is something # like 100 times the difference. Okay 50 ish is a default value for Y. # I'm not going to do anything about it right now. Maximum a bit later. camera = [camera[0], camera[1]] # I don't want scale to exist here. data["camera"] = camera # Events. They are very important. Now in the Blender-Organizer I used an # HTML like format to mark shots and assets with in the text. But it's not # a very efficient way of doing it. Because it requiered constant parsing # of the text in real time. Which caused quite a noticable lag. And also # every time I needed to read a part of it. I had to do parsing of the text. # So I guess the VCSS file format will be designed to deal with this kind of # thing. I'm actually more toward putting the text into separate files. Simple # text documents. And making the VCSS a linking system. if "" in bos: for event in bos.split("")[:-1]: event = event[event.find("")+8:] # Now we have text of the event. Let's parse out of it folowing # stuff. We will need it if there are multiple scenes. Or when # there are no scenes at all. eventname = event[event.find('"')+1:event.replace('"'," ",1).find('"')] # Let's parse the coordinates. c = event[event.find('[')+1:event.find(']')] c = c.split(",") eventpositon = [float(c[0])*cx,float(c[2])*cy] eventsize = [float(c[1])*cx,60.0] # Now since we know the name of the event and the sizes. We can # start parsing the scenes from the text with in the event. eventtext = event[event.find(']')+2:-1] # Now basically have to do the same exact thing with and # later with , to make all work. # But first let's record a scene if it has no scene in it. if not "" in eventtext: if eventname in data["scenes"]: eventname = eventname + "_copy" data["scenes"][eventname] = { "fraction":0.0, # Percentage "position":eventpositon, "size":eventsize, "parent":"", # For when it's in a Frame (Event) "shots":[[ "text_block",[["text", eventtext]] ]] } else: # If there are more then 1 scene per event. We want to create # an event, frame thing for them. aos = eventtext.count("") parent = "" #This will be it's name if aos > 1: parent = eventname data["events"][eventname] = { "position":eventpositon, "size":[0,0] } # Now let's continue parsing the scenes. for num, scene in enumerate(eventtext.split("")[:-1]): scenename = scene[scene.find('"')+1:scene.replace('"'," ",1).find('"')] scenename = scenename.replace(" ", "_") scenetext = scene[scene.replace('"', " ", 1).find('"')+1:-1] scenesize = [eventsize[0] / aos , eventsize[1]] sceneposition = [eventpositon[0] + scenesize[0]*num, eventpositon[1]] data["scenes"][scenename] = { "fraction":0.0, # Percentage "position":sceneposition, "size":scenesize, "parent":parent, # For when it's in a Frame (Event) "shots":[[ "text_block",[["text", scenetext]] ]] } # Parsing the text inside the scenes... OMG. Finding the SHOTS. for scenename in data["scenes"]: scenetext = data["scenes"][scenename]["shots"][0][1][0][1] # Okay so we have both scene name and scene text. So it's # time so send this data into the main data thingy, right? # Wrong. Now we need to parse pointers to the shots, assets # and other stuff. This file format gonna take a while. # If you look into the history of this file on NotABug there # Will be a version with a novel-long article here in the # comments. I was toying with ideas of how to make stuff # work. I will not change the whole system a bit from what # I though there. (Using text pointers) and make it more like # list of shot_block or text_block. Which both are just text. # But one has a bit more metadata then the other. And inside # which you can specify links, frases, images and simple text... # So first of all we need to break the text up into shots. shots = [] sa = scenetext.count("") # Shots amount ts = scenetext # Copy of the scene text to butcher for s in range(sa): # If the first part of the scene is not a shot. if not ts.startswith(""): shots.append([ "text_block", [["text",ts[:ts.find("")]]] ]) # Now we need to erase the part from the ts. ts = ts[ts.find("")+6:] # Now we can parse the shot itself. We need the name. if ts.count('"') > 1: shotname = ts[ts.find('"')+1:ts.replace('"', " ", 1).find('"')] ts = ts[ts.replace('"', " ", 1).find('"')+1:] else: shotname = "Unnamed" # Put it also into the list. shots.append([ "shot_block", shotname, [["text",ts[:ts.find("")]]] ]) # And erase it. ts = ts[ts.find("")+7:] shots.append([ "text_block", [["text",ts]] ]) # Now I want to get a fraction from a scene. shotsfractions = [] for shot in shots: if shot[0] == "shot_block": # Let's see if it has a checklist. if os.path.exists(project_location\ +"/rnd/"+scenename+"/"+shot[1]+"/shot.progress"): check = checklist.get_list(project_location+"/project.progress") shotsfractions.append(check["fraction"]) else: folder = project_location\ +"/rnd/"+scenename+"/"+shot[1] try: if len(os.listdir(folder+"/rendered")) > 0: shotsfractions.append(1.0) elif len(os.listdir(folder+"/test_rnd")) > 0: shotsfractions.append(0.8) elif len(os.listdir(folder+"/opengl")) > 0: shotsfractions.append(0.6) elif len(os.listdir(folder+"/storyboard")) > 0: shotsfractions.append(0.4) elif len(os.listdir(folder+"/extra")) > 0: shotsfractions.append(0.2) except: shotsfractions.append(0.0) # Writting parsed verions of the scenes. With all the shots data["scenes"][scenename]["shots"] = shots # Now that we have SHOTS Fractions we need a scene fraction. try: data["scenes"][scenename]["fraction"] = \ sum(shotsfractions) / len(shotsfractions) except: data["scenes"][scenename]["fraction"] = 0.0 # Now since we have shots and we have their factors. I'd like to do # parsing of the text parts inside shots and texts between shots to # find all the images, links and phrases. for scenename in data["scenes"]: for shotnum, shot in enumerate(data["scenes"][scenename]["shots"]): # Our shot data could be either text_block ot shot_block # and they are formatted a little bit differently. if shot[0] == "shot_block": text = shot[2][0][1] else: text = shot[1][0][1] # Now keep in mind that similar stuff has to be done again # when we saving the parsed text back textblock = [] # This requires some extreme cleverness. Since I already hate # this file format. WHY IS IT SO COMPLICATED?! part = "" skip = 0 for num, letter in enumerate(text): if num >= skip: if part.endswith(""): # putting the normal text in if part[skip:].replace("", ""): textblock.append([ "text", part[skip:].replace("", "") ]) # parsing the item itempart = text[num:text[num:].find("")+num] link = itempart[itempart.find('"')+1: itempart.replace('"', " ", 1).find('"')] if link.startswith("/dev"): link = link[4:] itemtext = itempart[itempart.replace('"', " ", 1).find('"')+1:] # puttin the link into the list textblock.append([ "link", link, itemtext ]) # skiping to after the item skip = text[num:].find("")+6 part = "" # Images. With the <> elif part.endswith(""): # putting the normal text in if part[skip:].replace("", ""): textblock.append([ "text", part[skip:].replace("", "") ]) # parsing the item link = text[num:text[num:].find("")+num] textblock.append([ "image", link ]) # skiping to after the item skip = text[num:].find("")+7 part = "" # Images. With the [] (a little older version but still # should be supported by default) elif part.endswith("[image]"): # putting the normal text in if part[skip:].replace("[image]", ""): textblock.append([ "text", part[skip:].replace("[image]", "") ]) # parsing the item link = text[num:text[num:].find("[/image]")+num] textblock.append([ "image", link ]) # skiping to after the item skip = text[num:].find("[/image]")+7 part = "" # Now let's figure out the frases. Because it has a # text based thing in the beginning. Or maybe it's going # to be anything that was before it? Hmm... elif part.endswith(" - ["): # putting the normal text in if part[skip:].replace(" - [", ""): textblock.append([ "text", part[skip:].replace(" - [", "") ]) # I designed the - [ thing to type conversation # easier. Example: # John - [Hello, World.] # This would be John saying the words "Hello, World." # but with the inclusion of it became quite a # problem. Because I would like John to be a link. # It ended up looking something like: #"/dev/chr/John"John - [Hello, World.] # This is a bit of a problem. Because any link that # marked as is already in a textblock list. character = [] # If it's a link if textblock[-1][0] == "link": character = textblock[-1] del textblock[-1] # If it's just a text elif textblock[-1][0] == "text": character = textblock[-1][1] character = character[character.rfind("\n")+1:] if character: textblock[-1][1] = textblock[-1][1].replace(character, "") character = ["text", character] # Now let's get the frase frase = text[num:text[num:].find("]")+num] # If any character. Put it into a textblock if character: textblock.append([ "frase", character, frase ]) # skiping to after the item skip = text[num:].find("]") part = "" else: part = part + letter if shot[0] == "shot_block": data["scenes"][scenename]["shots"][shotnum][2] = textblock else: data["scenes"][scenename]["shots"][shotnum][1] = textblock # Okay this was event. LOL. This is a large function to parse them all. # Crazy complex bastards. Anyway. It's over. And we have to only do the # easy rest of it. Untill we will get to calculating the project. Because # we need to filter out all scenes that are not in the main chain. # Let's start with hard thing first. So to feel good when having to do the # easy stuff in the end. ARROWS. (connections between scenes) # To make it work. We need to fisrt of all earase all the stuff before # the first arrow. Because could be mentioned in the script it # self. Similarly is too. So it's good to complitelly clear bos # out of anything that came before. bos = bos[bos.rfind(""):] # From here everything is PER LINE BASED... YEAH!!!! bos = bos.split("\n") for line in bos: # This is arrows... if line.startswith(""): arrow = line[line.find("<")+7:line.rfind(" ") newarrow = [] for i in arrow: i = i.split(",") if i[0] != "-1": newarrow.append([ "scene", i[1][1:-1] ]) else: newarrow.append( i[1][1:-1] ) data["arrows"].append(newarrow) # And this is links. Formerly known as Images. Initially the idea was to # just store images. But later. I started adding assets like this as well. elif line.startswith(""): stuff = line.split(",") link = stuff[3][1:-1] coordinates = [] try: coordinates.append(float(stuff[0].replace("", ""))*cx) except: coordinates.append(0.0) try: coordinates.append(float(stuff[1])*cy) except: coordinates.append(0.0) # Lately the primary reason to use was linking assets directly # into story editor's space. But it was a hack. Basically I was # linking a preview image. Preview.png or Preview.jpg from the # renders of the asset. And the drawer of the images in the story- # editor already knew to redirect the clicks to a different function. # But for VCStudio I want to link to asset or a file. linktype = "file" if "/renders/Preview." in link: linktype = "asset" link = link[:link.rfind("/renders/Preview.")].replace("/dev", "") data["links"].append([ linktype, link, coordinates, "" ]) # Markers. I nearly forgot about the markers. In the Story-Editor of # Blender-Orgaznier they were something like markers in VSE in Blender. # Vertical lines visible on every altitude. elif line.startswith(""): marker = line.replace("", "").replace("", "").split(",") try: markloc = float(marker[0]) except: markloc = 0.0 markstring = marker[1].replace('"', "") # I do want to do something quite interesting with markers in the # VCStudio tho. I want them to be like little nodes with text while # in the frame. And be like directional guides. Sticking to the # edges of the screen when outside the screen. Something like items # that are outside of the map in the videogames. So you can see # which direction are they. For this markers will need to have # both X and Y coordinates. data["markers"][markstring] = [markloc, 0.0, ""] # For now the Y is 0. Because we are reading from the old version. # Okay we've got the Arrows data. Pretty much all the data. Now we just # need to calculate the scenes fraction. For that we need to recreate the # train of scenes. From START till END. fractions = [] lastarrow = 'start' for i in data["arrows"]: if lastarrow == "end": break for arrow in data["arrows"]: if arrow[0] == lastarrow: lastarrow = arrow[1] if arrow[1] != "end": fractions.append( data["scenes"][arrow[1][1]]["fraction"] ) else: break # FINAL STUFF... try: data["fraction"] = sum(fractions) / len(fractions) except: data["fraction"] = 0.0 #save(project_location, data) #data = load(project_location) return data def get_asset_data(win, name, force=False): # This function will return a data of about the asset. if name not in win.assets or force: data = { "fraction":0.0 } try: # Now let's get a fraction of the asset if os.path.exists(win.project+"/ast/"+name+".blend"): data["fraction"] = 1.0 else: check = checklist.get_list(win.project+"/dev/"+name+"/asset.progress") data["fraction"] = check["fraction"] except: pass win.assets[name] = data return win.assets[name] def save(project, story): # This is a save of the new VCStudio. Which is using a standard file format. # so people could parse the data themselves. ( Or because I'm lazy ) try: os.mkdir(project+'/pln/') except: pass with open(project+'/pln/story.vcss', 'w') as fp: json.dump(story, fp, sort_keys=True, indent=4) def load(project): # This is a convertion back from JSON data to out beloved python dict try: with open(project+'/pln/story.vcss') as json_file: data = json.load(json_file) except: # If there is no file. data = { "fraction": 0.0, # Percentage of the Scenes finished. "camera" : [0,0], # The position of where the user left "selected": [], # List of selected items in the story editor "active": None, # Active item. "scenes" : {}, # List of scenes. "arrows" : [], # List of connections. (A LIST. NOT DICT.) "links" : [], # List of links to files or assets. "markers": {}, # List of markers. "events" : {} # List of frame like containers. (It will have similar) } # function as events. But will have no text data with in # it. It will be more like Blender's node editor's Frame. fractions = [] lastarrow = 'start' for i in data["arrows"]: if lastarrow == "end": break for arrow in data["arrows"]: if arrow[0] == lastarrow: lastarrow = arrow[1] if arrow[1] != "end": fractions.append( data["scenes"][arrow[1][1]]["fraction"] ) else: break # FINAL STUFF... try: data["fraction"] = sum(fractions) / len(fractions) except: data["fraction"] = 0.0 return data