3
$\begingroup$

I don't want to load data from an external file location. Instead I want the user to drop an xml file into an open text window, then run a script that can use this file as a data source. The file will always have the extension .csv. But I don't know how to search for an existing datablock like this?

Currently its a hard coded path for testing:

# READ INPUT FILE

fp   = "/Users/davidmcsween/Downloads/TEST LIST.csv"

file = csv.reader( open( fp, newline='' ), delimiter=',' )

nameIdx = 3 # Column index of text object's name
bodyIdx = 4 # Column index of text object's body (content)

I had hoped that I could use

fp   = bpy.data.texts[", *,".csv"].name = "", *,".csv"

But this fails.

$\endgroup$

2 Answers 2

3
$\begingroup$

In as much as you can get all the texts in a blend file with [text for text in bpy.data.texts] and the filepath is text.filepath. If a file is pasted into the text editor then it will have filepath = "". I recommend using the text editor current text as your csv data. A simple panel is added to the text editor properties, which ensures that the current csv file is

current_csv_file = context.edit_text

Firstly, a simple operator that prints out the csv data, split by "," line for line.

import bpy

def main(context):
    text = context.edit_text
    for line in text.lines:
        split = line.body.split(",")
        print(split)

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "text.import_csv"
    bl_label = "Import CSV"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        main(context)
        return {'FINISHED'}

def register():
    bpy.utils.register_class(SimpleOperator)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
    register()

Can make a panel in the text editor UI like so

enter image description here

import bpy

class TextPTImportCSV(bpy.types.Panel):
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "Import CSV"
    bl_idname = "TEXT_PT_import_csv"
    bl_space_type = 'TEXT_EDITOR'
    bl_region_type = 'UI'

    @classmethod
    def poll(cls, context):
        text = context.edit_text
        return text.name[-4:].lower() == ".csv"

    def draw(self, context):
        layout = self.layout
        layout.label(context.edit_text.name)        
        layout.operator("text.import_csv")

def register():
    bpy.utils.register_class(TextPTImportCSV)

def unregister():
    bpy.utils.unregister_class(TextPTImportCSV)

if __name__ == "__main__":
    register()

which running on the simple test file prints

['1', ' 1', ' 1', ' 1', ' 1', ' 1']
['2', ' 2', ' 2', ' 2', ' 2', ' 2']
['3', ' 3', ' 3', ' 3', ' 3', ' 3']

to the console.

EDIT: as a side note, split above will be a list of strings, and will need to be converted if they are other. Here is a basic way to make a key for your csv file. Consider a file that has 4 columns, the first is an integer for frame, the next three a location. Quite likely there is a way to use the text.lines as input to csv.reader (simple workaround would be to save to tmp file)

# key of csv file, [property name,  column(s), type]
key = [["frame", 0, int], # frame in col 1
       ["location", [1, 2, 3], float] # loc x, y, z in cols 1, 2, 3.
       ]
'''
data in csv like
1, 1.0, 1.0, 1.0
'''    
split = ['1', ' 1.0', ' 1.0', '1.0']

for prop, cols, prop_type in key:
    if isinstance(cols, list):
        val = [prop_type(split[col]) for col in cols]
    elif isinstance(cols, int):
        val = prop_type(split[cols])

    print(prop, " = ", val)
$\endgroup$
4
  • $\begingroup$ Thank you for your help on this one, can you tell me what happening in this line- return text.name[-4:].lower() == ".csv" I gather that it is the identifier above the button but I don't understand how the line works? $\endgroup$
    – 3pointedit
    Aug 14, 2017 at 12:00
  • $\begingroup$ only poll (display) the panel if the last 4 letters of name, converted to lower case, are ".csv". Arguably could have used this for the operator too. $\endgroup$
    – batFINGER
    Aug 14, 2017 at 12:08
  • $\begingroup$ So the usable variable for me will be "split"? That will be the csv file contents to use later? $\endgroup$
    – 3pointedit
    Aug 15, 2017 at 8:02
  • $\begingroup$ split is just one line of text editor, split into a list using the "," as a delimiter. Generally would deal with csv data on a line by line basis. Made an edit on one way to use comma split list. Could be a way to use the text editor content (eg text.lines is iterable) as input to csv reader, might put together a q on this. $\endgroup$
    – batFINGER
    Aug 15, 2017 at 10:53
2
$\begingroup$

As explained by batFINGER text blocks have a name and a filepath, but if you just paste data into a text block, you might not have a filepath.

If you don't need the overhead of a custom operator, the following example is a bit more compact:

import bpy
import csv

def csvs():
    return [t for t in bpy.data.texts if t.name[-4:].lower() == ".csv"]

first = csvs()[0]

lines = first.as_string().splitlines() 
#lines = first.lines # those aren't actually strings

f = csv.reader(lines)
for parts in f:
    print(parts)
$\endgroup$
2
  • $\begingroup$ Thanks, was trying csv.reader(first.lines). Never bothered to use csv before. $\endgroup$
    – batFINGER
    Aug 15, 2017 at 14:41
  • $\begingroup$ Thank you both for your help. I really am quite ignorant so I don't understand without lots of commenting. But I will try to work through these answers. Sorry for being dumb about it. $\endgroup$
    – 3pointedit
    Aug 16, 2017 at 0:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .