Skip to content

Commit

Permalink
Merge pull request #3 from theTaikun/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
theTaikun authored Aug 14, 2021
2 parents ee6ed1c + 6ab01ee commit 5bc2682
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 18 deletions.
43 changes: 38 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
# PlotRock
3D Graphing Addon for Blender
# PlotRock - 3D Graphing Addon for Blender

Plotrock is a [Blender](http://www.blender.org) plugin that allows users to create line graphs,
using data from their tables and spreadsheets.

## Features
* Import files from Excel, LibreOffice, Google Sheets, or other similar spreadsheet programs.
* Ability to edit CSV within Blender and regenerate plot.
* Ability to edit .csv file within Blender and regenerate plot.
* Handy sidemenu that allows plot customization.
* Utilize all Blender tools such as materials, lighting, and animated cameras.

https://user-images.githubusercontent.com/43371347/129456528-502482cb-86b4-4a55-a419-97ae086831ed.mp4


## Install
1. Find the latest [release](https://github.com/theTaikun/plotrock/releases).
2. Download the `Source Code` zip.
3. In Blender, navigate to `Edit` => `Preferences` => `Add-Ons`.
4. Click `Install` and select the dowloaded .zip.
5. Now that the addon is installed, click its checkbox to enable it.

## Usage
_PlotRock side menu can be accessed by pressing the N key in the 3D Vieport._

1. Attain data, either from exporting spreadsheet data to .csv, or procuring a .csv file from elsewhere.
2. Import data either from `File`=>`Import`=>`Import CSV for plotting` or from the PlotRock side menu.
1. On the right-hand side of the import menu, double check import settings. By default, imports file with headers, and comma seperated.
3. To edit the data:
1. Open a Text Editor window, and select the imported file.
2. Edit the plot data as needed
3. Click `Update Plot` from the PlotRock side menu.
4. To customize the shape of the plot, select it, and adjust the sliders in the RockPlot menu.
5. Finalize by adding camera(s) and lighting, to taste.

## Limitations
RockPlot is still a work in progress,
and as such,
has some limitations.

* Only handles files formatted as CSV.
* ~~CSVs must actually use commas. Other deliminators not yet supported.~~
* ~~CSVs must actually use commas. Other deliminators not yet supported.~~ Can use either comma or semicolon delimiters.
* Data within CSV has some requirements:
* 2D data, meaning two column. Must be only one Y value for each X value.
* Both X and Y values must be a number.
* Files must have the extension `.csv` or `.txt`.
* ~~Headers not yet supported.~~
* Only one plot per axis.
* Currently only creates line graphs.
7 changes: 6 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
bl_info = {
"name": "PlotRock",
"description": "Create 3D plots from CSV data",
"author": "Isaac Phillips (theTaikun)",
"version": (0,0,1),
"blender": (2, 80, 0),
"version_code": 1, # not used by blender, but keeping track here
"blender": (2, 92, 0),
"location": "File > Import > Import CSV for plotting",
"tracker_url": "https://github.com/theTaikun/plotrock/issues/new",
"category": "Object",
}

Expand Down
141 changes: 139 additions & 2 deletions plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ def convertData(csv_textdata, entry_delimiter=",", has_headers=True):
pos_list = [list(map(float, x)) for x in string_list] # convert list of strings to list of floats
return pos_list, headers

# findRoot function courtesy of MMDTools addon
def findRoot(obj):
if obj:
if obj.plotrock_type == 'ROOT':
return obj
return findRoot(obj.parent)
return None


class NewPlot:
""""
Expand Down Expand Up @@ -78,24 +86,141 @@ def create_curve(self, coords_list):
spline = self.spline
spline.points.add(len(coords_list) -1 )
for i, val in enumerate(coords_list):
spline.points[i].co = (val + [2.0] + [1.0])
spline.points[i].co = (val + [0.0] + [1.0])

# Size of Empty same for all axis => set as max of x and y value
max_x = max(coords_list)[0] # Max of nested list checks first val
max_y = max(coords_list, key=lambda x: x[1])[1] # Funct to check max by second val, and return that val
self.root.empty_display_size = max(max_x, max_y) # compares the 2 maxes

self.root.plotrock_settings.max_x = max_x
self.root.plotrock_settings.max_y = max_y
self.crv.plotrock_csv = self.csv_textdata

def create_obj(self):
print("create new obj")

self.root = bpy.data.objects.new("rockplot_root", None)
self.root.empty_display_type = "ARROWS"
self.root.plotrock_type = "ROOT"
crv = bpy.data.curves.new('crv', 'CURVE')
crv.dimensions = '2D'
crv.extrude = 0.5
crv.bevel_depth = 0.05
crv.plotrock_type="plot"
spline = crv.splines.new(type='POLY')
spline.use_smooth = False
self.crv = crv
self.spline = spline
self.obj = bpy.data.objects.new('object_name', crv)
self.obj.location[2] = 0.5
self.obj.parent = self.root
bpy.data.scenes[0].collection.objects.link(self.obj)
bpy.data.scenes[0].collection.objects.link(self.root)
self.grid = self.create_grid()
self.grid.parent = self.root
bpy.data.scenes[0].collection.objects.link(self.grid)


def create_grid(self):
gridGeo = bpy.data.node_groups.new("gridNodeTree", "GeometryNodeTree")
gridMesh = bpy.data.meshes.new("gridMesh")
gridObj = bpy.data.objects.new("gridObj", gridMesh )
geoModifier = gridObj.modifiers.new("gridGeoNodes", "NODES")
geoModifier.node_group = gridGeo
wireModifier = gridObj.modifiers.new("gridWire", "WIREFRAME")
wireModifier.thickness = 0.125

nodes = gridGeo.nodes

gridGeo.inputs.new("NodeSocketGeometry", "Geometry")
gridGeo.outputs.new("NodeSocketGeometry", "Geometry")
input_node = nodes.new("NodeGroupInput")
input_node.location.x = -700 - input_node.width
input_node.location.y = -100
output_node = nodes.new("NodeGroupOutput")
output_node.is_active_output = True
output_node.location.x = 200


group_input_size= gridGeo.inputs.new("NodeSocketVector", "Size")
group_input_size.default_value=[10,10,0]
# TODO: allow both driver and user modifiable
group_input_size.description = "Custom Grid Size. TEMPORARILY DISABLED"

node = nodes.new("NodeReroute")
node.name = "SIZE_SPLITTER"
node.location.x = -600

node=nodes.new("FunctionNodeInputVector")
node.name = "DRIVER_SIZE"
node.location.x = -700 - node.width
node.location.y = 100
fcurve = node.driver_add("vector")
var = fcurve[0].driver.variables.new()
fcurve[0].driver.expression = "var"
var.targets[0].id = self.root
var.targets[0].data_path = "plotrock_settings.max_x"
var = fcurve[1].driver.variables.new()
fcurve[1].driver.expression = "var"
var.targets[0].id = self.root
var.targets[0].data_path = "plotrock_settings.max_y"

node = nodes.new("GeometryNodeMeshGrid")
node.location.x = -100 - node.width
node.location.y = 200
node.name = "MESH_GRID"

node = nodes.new("GeometryNodeTransform")
node.name = "XLATE_XFORM"

node = nodes.new("ShaderNodeVectorMath")
node.name = "DIV_BY_TWO"
node.location.x = -50 - node.width
node.location.y = -100
node.operation = "DIVIDE"
node.inputs[1].default_value=[2,2,1]

node = nodes.new("ShaderNodeVectorMath")
node.name = "ADD_GRID_LINE"
node.location.x = -450 - node.width
node.location.y = 200
node.operation = "ADD"
node.inputs[1].default_value=[1,1,0]

node = nodes.new("ShaderNodeSeparateXYZ")
node.name = "SepXYZ_size"
node.location.x = -275 - node.width
node.location.y = 250

node = nodes.new("ShaderNodeSeparateXYZ")
node.name = "SepXYZ_verts"
node.location.x = -275 - node.width
node.location.y = 100

# Choose between user-editable modifier input, and automatic driver
#size_input = input_node.outputs[1]
size_input = nodes["DRIVER_SIZE"].outputs[0]


gridGeo.links.new(nodes["SIZE_SPLITTER"].inputs[0], size_input)
gridGeo.links.new(output_node.inputs[0], nodes["XLATE_XFORM"].outputs[0])

gridGeo.links.new(nodes["XLATE_XFORM"].inputs["Geometry"], nodes["MESH_GRID"].outputs[0])
gridGeo.links.new(nodes["XLATE_XFORM"].inputs[1], nodes["DIV_BY_TWO"].outputs[0])
gridGeo.links.new(nodes["SepXYZ_size"].inputs[0], nodes["SIZE_SPLITTER"].outputs[0])
gridGeo.links.new(nodes["DIV_BY_TWO"].inputs[0], nodes["SIZE_SPLITTER"].outputs[0])
gridGeo.links.new(nodes["ADD_GRID_LINE"].inputs[0], nodes["SIZE_SPLITTER"].outputs[0])
gridGeo.links.new(nodes["MESH_GRID"].inputs[0], nodes["SepXYZ_size"].outputs[0])
gridGeo.links.new(nodes["MESH_GRID"].inputs[1], nodes["SepXYZ_size"].outputs[1])

gridGeo.links.new(nodes["MESH_GRID"].inputs[2], nodes["SepXYZ_verts"].outputs[0])
gridGeo.links.new(nodes["MESH_GRID"].inputs[3], nodes["SepXYZ_verts"].outputs[1])

gridGeo.links.new(nodes["SepXYZ_verts"].inputs[0], nodes["ADD_GRID_LINE"].outputs[0])

return gridObj


class UpdatePlot(bpy.types.Operator):
bl_idname = "plotrock.update_plot"
Expand All @@ -122,7 +247,17 @@ def update_curve(self):
spline = self.spline
for i, val in enumerate(self.pos_list):
print("updating point {}".format(i))
spline.points[i].co= (val + [2.0] + [1.0])
spline.points[i].co= (val + [0.0] + [1.0])

def update_axis(self):
coords_list = self.pos_list
# Size of Empty same for all axis => set as max of x and y value
max_x = max(coords_list)[0] # Max of nested list checks first val
max_y = max(coords_list, key=lambda x: x[1])[1] # Funct to check max by second val, and return that val
self.root.empty_display_size = max(max_x, max_y) # compares the 2 maxes

self.root.plotrock_settings.max_x = max_x
self.root.plotrock_settings.max_y = max_y

def execute(self, context):
print("updating")
Expand All @@ -132,10 +267,12 @@ def execute(self, context):
self.csv_textdata = self.crv.plotrock_csv
self.delimiter = self.csv_textdata['delimiter']
self.has_headers = self.csv_textdata['has_headers']
self.root = findRoot(self.obj)

self.pos_list, self.headers = convertData(self.csv_textdata, self.delimiter, self.has_headers)

self.update_curve()
self.update_axis()
return {"FINISHED"}


Expand Down
21 changes: 17 additions & 4 deletions properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,34 @@

type_property= bpy.props.EnumProperty(
name="Type",
description="PlotRock Component Type",
# Warnings meant for end user, not to alter within UI
description="PlotRock Component Type (DO NOT CHANGE)",
default = "NONE",
items=(
("plot", "Plot", "", 1),
("test_prop", "Test Property", "", 2),
("NONE", "None", "", 3)
("plot", "Plot", "DO NOT CHANGE", 1),
("ROOT", "Root", "DO NOT CHANGE", 2),
("NONE", "None", "DO NOT CHANGE", 3)
)
)

class RootSettings(bpy.types.PropertyGroup):
max_x: bpy.props.FloatProperty()
max_y: bpy.props.FloatProperty()

csv_file = bpy.props.PointerProperty(type=bpy.types.Text)

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

bpy.types.Curve.plotrock_type = type_property
bpy.types.Object.plotrock_type = type_property
bpy.types.Curve.plotrock_csv = csv_file
bpy.types.Object.plotrock_settings = bpy.props.PointerProperty(type=RootSettings)

def unregister():
del bpy.types.Curve.plotrock_type
del bpy.types.Object.plotrock_type
del bpy.types.Curve.plotrock_csv
del bpy.types.Object.plotrock_settings

bpy.utils.unregister_class(RootSettings)
5 changes: 5 additions & 0 deletions sample-data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1,1
2,2.7
3,3
4,3
5,6
6 changes: 6 additions & 0 deletions sample-data_headers.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
day,value
1,1
2,2.7
3,3
4,3
5,6
49 changes: 43 additions & 6 deletions ui_panel.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import bpy


class LayoutDemoPanel(bpy.types.Panel):
"""Creates a Panel in the scene context of the properties editor"""
bl_label = "RockPlot Settings"
bl_idname = "OBJECT_PT_layout"
class OperatorPanel(bpy.types.Panel):
bl_label = "Operator"
bl_idname = "OBJECT_PT_plotrock_layout"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ""
Expand Down Expand Up @@ -75,12 +74,50 @@ def draw(self, context):
row.scale_y = 3.0
row.operator("plotrock.update_plot")


class PlotPanel(bpy.types.Panel):
bl_label = "Plot Settings"
bl_idname = "OBJECT_PT_plotrock_plot"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ""
bl_category = "RockPlot"

def draw(self, context):
layout = self.layout
obj = context.active_object

if(obj is not None and obj.type == "CURVE" and obj.data.plotrock_type == "plot"):
split = layout.split()
col=split.column()
col.label(text="Line Shape")

# Line Depth
col=split.column()
col.prop(obj.data, 'extrude', text="Depth")

# Line Width
col.prop(obj.data, 'bevel_depth', text='Width')

row = layout.row()
row.prop(obj.data.splines[0], 'use_smooth')

row = layout.row()
row.prop(obj, 'location', index=2, text="Z-Position")
else:
layout.label(text="Select a Plot")
return



def register():
bpy.utils.register_class(LayoutDemoPanel)
bpy.utils.register_class(OperatorPanel)
bpy.utils.register_class(PlotPanel)


def unregister():
bpy.utils.unregister_class(LayoutDemoPanel)
bpy.utils.unregister_class(OperatorPanel)
bpy.utils.unregister_class(PlotPanel)


if __name__ == "__main__":
Expand Down

0 comments on commit 5bc2682

Please sign in to comment.