I just recently came across this module that simplifies making new addons and python scripts tremendously.
Are any of you using it yet?
I just recently came across this module that simplifies making new addons and python scripts tremendously.
Are any of you using it yet?
personally, I find that âwrappersâ like this just obfuscate what is happening and introduce unnecessary latency or points of failure in a script.
This is generally true. The way these wrappers can shine though is if the API which is being wrapped is bad while also being cemented in place for the rest of its lifetime. Here, the latter does not apply and the former only applies partially. At least, Iâd totally look at the sources of this to use as reference when needed.
For me, an abstraction layer is not a very good idea in this case. A better solution would be more or better documentation of the original API. Letâs say I know by heart half of easyBPY, and find out there it lacks some flexibility at some place. I have to investigate both the wrapper and the Blender API. So I prefer to get familiar with one, which is already quite a task.
I have been thinking, would it be a realistic plan to get a couple of Blender addon developers to band together and start building an unofficial documentation on GitHub? I have learned a lot from asking questions around here which a read of the documentation would never have revealed.
I honestly donât understand when people say they have issues with the documentation, Iâm not really sure what the expectation would be for an improvement? Everything I know is a result of reading that documentation and I canât even remember a time when I wasnât able to find the answer (if an answer existed). The only walls I have run into with the API have been when I want it to do something and itâs not even possible.
Lack of code examples would be my biggest gripe on the API. Beyond that, for it being Python code, it is not very Pythonic.
There are plenty of examples in the API documentation, especially for the more complex modules such as bmesh, bgl, blf, gpu, etc. Things that get asked frequently (even though they are not very complex) such as bpy.ops are also heavily documented with numerous examples and explanations. Are you wanting a detailed usage example for every property and function in the entire API?
Here is a typical example . . . . https://docs.blender.org/api/current/bpy.types.Panel.html These are extremely simple examples.
Right now, I am trying to update some old code, the code runs now, (it did not even run before I started) but some of the buttons do not show. The simple examples on that page are not going to help me at all. Templates has examples, but they are the same type of example, for the properties window. What I mean by it not being âPythonicâ is, Python is breif, concise, and usually not very difficult to code. Ever tried coding bmesh? It is a laughing stock.
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Chart Graphics Generator",
"author": "klibre, PKHG, thanks to JesterKing, Oscurart, Carlos Guerrero",
"version": (0,0,5),
"blender": (2, 6, 3),
"api": 40101,
"category": "Add Curve",
"location": "View3d > Tools",
"description": "Tool to build bar graphics from csv databases,",
"warning": "",
"wiki_url": "",
"tracker_url": "http://www.klibre.net"}
#----------------------------------------------------
import bpy
import csv
from math import pi
import mathutils
import random
import sys
#-------------------globals---------------------------------
pid2 = pi * 0.5
sce = bpy.context.scene
obj = bpy.context.object
bc = bpy.context
boo = bpy.ops.object
boc = bpy.ops.curve
bot = bpy.ops.transform
suma = 0
chartType = '' #not yet used
negativValueSeen = False
sizesCVSfile = None
colnrPKHG = '-1'
factorescala = 5
cvs_colums = 0
allData = None
sce.frame_start = 0
#sce.frame_end = 500
time = 20
origin = bc.scene.cursor.location
#-------------------end globals---------------------------------
def interuptMe(where,debug = True):
if debug:
print(where);
__import__('code').interact(local={k: v for ns in (globals(), locals()) for k, v in ns.items()})
def getCSV(path):
global sizesCVSfile, cvs_colums
result = False
try:
csvfile = open(path, 'r')
dialect = csv.Sniffer().sniff(csvfile.read(1024))
csvfile.seek(0)
reader = csv.reader(csvfile, dialect=dialect)
csvData = []
for data in reader:
csvData.append(data)
result = csvData
cvs_rows = len(result)
cvs_colums = len(result[0])
sizesCVSfile = [cvs_colums, cvs_rows]
csvfile.close()
except:
print("no good csv file adress given!")
self.report({'INFO'}, "no good csv file adress given!")
return result
#
#
#def finalfps():
# if bpy.ops.screen.keyframe_jump(next=True) == true :
#
# bpy.ops.screen.keyframe_jump(next=True)
#
# else :
#
# return {"FINISHED"}
# testpath 'C:\A000\example'
class PANEL_PT_ImportadorUI(bpy.types.Panel):
bl_label = "Generador de Graficas"
bl_idname = "PANEL_PT_ImportadorUI"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
#
def draw(self, context):
global colnrPKHG, allData, cvs_colums, time
layout = self.layout
col = layout.column(align=1)
row = layout.row()
split=row.split()
col.label("Path of the CSV file:")
row.operator("lee.ruta",icon="FILE_SCRIPT")
col = split.column()
col.prop(bc.scene,"importPydataPath")
row = layout.row()
row.prop(bc.scene,"useColumnNr")
colnrPKHG = bc.scene.useColumnNr
path = bc.scene.importPydataPath
check = getCSV(path)
#
if colnrPKHG >= cvs_colums:
print("column ", colnrPKHG," not possible")
elif check:
allData = check
row=layout.row()
split=row.split()
colL0 = split.column()
colL0.label("Graphic Type:")
row = layout.row()
row.prop(bc.scene,"scaleFactor")
factorescala = bc.scene.scaleFactor
row=layout.row()
split=row.split()
colL1 = split.column()
colL1.operator("importador.cubos", icon="MESH_CUBE")
colR1 = split.column()
colR1.operator("importador.cilindros", icon="MESH_CYLINDER")
colL3 = split.column()
colL3.operator("importador.trazos", icon="CURVE_DATA")
colL6 = split.column()
colL6.operator("fabricar.quesito", icon="MESH_CIRCLE")
row=layout.row()
split=row.split()
colL5 = split.column()
#
if suma <= 1:
print("build graphic before")
elif check:
allData = check
colL5.label("Configuration:")
row=layout.row()
split=row.split()
colR2 = split.column()
colR2.operator("calculo.porcentajes", icon="LINENUMBERS_ON")
split=row.split()
colL4 = split.column()
colL4.operator("align.data", icon="FONTPREVIEW")
colL5 = split.column()
colL5.operator("colorear.grafica", icon="MATERIAL")
row=layout.row()
class leeruta (bpy.types.Operator):
bl_idname = "lee.ruta"
bl_label = "Leer"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global path
path = bc.scene.importPydataPath
return {"FINISHED"}
# format data for values
class formatcurves (bpy.types.Operator):
bl_idname = "format.curves"
bl_label = "format curves"
#
def execute(self,context):
# boo.convert(target='CURVE', keep_original=False)
bc.selected_objects[0].data.extrude = 0.01
bc.selected_objects[0].data.bevel_depth = 0.005
return {"FINISHED"}
# importa data, fabica cubos con los datos
class importadorcubos(bpy.types.Operator):
bl_idname = "importador.cubos"
bl_label = "Cubes"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global colnrPKHG, allData, chartType, origin
factorescala = bc.scene.scaleFactor
chartType = 'cubes'
distancia= 0
iescalado = 0
bpy.ops.ver.nombres()
bpy.ops.calculo.total()
offset = bpy.context.scene.cursor_location.copy() #scris de mine dupa printscreenuri
for data in allData:
distancia += 3
escalando = float(data[colnrPKHG])/factorescala
bpy.context.scene.cursor_location = (offset[0],offset[1]+2,offset[2])#scris de mine dupa printscreenuri
if escalando >= 0:
boo.text_add(location=((origin[0] + distancia),(origin[1]+1.5),(origin[2]+((escalando)*2)+0.2)),rotation=(pid2, 0, 0))
boo.editmode_toggle()
bpy.ops.font.delete()
bpy.ops.font.text_insert(text=data[colnrPKHG], accent=False)
boo.editmode_toggle()
bpy.ops.mesh.primitive_cube_add(location=((origin[0]+distancia),(origin[1]+1.5),(origin[2]+escalando)))
bot.resize(value=(1,1,escalando))
xmat = random.uniform(0.05,0.95)
ymat = random.uniform(0.05,0.95)
zmat = random.uniform(0.05,0.95)
colordifuso = bpy.data.materials.new('RandomColor')
colordifuso.diffuse_color = (xmat,ymat,zmat)
colordifuso.specular_color = ((xmat+0.4),(ymat+0.4),(zmat+0.4))
colordifuso.specular_intensity = (0.7)
bc.object.data.materials.append(colordifuso)
boo.select_all(action='DESELECT')
else:
negativValueSeen = True
self.report({'WARNING'}, "negative value occured")
print("error: not meaningfull with negativ values")
return {"CANCELLED"}
#
return {"FINISHED"}
# importa data, fabica cilindros y los textcurves con la data
class importadorcilindros (bpy.types.Operator):
bl_idname = "importador.cilindros"
bl_label = "Cylinders"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global colnrPKHG, allData, chartType, origin
factorescala = bc.scene.scaleFactor
chartType = 'cylinders'
distancia= 0
iescalado = 0
bpy.ops.ver.nombres()
bpy.ops.calculo.total()
offset = bpy.context.scene.cursor_location.copy() #scris de mine dupa printscreenuri
#
for data in allData:
distancia += 3
escalando = float(data[colnrPKHG])/factorescala
bpy.context.scene.cursor_location = (offset[0],offset[1]+2,offset[2])#scris de mine dupa printscreenuri
if escalando >= 0:
boo.text_add(location=((origin[0] + distancia),(origin[1]+1.5),(origin[2]+((escalando)*2)+0.2)),rotation=(pid2, 0, 0))
boo.editmode_toggle()
bpy.ops.font.delete()
bpy.ops.font.text_insert(text=data[colnrPKHG], accent=False)
boo.editmode_toggle()
bpy.ops.mesh.primitive_cylinder_add(location=((origin[0]+distancia+1),(origin[1]+1.5),(origin[2]+escalando)))
bot.resize(value=(1,1,escalando))
xmat = random.uniform(0.05,0.95)
ymat = random.uniform(0.05,0.95)
zmat = random.uniform(0.05,0.95)
colordifuso = bpy.data.materials.new('RandomColor')
colordifuso.diffuse_color = (xmat,ymat,zmat)
colordifuso.specular_color = ((xmat+0.4),(ymat+0.4),(zmat+0.4))
colordifuso.specular_intensity = (0.7)
bc.object.data.materials.append(colordifuso)
boo.select_all(action='DESELECT')
else:
negativValueSeen = True
self.report({'WARNING'}, "negative value occured")
print("error: not meaningfull with negativ values")
return {"CANCELLED"}
#
return {"FINISHED"}
# muestra la suma total de los elementos
class calculototal (bpy.types.Operator):
bl_idname = "calculo.total"
bl_label = "Total"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global colnrPKHG, allData, suma , negativValueSeen, factorescala, origin
negativValueSeen = False #assume OK at start
suma = 0
offset = bpy.context.scene.cursor_location.copy()
for data in allData:
bpy.context.scene.cursor_location.copy()
tmp = float(data[colnrPKHG])
if tmp < 0:
negativValueSeen = True
self.report({'WARNING'}, "negative value occured")
print("error: not meaningfull with negativ values")
return {"CANCELLED"}
suma += tmp
sumados ="total:" + str(suma)
boo.text_add(location=((origin[0]-2.5),origin[1]+2,(origin[2] + 3)),rotation=(pid2, 0, 0))
boo.editmode_toggle()
bpy.ops.font.delete()
bpy.ops.font.text_insert(text=(sumados), accent=False)
boo.editmode_toggle()
bpy.ops.format.curves()
sce.frame_end = suma + 30
boo.select_all(action='DESELECT')
return {"FINISHED"}
class calculoporcentajes (bpy.types.Operator): # calcul procentaj
bl_idname = "calculo.porcentajes"
bl_label = "percentages"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global colnrPKHG, allData, suma, negativValueSeen, factorescala, origin
distancia= 0
bpy.ops.calculo.total()
offset = bpy.context.scene.cursor_location.copy()
print("\n?????? negativValueSeen", negativValueSeen)
if negativValueSeen:
self.report({'WARNING'}, "negative value occured")
return {'CANCELLED'}
for data in allData:
bpy.context.scene.cursor_location = (offset[0],offset[1]+2,offset[2])
distancia += 3
sumando = float(data[colnrPKHG])
if sumando >= 0:
porciento = '{:.2%}.'.format(sumando/suma)
escalado = float(data[colnrPKHG])/factorescala
boo.text_add(location=((origin[0]+0.5 + distancia),(origin[1]-3),(origin[2] + (escalado*2)+0.5)),rotation=(pid2, 0, 0))
boo.editmode_toggle()
bpy.ops.font.delete()
bpy.ops.font.text_insert(text=(porciento), accent=False)
boo.editmode_toggle()
bot.resize(value=(0.6,0.6,0.6))
bpy.ops.format.curves()
boo.select_all(action='DESELECT')
else:
print("negativ value for percentage not meaningful+ ")
return {"FINISHED"}
# importa data, fabica cilindros y los textcurves con la data
class vernombres (bpy.types.Operator):
bl_idname = "ver.nombres"
bl_label = "Names"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
global factorescala, origin
path = bc.scene.importPydataPath
reader = csv.reader(open(path, 'r'))
distancia= 0
offset = bpy.context.scene.cursor_location.copy()
#separacion de las cajitas
for data in reader:
bpy.context.scene.cursor_location = (offset[0],offset[1]+2,offset[2])
distancia += 3
data[0]=data[0].replace(' ', '\n')
boo.text_add(location=((origin[0]+0.2 + distancia),origin[1]+4,(origin[2]+0)),rotation=(pid2, 0, 0))
boo.editmode_toggle()
bpy.ops.font.delete()
bpy.ops.font.text_insert(text=data[0], accent=False)
boo.editmode_toggle()
bot.resize(value=(0.6,0.6,0.6))
bpy.ops.format.curves()
boo.select_all(action='DESELECT')
return {"FINISHED"}
# asigna un material a la grafica azar
# Material by http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Cookbook/Code_snippets/Materials_and_textures
class coloreargrafica (bpy.types.Operator):
bl_idname = "colorear.grafica"
bl_label = "Paint it!"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
for ob in bpy.data.objects:
if ob.type == 'CURVE' or ob.type == 'MESH':
bpy.ops.object.material_slot_copy()
else:
self.report({'INFO'}, "Please. select any object before")
return{'FINISHED'}
# Align data to camera
class aligncurvestocamera (bpy.types.Operator):
bl_idname = "align.data"
bl_label = "Align data"
bl_options = {"REGISTER", "UNDO"} # %% ADDED
#
def execute(self,context):
boo.select_by_type(type='FONT')
if bc.selected_objects[0].type == 'FONT':
boo.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
boo.visual_transform_apply()
bot.select_orientation(orientation='VIEW')
bot.transform(mode='ALIGN', constraint_orientation='VIEW')
boo.select_all(action='DESELECT')
#
# else:
# message = "You have to build data before"
# self.report({'WARNING'}, message)
#
return{'FINISHED'}
#-----REGISTROS------------------------
def register():
bpy.utils.register_class(PANEL_PT_ImportadorUI)
bpy.utils.register_class(leeruta)
bpy.utils.register_class(formatcurves)
bpy.utils.register_class(importadorcubos)
bpy.utils.register_class(importadorcilindros)
bpy.utils.register_class(calculototal)
bpy.utils.register_class(calculoporcentajes)
bpy.utils.register_class(vernombres)
bpy.utils.register_class(coloreargrafica)
bpy.utils.register_class(aligncurvestocamera)
bpy.types.Scene.importPydataPath=bpy.props.StringProperty(default="Insert absolute path")
bpy.types.Scene.useColumnNr=bpy.props.IntProperty(name = "Nr. of column to use",min = 1, max = 100, soft_max = 100, default = 1) #soft_max numar de grafice ce pot aparea
bpy.types.Scene.scaleFactor=bpy.props.IntProperty(name = "Scale factor",min = 1, max = 1000, soft_max = 100, default = 10)
#
## REGISTRA CLASSES
burc = bpy.utils.register_class
def unregister():
bpy.utils.unregister_class(PANEL_PT_ImportadorUI)
bpy.utils.unregister_class(leeruta)
bpy.utils.unregister_class(formatcurves)
bpy.utils.unregister_class(importadorcubos)
bpy.utils.unregister_class(importadorcilindros)
bpy.utils.unregister_class(calculototal)
bpy.utils.unregister_class(calculoporcentajes)
bpy.utils.unregister_class(vernombres)
bpy.utils.unregister_class(coloreargrafica)
bpy.utils.unregister_class(aligncurvestocamera)
burc = bpy.utils.unregister_class
if __name__ == "__main__":
register()
OK, then going back to @Zylâs question
I donât know what examples I could write that would help someone who only recently started using Blender port an 8 year old addon to the most recent version. Youâre in âspecific questionâ territory with that one.
I do, daily. Whom is laughing about bmesh? is it the people wondering how itâs being used to crank out groundbreaking blender addons that are still not entirely replicated in other DCC packages? Hardops, Box Cutter, Meshmachin3, Decalmachine, Kit Ops, the list goes on- these all use bmesh. Is it hard to learn? Thatâs debatable but Iâll give it to you. A laughing stock though? Come on nowâŚ
I found the issue in the code I posted.
useColumnNr
more specifically:
bpy.context.scene.useColumnNr
Did a Google search on âuseColumnNrâ, which should bring up anything in the API back to even some early APIâs . . . . It IS in blender, you can look it up in the console. This is what I got back:
By the way, I have been using Blender for years, before version 2.64, and before that, I was scripting using MEL in MAYA. Have been writing code since the early 90âs, so if that is âsomeone who only recently started using Blenderâ I guess that makes you more of a dinosaur than I am . . . .
The reason you canât find âuseColumnNrâ is because it doesnât exist- in this version of Blender or any other. itâs not part of Blenderâs API, it is defined and used only in that script you linked.
Try it yourself:
bpy.types.Scene.myvar = bpy.props.IntProperty('test', default=42)
print(bpy.context.scene.myvar)
Incidentally, this behavior of adding attributes to existing classes is thoroughly explained in the documentation.
In the context of this conversation, I was referring to your having just started using python in Blender. Porting an 8 year old addon when you are not already experienced with the API is going to be a difficult task no matter what your background is.
I did NOT just start writing Python code in Blender, have been doing it for years. And that DOES exist in Blender 2.91, which I am currently running, and, as I said, try looking it up in the console, and you will find it there. Open up the console, at the command line, put in this, with the period at the end as shown, then press your TAB button . . .
bpy.context.scene.useColumnNr.
You can also use:
dir(bpy.context.scene.useColumnNr)
As shown below, and this IS Blender 2.91 I am running.
I just explained that it doesnât exist, and more importantly WHY it doesnât exist. It exists on your machine because youâre running an addon that has created it. It does not exist in the Blender API, and therefore cannot be found in the official documentation. Not sure how else to spell this one out.
This is not a plugin de facto, I have not installed it as such, and I wouldnât anyway, because it is so old. I am strictly running it in the text editor, and the console. If that creates it, when it should not exist, and there is no code history that tells me that it is no longer in use, that kind of makes my point right there anyway.
I see how it was created. I get it now. I have created my own before, but was busy for a long time studying for the CCNA, so I did forget a lot. I am no newby, and you should not be making such assumptions. It makes it all the more difficult to communicate when that happens, because it adds to the feeling that the person you are communicating with is NOT listening, and then it snowballs from there.
moot point⌠dropping
Good idea. If you ask about something and want to know what can improve it, then do not want to listen, there is not much more I can say about it.
I was thinking more along the lines of- why are we bothering discussing documentation when itâs crystal clear that itâs not being read? So youâre right, not much more to be said about it