ReportLab variable nextPageTemplate's - python
I am in the process of converting the database from pfaf.org (plant for a future) into a pdf book.
I have hit a bit of a stumbling block regarding pageTemplates.
Each plant may begin on a left or a right aligned page; and may potentially be two or more pages.
I have two templates for the first plant page (left and right) and also two more templates for the potential subsequent pages.
Currently this is handled as follows (for instance):
for i, plant in enumerate(plants):
#the first plant is printed on a right-hand page
if i % 2 == 0:
template = 'left'
second = 'right'
else:
template = 'right'
second = 'left'
Story.append(nextPageTemplate(template,'*', 'secondary_'+template, 'secondary_'+second))
Story.append(PageBreak())
#append various paragraphs, jumping between frames, etc...
The code (as you can probably tell) works fine for single page plants.
It also works (semi) as expected for multi page plants.
However, as you can also probably see, a two (or four,etc) page plant will break the template arrangement, because the above code assumes page position based upon plant number, rather than page number.
I can't see a solution for this in the location of the code above (i.e during the Story.append cycle) - as at that point I can not tell whether the plant has used more than one page, and thus what page I am currently on, as such.
I hoped that I could perhaps tweak the nextPageTemplate structure from my custom docTemplate, but I can't figure out if this is possible.
Is it? Or is there another solution? Would really appreciate any help. Have been reading all over, but the best examples I can find don't quite cover this scenario.
Any questions, please ask.
Thank you
Thank you, Nitzle:
The trouble is that I don't know how many pages each plant will take up.
For instance - a new plant starts on an odd page so I give it a cycle of templates ('right', '*', 'secondaryLeft', 'secondaryRight'). [the secondary pages are just a single frame with appropriate margin.]
If that plant is one page long, no problem, the next plant will have the template cycle opposite to the above.
However, if the plant has, say, two pages, it will cause the following plant to fall again on an odd page again and thus the template cycle should not change... I hope this makes sense.
This is the circumstance I am having trouble solving... If I do as you say, it doesn't allow for multiple page plants. Most of my code is as follows; I have tried to slim it down a little though :) hopefully it still contains all relevant stuff and not too much unnecessary.
import os
import sys
import MySQLdb
from reportlab.platypus import Spacer, Image, Table, TableStyle, PageBreak, FrameBreak, paraparser
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, NextPageTemplate, _doNothing
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.platypus.frames import Frame
from reportlab.platypus.flowables import KeepInFrame
from reportlab.platypus.paragraph import Paragraph
from reportlab.lib.units import mm, cm
from reportlab.lib.pagesizes import A4, A5
from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_RIGHT
from reportlab.lib.styles import StyleSheet1, ParagraphStyle as PS
from reportlab.lib import colors
from reportlab.graphics.shapes import Drawing, Rect, String
from reportlab.pdfbase.pdfmetrics import registerFont, stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import warnOnMissingFontGlyphs
warnOnMissingFontGlyphs = 0
registerFont(TTFont('Museo_', '/home/wuwei/.fonts/Museo300-Regular.ttf'))
registerFont(TTFont('Museo_M', '/home/wuwei/.fonts/Museo500-Regular.ttf'))
registerFont(TTFont('Trebuchet', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS.ttf'))
registerFont(TTFont('Trebuchet_I', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS_Italic.ttf'))
## SOME VARIABLE DEFINITIONS ##
titleFont = "Museo_M"
subtitleFont = "Museo_"
stdFont = "Trebuchet"
stdItalic = "Trebuchet_I"
#stdSize = 14
"""CREATE GLOBALS"""
_w, _h = A4
_head_w = 17.5*cm
_head_pad = 0.2*cm
_main_w = 17.2*cm
_budge = 0.3*cm
_left_margin = 1.5*cm
_right_margin = 2.0*cm
_top_margin = 1.5*cm
_bottom_margin = 2.0*cm
_latinFontS = 18
#reset superFraction to style 'common name' placement
paraparser.superFraction = 0.15
paraparser.sizeDelta = 0
###########################################################################################################
######################################### ###############################################
######################################## DB FUNCTIONS #############################################
######################################### ###############################################
###########################################################################################################
def connectToDB():
try:
connection = MySQLdb.connect (host = "localhost",
user = "root",
passwd = "****************",
db = "pfaf")
except MySQLdb.Error, e:
print "I guess, either you don't have a local copy of the pfaf db"
print "or something is wrong with your connection details."
print "Error %d: %s" % (e.args[0], e.args[1])
sys.exit (1)
return connection
def close(item, exit=0):
#used to close both database cursors and connections
item.close()
if exit == 1:
sys.exit (0)
def runQuery(q, conn):
results = conn.cursor(MySQLdb.cursors.DictCursor)
results.execute (q)
return results
def Fetch(results, fetchAll=0):
if fetchAll:
print "fetchAll"
# FETCHALL option:
rows = results.fetchall()
#cursor.close()
#conn.close()
'''for row in rows:
print "%s, %s" % (row["Latin Name"], row["Hardyness"])
print "%d rows were returned" % results.rowcount'''
return rows
else:
# FETCHONE option:
##--- Print some debug info to command line ---##
print "Latin Name - Common Name - Hardyness"
while (1):
row = results.fetchone()
if row == None:
break
latin_name = row["Latin Name"]
common_name = row["Common name"]
hardyness = row["Hardyness"]
family = row["Family"]
synonyms = row["Synonyms"]
##--- Print some more useful debug info to command line ---##
print "%s - %s - %s" % (latin_name, common_name, hardyness)
print row
if results.rowcount != 1:
print "%d rows were returned" % results.rowcount
else:
print "%d row was returned" % results.rowcount
return row
###########################################################################################################
######################################### ###############################################
######################################## STORY PROCESSING #############################################
######################################### ###############################################
###########################################################################################################
def drawBorders(canv, side):
canv.saveState()
d = Drawing(0,0)
#header border#
r = Rect( side-_budge, _h-(2.4*cm), _head_w+(_budge*2), 1.2*cm, rx=5, ry=5 )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 1.5
d.add(r)
#hardyness border#
rad = 5
hWidth = 1.4*cm
if side == _left_margin:
hPos = -rad
else:
hPos = _w - hWidth + rad
r = Rect( hPos, _h-(3.8*cm), hWidth, 1.2*cm, rx=rad, ry=rad )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 1.5
d.add(r)
d.drawOn(canv, 0, 0)
canv.restoreState()
def drawFooter(canv, doc):
canv.saveState()
canv.setFont(stdFont,10)
canv.drawCentredString((_w/2.0), 1.5*cm, "%d - %s" % (doc.page, doc.latinName))
canv.restoreState()
class LeftPageTemplate(PageTemplate):
def __init__(self):
#allow a bigger margin on the right for binding
latinF = Frame(_left_margin, 27.5*cm, _head_w, 0.8*cm, id='latinL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
hardyF = Frame(0.1*cm, 26.05*cm, cm, cm, id='hardL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
synF = Frame(_left_margin, 26.65*cm, _main_w, 0.55*cm, id='synL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
otherF = Frame(_left_margin, 22.1*cm, 12.4*cm, 4.5*cm, id='otherL', showBoundary=1)
calF = Frame(14.2*cm, 22.1*cm, 4.5*cm, 4.5*cm, id='calL', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
flowF = Frame(_left_margin, 2.0*cm, _main_w, 19.85*cm, id='flowL', showBoundary=1)
PageTemplate.__init__(self,
id='left',
frames=[latinF, hardyF, synF, otherF, calF, flowF],
pagesize=A4)
def beforeDrawPage(self, canv, doc):
drawBorders(canv, _left_margin)
def afterDrawPage(self, canv, doc):
drawFooter(canv, doc)
class RightPageTemplate(PageTemplate):
def __init__(self):
#allow a bigger margin on the left for binding
latinF = Frame(_right_margin, 27.5*cm, _head_w, 0.8*cm, id='latinR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
hardyF = Frame(_w-1.1*cm, 26.05*cm, cm, cm, id='hardR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
synF = Frame(_right_margin+_budge, 26.65*cm, _main_w, 0.55*cm, id='synR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
calF = Frame(_right_margin+_budge, 22.1*cm, 4.5*cm, 4.5*cm, id='calR', showBoundary=0,
rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
otherF = Frame(_right_margin+5.1*cm, 22.1*cm, 12.4*cm, 4.5*cm, id='otherR', showBoundary=1)
flowF = Frame(_right_margin+_budge, 2.0*cm, _main_w, 19.85*cm, id='flowR', showBoundary=1)
PageTemplate.__init__(self,
id='right',
frames=[latinF, hardyF, synF, otherF, calF, flowF],
pagesize=A4)
def beforeDrawPage(self, canv, doc):
drawBorders(canv, _right_margin)
def afterDrawPage(self, canv, doc):
drawFooter(canv, doc)
class MyDocTemplate(BaseDocTemplate):
_invalidInitArgs = ('pageTemplates',)
def __init__(self, filename, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
self.latinName = "(none initially)"
self.latinWidth = 0 #(none initially)
def afterInit(self):
self._calc() #in case we have changed margin sizes etc
self.leftMargin = _left_margin
self.rightMargin = _right_margin
self.topMargin = _top_margin
self.bottomMargin = _bottom_margin
self.width = _w - self.leftMargin - self.rightMargin
self.height = _h - self.topMargin - self.bottomMargin
frameStd = Frame(cm, self.bottomMargin, (_w - 2*cm), (_h - 3*cm), id='cvr', showBoundary=0)
frameToC = Frame(self.rightMargin, self.bottomMargin, self.width, self.height, id='tocFrame', showBoundary=0)
frameL = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secLeftFrame', showBoundary=1)
frameR = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secRightFrame', showBoundary=1)
self.addPageTemplates( [PageTemplate(id='Cover', frames=frameStd, onPage=coverPage, pagesize=self.pagesize),
PageTemplate(id='ToC', frames=frameToC, onPage=tocPage, pagesize=self.pagesize),
PageTemplate(id='blank', frames=frameStd, onPage=_doNothing, pagesize=self.pagesize),
LeftPageTemplate(),
RightPageTemplate(),
PageTemplate(id='secondary_left', frames=frameL, onPage=_doNothing, pagesize=self.pagesize),
PageTemplate(id='secondary_right', frames=frameR, onPage=_doNothing, pagesize=self.pagesize)
] )
def afterFlowable(self, flowable):
"""Registers ToC entries - and captures latin name for footer"""
if isinstance(flowable, Paragraph):
style = flowable.style.name
key = None
firstWord = style.split('_',1)[0]
if (style == 'LatinName') or (style == 'LatinNameR') or (firstWord == 'LatinName'):
level = 0
key = 'latin-%s' % self.seq.nextf('LatinName')
self.canv.bookmarkPage(key)
wholeStr = flowable.getPlainText()
if self.page % 2 == 0: #even numbers are on left pages
latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[0] #looks for '  ' as divider
else:
latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[1]
self.latinName = latinOnly
E = [level, latinOnly, self.page]
if key is not None: E.append(key)
self.notify('TOCEntry', tuple(E))
'''elif (style == 'CommonName'):
self.commonName = flowable.getPlainText()
self.commonWidth = stringWidth(self.commonName, styles['common'].fontName, styles['common'].fontSize)'''
else:
return
""" coverPage and otherPages are intended for non-flowing (i.e standard) parts of the pages """
def coverPage(canvas, doc):
Title = "Plants for a Future"
pageinfo = "The full database collected as a printable book"
canvas.setTitle(Title + " : " + pageinfo)
print "creating cover page..."
canvas.saveState()
d = Drawing(0,0)
r = Rect( 0, 0, 12*cm, 4*cm, rx=5, ry=5 )
r.strokeColor = colors.black
r.fillColor = colors.white
r.strokeWidth = 3
d.add(r)
d.drawOn(canvas, (_w/2.0)-6*cm, _h-(6.2*cm))
canvas.setFont(stdFont, 30)
canvas.drawCentredString(_w/2.0, _h-108, Title)
canvas.setFont(stdItalic, 14)
canvas.drawCentredString(_w/2.0, _h-150, pageinfo)
canvas.restoreState()
def tocPage(canvas, doc):
canvas.saveState()
canvas.setFont(stdFont,10)
canvas.drawCentredString((_w/2.0), 1.5*cm, "Table of Contents")
canvas.restoreState()
def getMedicinal(plant):
p = plant
initial = p["Medicinal"]
return initial
""" Run after 'Story' has been fully populated """
def go():
doc = MyDocTemplate('result01.pdf')
passes = doc.multiBuild(Story)
########################################################################
"""Build StyleSheet"""
styles = buildStyle()
h1 = PS(name = 'HeadingOne',
fontName = stdFont,
fontSize = 14,
leading = 16)
h2 = PS(name = 'HeadingTwo',
fontName = stdFont,
fontSize = 12,
leading = 14,
leftIndent = 1*cm)
Story=[]
a = Story.append
a(NextPageTemplate('blank'))
a(PageBreak())
a(NextPageTemplate('ToC'))
a(PageBreak())
toc = TableOfContents()
toc.levelStyles = [ h1, h2 ]
a(toc)
a(NextPageTemplate('blank'))
a(PageBreak())
"""###LEFT PAGES SHOULD BE STYLED RIGHT-ALIGNED, AND RIGHT PAGES LEFT-ALIGNED###"""
#print type(plants)
for i, plant in enumerate(plants):
### THIS INITIAL CHECK BREAKS AS IT NEEDS TO BE BASED ON PAGE NUMBER, NOT PLANT NUMBER!!! ###
if i %2 == 0: #IF FIRST PLANT APPEARS ON A RIGHTSIDE PAGE, ELSE REVERSE THE R and L
page='R'
template = 'right'
second = 'left'
else:
page='L'
template ='left'
second = 'right'
#FIRST THINGS FIRST:
#Make sure the page templates flow nicely for each plant "chapter"
a(NextPageTemplate([template, '*', ('secondary_'+template), ('secondary_'+second) ]))
a(PageBreak())
'''CAPTURE PLANT INFO IN OBJECTS'''
p = plant
'''for info in plant:
print info, p[info]'''
'''Header'''
latin = p["Latin Name"]
common = p["Common name"]
family = p["Family"]
syn = p["Synonyms"]
"""X. congestum. (Lour.)Merrill. X. racemosum. Miq. Apactis japonica. Croton congestum.
Flacourtia japonica. Walp. Hisingera japonica. H. racemosa."""
hardy = str(p["Hardyness"])
'''Basic Info'''
author = p["Author"]
botanicalrefs = p["Botanical references"]
width = p["Width"]
height = p["Height"]
habit = p["Habit"]
planttype = clean("Deciduous/Evergreen", p)
plantrange = p["Range"]
habitat = p["Habitat"]
soil = clean("Soil", plant)
shade = p["Shade"]
moisture = p["Moisture"]
drained = p["Well-drained"]
nf = p["Nitrogen fixer"]
pH = p["pH"]
acid = p["Acid"]
alkaline = p["Alkaline"]
saline = p["Saline"]
wind = p["Wind"]
rate = clean("Growth rate", plant)
pollution = p["Pollution"]
poorsoil = p["Poor soil"]
drought = p["Drought"]
heavyclay = p["Heavy clay"]
tender = clean("FrostTender", plant)
inleaf = p["In leaf"]
flowering = p["Flowering time"]
seedripens = p["Seed ripens"]
flowertype = p["Flower Type"]
pollinators = p["Pollinators"]
selffertile = clean("Self-fertile", plant)
hazards = p["Known hazards"]
rating_edible = p["Rating"]
rating_med = p["Medicinal Rating"]
edibleuses = p["Edible uses"]
medicinaluses = getMedicinal(plant)
otheruses = p["Uses notes"]
#the following encoding allows for special characters such as degree symbol
cultivation = unicode(p["Cultivation details"], 'latin-1')#'ISO-8859-1')
propagation = p["Propagation 1"]
scented = p["Scented"] #boolean - requires further lookup in `ScentedPlants` table
string = '''%s is %s %s growing to %gm by %gm at a %s rate.<br/>
It's habitats are %s <br/><br/> Range: %s
<br/><br/>
Suitable for %s soils. <br/><br/>
Shade: %s, Moisture: %s <br/>
Well-drained: %d, Nitrogen fixer: %d <br/> ph: %s <br/>
Acid: %d, Alkaline: %d, Saline: %d <br/>
Wind: %s
<br/><br/>
Author: %s <br/> Botanical References: %s''' % (
latin, planttype, habit.lower(), width, height, rate,
habitat[0].lower()+habitat[1:], plantrange,
soil, shade, moisture, drained,
nf, pH, acid, alkaline, saline, wind, author, botanicalrefs )
string = unicode(string, 'latin-1')
latinW = stringWidth(latin, styles['latin'].fontName, styles['latin'].fontSize)
commonW = stringWidth(common, styles['common'].fontName, styles['common'].fontSize)
if (latinW + commonW + (_head_pad*3)) > _head_w:
styleName = "LatinName_" + str(i)
latinStyle = PS( name=styleName,
parent=styles['Normal'],
fontName=titleFont,
fontSize=_latinFontS,
leading=22,
spaceAfter=0)
j = 1
#should the latin string be too long, attempt to shrink until it fits
while (latinW + commonW + (_head_pad*3)) > _head_w:
#change the font size until ok...
latinStyle.fontSize = _latinFontS -j
latinW = stringWidth(latin, latinStyle.fontName, latinStyle.fontSize)
j += 0.2
else:
latinStyle = styles['LatinName']
if page == 'L':
headerText = '''<para align="left">
%s
<font face="%s" size="%d"> <super>%s</super></font>
</para>''' % (latin, subtitleFont, 12, common)
else:
headerText = '''<para align="right">
<font face="%s" size="%d"><super>%s</super> </font>
%s
</para>''' % (subtitleFont, 12, common, latin)
latinPara = Paragraph(headerText, latinStyle)
a(FrameBreak('latin'+page))
a(latinPara)
a(FrameBreak('syn'+page))
a(KeepInFrame(_main_w, 1.5*cm,
[Paragraph(syn, styles['syn'+page])],
mode="shrink")) #can be shrink, truncate or overflow
a(FrameBreak('hard'+page))
a(Paragraph(hardy, styles['hardy']))
a(FrameBreak('cal'+page))
#SHALL BE ULTIMATELY POPULATED VIA DATABASE#
greyOut = [ [0,0,1,1,1,1,1,0,0,0,0,0], [0,0,0,0,0,1,1,1,1,0,0,0], [0,0,0,0,0,0,0,0,1,1,1,0] ]
cal = drawCalendar(greyOut)
a(cal)
a(FrameBreak('flow'+page))
a(Paragraph(string, styles['Normal']))
a(Paragraph("Edible Uses", styles['title']))
a(Paragraph("Medicinal Uses", styles['title']))
a(Paragraph("Other Uses", styles['title']))
a(Paragraph("Cultivation", styles['title']))
a(Paragraph(cultivation, styles['Normal']))
a(Paragraph("Propagation", styles['title']))
a(Paragraph(propagation, styles['Normal']))
##ASSEMBLE PDF###
go()
If you're just switching between a "left" and a "right" template, you could try using the _handle_nextPageTemplate method of the BaseDocTemplate class. One way to keep track of page number would be to use the afterPage hook to increment a page number.
from reportlab.platypus import BaseDocTemplate
class MyDocTemplate(BaseDocTemplate):
def __init__(self, *args, **kwargs):
BaseDocTemplate.__init__(self, *args, **kwargs)
self.__pageNum = 1
def afterPage(self):
"""Called after all flowables have been drawn on a page"""
# Increment pageNum since the page has been completed
self.__pageNum += 1
# If the page number is even, force "left-side" template
if self.__pageNum % 2 == 0:
self._handle_nextPageTemplate('left_template')
else:
self._handle_nextPageTemplate('right_template')
I haven't tested the code above, but you may need to use beforePage instead depending on how it checks page template order.
Related
Reportlab - How to add margin between Tables?
So i am trying to create three tables per page, the following code will collide all three tables together with 0 margin between them. I would like some white space between two tables. Is there a configuration for that? doc = SimpleDocTemplate("my.pdf", pagesize=A4) elements = [] i = 0 for person in persons: data = get_data() t = Table(data, colWidths=col_widths, rowHeights=row_heights) elements.append(t) i = i + 1 if i % 3 == 0: elements.append(PageBreak()) doc.build(elements)
You could try using the Spacer function to add space between the tables. An example of its use from the documentation is: from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer def go(): doc = SimpleDocTemplate("hello.pdf") Story = [Spacer(1,2*inch)] for i in range(100): bogustext = ("This is Paragraph number %s. " % i) *20 p = Paragraph(bogustext, style) Story.append(p) Story.append(Spacer(1,0.2*inch)) doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)
Python PPTX table.cell color
So guys here's my problem I would like to fill my table cells with different colors from the default ones... I've checked the docs and made multiple searches on google but couldn´t find something helpful. Here's my code: def create_default_slide(user, ppt, shapes, experience_text, skills): max_height = Inches(ppt.slide_height.inches - kBASE_BOTTOM_INCHES) height = Inches(kBASE_TOP_INCHES) left = Inches(0) top = Inches(.1) shapes.add_picture(kASSETS_DIRECTORY + "ppt_softinsa_header.png", left, top, height=height, width=ppt.slide_width) # shapes.title.text = "curriculum vitae – Resource {}".format(1) title_box = shapes.add_textbox(left=Inches(0.5), top=Inches(kBASE_TOP_INCHES * 1.5), width=ppt.slide_width, height=Pt(px_to_pt(50))) title_box.text = u'Curriculum Vitae – {}'.format(user.name) info_table = shapes.add_table(rows=3, cols=2, left=Inches(.2), top=Inches(kBASE_TOP_INCHES * 3), width=200, height=200).table # set table properties info_table.first_row = False info_table.horz_banding = False info_table.vert_banding = True # set column widths info_table.columns[0].width = Inches(1.4) info_table.columns[1].width = Inches(3) rows_number = len(info_table.rows) user_info = user.basic_info() for i in range(rows_number): info_table.cell(i, 0).text = kINTRODUCTION_COLUMN[i] info_table.cell(i, 1).text = user_info[i] # sets the font size for the content info of the table info_cell = info_table.rows[i].cells[1] info_cell.text_frame.paragraphs[0].font.size = Pt(kCELL_INFO_FONT_SIZE) experiences_table = shapes.add_table(rows=2, cols=1, left=Inches(5), top=Inches(kBASE_TOP_INCHES * 3), width=200, height=Inches(9.9).).table # set table dimensions experiences_table.columns[0].width = Inches(4.7) experiences_table.rows[0].height = Inches(kTABLE_HEADER_INCHES) # set cell font size experience_title_cell = experiences_table.rows[0].cells[0] experience_cell = experiences_table.rows[1].cells[0] experience_cell.text_frame.paragraphs[0].font.size = Pt(kCELL_INFO_FONT_SIZE) # set header # "Professional Experience" experiences_table.cell(0, 0).text = u"Experiência Profissional" import re expr = re.compile(ur'- .+ até [^\n]+\n') for experience_item in experience_text: if expr.search(experience_item): lines = experience_item.split('\n') paragraph = experiences_table.cell(1, 0).text_frame.paragraphs[0] bold_run = paragraph.add_run() bold_run.font.bold = True bold_run.text = lines[0] + '\n' rest_run = paragraph.add_run() rest_run.font.bold = False rest_run.text = '\n'.join(lines[1:]) + '\n' else: experiences_table.cell(1, 0).text = '\n'.join(experience_text) education_table = shapes.add_table(rows=2, cols=1, left=Inches(.2), top=Inches(kBASE_TOP_INCHES * 5.5), width=200, height=Inches(3.2)).table # set column widths education_table.columns[0].width = Inches(4.4) education_table.rows[0].height = Inches(kTABLE_HEADER_INCHES) # set header title education_table.cell(0, 0).text = "Formação" # set font size for table info education_cell = education_table.rows[1].cells[0] education_cell.text_frame.paragraphs[0].font.size = Pt(kCELL_INFO_FONT_SIZE) user_education = user.education_info() education_info = [] skills_table = shapes.add_table(rows=2, cols=1, left=Inches(.2), top=Inches(kBASE_TOP_INCHES * 9.5), width=200, height=Inches(3.3)).table # set column widths skills_table.columns[0].width = Inches(4.4) skills_table.rows[0].height = Inches(kTABLE_HEADER_INCHES) # set header title skills_table.cell(0, 0).text = "Competências" # set font size for table info skills_cell = skills_table.rows[1].cells[0] skills_cell.text_frame.paragraphs[0].font.size = Pt(kCELL_INFO_FONT_SIZE) skills_table.cell(1, 0).text = "".join(skills) # TODO: check if it always on object or if it can be a list for course in user_education['courses']: education_info.append( u'{} de {}'.format( DEGREE_LEVELS[course['degree']] if course['degree'] else course['degree'], course['name'] ) ) user_certifications = user_education['certifications'] if len(user_certifications) is not 0: education_info.append( u'Certificações: {}'.format(u', '.join(user_certifications)) ) bullets = "" for i in range(len(education_info)): bullets += u'- {}\n'.format(education_info[i]) education_table.cell(1, 0).text = bullets text_box = shapes.add_textbox(left=Inches(0), top=Inches(ppt.slide_height.inches - kBASE_BOTTOM_INCHES), width=ppt.slide_width, height=Pt(px_to_pt(50))) # text_box.text = "Proposta Nº{} - Confidencial".format("P63838/1") p = text_box.text_frame.add_paragraph() p.text = u'Confidencial' # "Proposta Nº{} - Confidencial".format("P63838/1") p.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER p.font.size = Pt(8) shapes.add_picture(kASSETS_DIRECTORY + "ppt_footer.png", left=Inches(ppt.slide_width.inches - 2.5), top=Inches(ppt.slide_height.inches - (kBASE_BOTTOM_INCHES / 2)), height=Pt(px_to_pt(10)), width=Pt(px_to_pt(185))) return shapes
This piece of code sets the color of a single cell in a table: from pptx.dml.color import RGBColor # cell is a table cell # set fill type to solid color first cell.fill.solid() # set foreground (fill) color to a specific RGB color cell.fill.fore_color.rgb = RGBColor(0xFB, 0x8F, 0x00) I got this piece of code from an issue on the github page of the project. Sadly I don't know how to change the border color and width using this library.
How to export a mesh's movement from Blender?
I have a simple UV Sphere in Blender which moves from point A to point B (I've set it up by pressing "I" button on location A, clicked on LocRot then clicked on LocRot on point B as well). I'd like to export its movement. For exporting a camera's movement I use Blender Foundation\Blender\2.74\scripts\addons\io_anim_camera.py (This is Cameras & Markers (.py) exporter.) It's produces following output: ... ... # new frame scene.frame_set(1 + frame) obj = cameras['Camera.003'] obj.location = -272.1265563964844, -155.54611206054688, -121.49121856689453 <-- here is the current coordinate of the camera, this is what I need for spheres obj.scale = 0.9999998807907104, 0.9999998807907104, 0.9999999403953552 obj.rotation_euler = -1.6492990255355835, 0.00035389664117246866, 0.009288366883993149 obj.keyframe_insert('location') obj.keyframe_insert('scale') obj.keyframe_insert('rotation_euler') data = obj.data data.lens = 35.0 data.keyframe_insert('lens') ... ... I'm looking for the same thing for meshes. So here is the code of the basic exporter for cameras: # ##### 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 ##### # <pep8 compliant> bl_info = { "name": "Export Camera Animation", "author": "Campbell Barton", "version": (0, 1), "blender": (2, 57, 0), "location": "File > Export > Cameras & Markers (.py)", "description": "Export Cameras & Markers (.py)", "warning": "", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Import-Export/Camera_Animation", "support": 'OFFICIAL', "category": "Import-Export", } import bpy def write_cameras(context, filepath, frame_start, frame_end, only_selected=False): data_attrs = ( 'lens', 'shift_x', 'shift_y', 'dof_distance', 'clip_start', 'clip_end', 'draw_size', ) obj_attrs = ( 'hide_render', ) fw = open(filepath, 'w').write scene = bpy.context.scene cameras = [] for obj in scene.objects: if only_selected and not obj.select: continue if obj.type != 'CAMERA': continue cameras.append((obj, obj.data)) frame_range = range(frame_start, frame_end + 1) fw("import bpy\n" "cameras = {}\n" "scene = bpy.context.scene\n" "frame = scene.frame_current - 1\n" "\n") for obj, obj_data in cameras: fw("data = bpy.data.cameras.new(%r)\n" % obj.name) for attr in data_attrs: fw("data.%s = %s\n" % (attr, repr(getattr(obj_data, attr)))) fw("obj = bpy.data.objects.new(%r, data)\n" % obj.name) for attr in obj_attrs: fw("obj.%s = %s\n" % (attr, repr(getattr(obj, attr)))) fw("scene.objects.link(obj)\n") fw("cameras[%r] = obj\n" % obj.name) fw("\n") for f in frame_range: scene.frame_set(f) fw("# new frame\n") fw("scene.frame_set(%d + frame)\n" % f) for obj, obj_data in cameras: fw("obj = cameras['%s']\n" % obj.name) matrix = obj.matrix_world.copy() fw("obj.location = %r, %r, %r\n" % matrix.to_translation()[:]) fw("obj.scale = %r, %r, %r\n" % matrix.to_scale()[:]) fw("obj.rotation_euler = %r, %r, %r\n" % matrix.to_euler()[:]) fw("obj.keyframe_insert('location')\n") fw("obj.keyframe_insert('scale')\n") fw("obj.keyframe_insert('rotation_euler')\n") # only key the angle fw("data = obj.data\n") fw("data.lens = %s\n" % obj_data.lens) fw("data.keyframe_insert('lens')\n") fw("\n") # now markers fw("# markers\n") for marker in scene.timeline_markers: fw("marker = scene.timeline_markers.new(%r)\n" % marker.name) fw("marker.frame = %d + frame\n" % marker.frame) # will fail if the cameras not selected if marker.camera: fw("marker.camera = cameras.get(%r)\n" % marker.camera.name) fw("\n") from bpy.props import StringProperty, IntProperty, BoolProperty from bpy_extras.io_utils import ExportHelper class CameraExporter(bpy.types.Operator, ExportHelper): """Save a python script which re-creates cameras and markers elsewhere""" bl_idname = "export_animation.cameras" bl_label = "Export Camera & Markers" filename_ext = ".py" filter_glob = StringProperty(default="*.py", options={'HIDDEN'}) frame_start = IntProperty(name="Start Frame", description="Start frame for export", default=1, min=1, max=300000) frame_end = IntProperty(name="End Frame", description="End frame for export", default=250, min=1, max=300000) only_selected = BoolProperty(name="Only Selected", default=True) def execute(self, context): write_cameras(context, self.filepath, self.frame_start, self.frame_end, self.only_selected) return {'FINISHED'} def invoke(self, context, event): self.frame_start = context.scene.frame_start self.frame_end = context.scene.frame_end wm = context.window_manager wm.fileselect_add(self) return {'RUNNING_MODAL'} def menu_export(self, context): import os default_path = os.path.splitext(bpy.data.filepath)[0] + ".py" self.layout.operator(CameraExporter.bl_idname, text="Cameras & Markers (.py)").filepath = default_path def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_export) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_export) if __name__ == "__main__": register() So is it possible? How to change this code to export the movement of a spheres?
Well, If you want to export the movement not as a Python script but in Text or in CSV format. Then look at the following code snippet. CSV export can work for camera like this: fw("# frame camera x y z qx qy qz qw\n") for f in frame_range: scene.frame_set(f) for obj, obj_data in cameras: matrix = obj.matrix_world.copy() fw("%d," % f) fw("%s," % obj.name) fw("%r,%r,%r," % matrix.to_translation()[:]) fw("%r,%r,%r,%r" % matrix.to_quaternion()[:]) fw("\n") from bpy.props import StringProperty, IntProperty, BoolProperty from bpy_extras.io_utils import ExportHelper class CameraExporter(bpy.types.Operator, ExportHelper): """Export camera trajectory to a file""" bl_idname = "export_trajectory.cameras" bl_label = "Export camera trajectory" filename_ext = ".csv" filter_glob = StringProperty(default="*.csv", options={'HIDDEN'}) In my opinion it should work similarly for any other objects in scene. Not yet tried for other objects though!
API Exporting Issue
I need someone's expertise on this exporting problem of mine. How it works: Select a camera (animated or not is optional) >> File >> Export Selection >> File Type : .chan (need to load this script as a plugin) Here's where the problem starts. It is able to create a .text file, however, it is not 'exporting' or writing out the contents into the text file and the file size is of zero bytes. I am making use of the current API that it has been coded, modifying the code to add in some maya cmds Can someone kindly help me out? import math, sys, string, os import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMayaAnim as OpenMayaAnim import maya.cmds as cmds import maya.mel as mel kPluginTranslatorTypeName = "chan Export/Import" kVersionNumber = "0.5a" camSel = [] win_name = "chan_window" class CustomNodeTranslator(OpenMayaMPx.MPxFileTranslator): def __init__(self): OpenMayaMPx.MPxFileTranslator.__init__(self) def haveWriteMethod(self): return True def haveReadMethod(self): return True def filter(self): return " .chan" def defaultExtension(self): return "chan" def writer( self, fileObject, optionString, accessMode ): try: fullName = fileObject.fullName() fileHandle = open(fullName,"w") selectList = OpenMaya.MSelectionList() OpenMaya.MGlobal.getActiveSelectionList(selectList) node = OpenMaya.MObject() depFn = OpenMaya.MFnDependencyNode() path = OpenMaya.MDagPath() iterator = OpenMaya.MItSelectionList(selectList) animationTime = OpenMayaAnim.MAnimControl() maxTime = int(animationTime.maxTime().value()) minTime = int(animationTime.minTime().value()) while (iterator.isDone() == 0): iterator.getDependNode(node) depFn.setObject(node) iterator.getDagPath(path, node) cameraObject = OpenMaya.MFnCamera(path) transform = OpenMaya.MFnTransform(path) chanMe = fileExporter(transform, minTime, maxTime, cameraObject) for all in chanMe(): fileHandle.write(all) iterator.next() fileHandle.close() except: sys.stderr.write( "Failed to write file information\n") raise def processLine( self, lineStr ): self.importTheChan.writeFrameData(lineStr) class fileExporter(): """ module for exporting chan files from application. arguments: object, startFrame, endFrame """ def __init__(self, transform, startAnimation, endAnimation, cameraObj): self.fileExport = [] self.transform = transform self.cameraObj = cameraObj self.start = startAnimation self.end = endAnimation self.exportWin() def exportWin(self): self.expWindow = cmds.window(w=150, h=100, title = "Export Selection" ) cmds.columnLayout( adjustableColumn=True ) form = cmds.formLayout(numberOfDivisions=100) cmds.radioCollection() self.chk1 = cmds.radioButton( label='option1', onc = self.opt1On, ofc = self.opt1Off ) self.chk2 = cmds.radioButton( label='option2', onc = self.opt2On, ofc = self.opt2Off ) self.okayBtn = cmds.button(label='okay!', command=self.runSel, width=150, height=35) cmds.formLayout(form, edit=True, attachForm=[\ (self.chk1, 'top', 15),\ (self.chk1, 'left', 15),\ (self.chk2, 'top', 30),\ (self.chk2, 'left', 15),\ (self.okayBtn, 'top', 50),\ (self.okayBtn, 'left', 15)]) cmds.showWindow( self.expWindow ) def opt1On(self, args): print "User checked option1" startAnimation = cmds.playbackOptions(query=True, minTime=True) endAnimation = cmds.playbackOptions(query=True, maxTime=True) self.start = startAnimation self.end = endAnimation def opt1Off(self, args): print "User un-checked option1" cmds.radioButton(self.chk2, edit = True, enable = True) self.start = "" self.end = "" def opt2On(self, args): print "User checked option2" startAnimation = cmds.findKeyframe(which='first') endAnimation = cmds.findKeyframe(which='last') self.start = startAnimation self.end = endAnimation #self.start.append(int(startAnimation)) #self.end.append(int(endAnimation)) def opt2Off(self, args): print "User un-checked option2" self.start = "" self.end = "" def runSel(self, args): chkVal1 = cmds.radioButton(self.chk1, query=True, sl=1) chkVal2 = cmds.radioButton(self.chk2, query=True, sl=1) if chkVal1 == 1: print "opt1 Pressed!" print self.start print self.end self.test() self.closeWindow() elif chkVal2 == 1: print "opt2 Pressed!" print self.start print self.end self.test() self.closeWindow() else: cmds.warning("Check an option") def closeWindow(self): cmds.deleteUI(self.expWindow, window=True) def test(self): self.actualExp(self.transform, self.start, self.end, self.cameraObj) def actualExp(self, transform, startAnimation, endAnimation, cameraObj): mayaGlobal = OpenMaya.MGlobal() mayaGlobal.viewFrame(OpenMaya.MTime(1)) # Converts the float arguement into integer for i in range(int(startAnimation), int(endAnimation + 1)): focalLength = cameraObj.focalLength() vFilmApp = cameraObj.verticalFilmAperture() focalOut = 2 math.degrees(math.atan(vFilmApp 25.4/ (2 focalLength))) myEuler = OpenMaya.MEulerRotation() spc = OpenMaya.MSpace.kWorld trans = transform.getTranslation(spc) rotation = transform.getRotation(myEuler) rotVector = OpenMaya.MVector(myEuler.asVector()) self.fileExport.append((str(i) + '\t' + str(trans[0]) + "\t" + str(trans[1]) + "\t" + str(trans[2]) + "\t" + str(math.degrees(rotVector[0])) + "\t" + str(math.degrees(rotVector[1])) + "\t" + str(math.degrees(rotVector[2])) + "\t" + str(focalOut) + "\n")) mayaGlobal.viewFrame(OpenMaya.MTime(i+1)) def __call__(self, args): return self.fileExport def radianToDegree(self, radians): outDegrees = 0.0 outDegrees = (float(radians) / (math.pi)) 180 return outDegrees # creator def translatorCreator(): return OpenMayaMPx.asMPxPtr( CustomNodeTranslator() ) # initialize the script plug-in def initializePlugin(mobject): mplugin = OpenMayaMPx.MFnPlugin(mobject) try: mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator) except: sys.stderr.write( "Failed to register translator: %s" % kPluginTranslatorTypeName ) raise # uninitialize the script plug-in def uninitializePlugin(mobject): mplugin = OpenMayaMPx.MFnPlugin(mobject) try: mplugin.deregisterFileTranslator( kPluginTranslatorTypeName ) except: sys.stderr.write( "Failed to deregister translator: %s" % kPluginTranslatorTypeName ) raise
the __call__ method is what's supposed to provide the contents of the file. It returns self.fileExport, which is an empty list that is not getting populated.
The problem here is the writer method of the plugin will not wait for your exportWin UI to return the user inputs when you call chanMe = fileExporter(transform, minTime, maxTime, cameraObject) By the time the user has entered the inputs, the statements that follow have already been executed: for all in chanMe(): fileHandle.write(all) iterator.next() fileHandle.close() That is why plugin-based file exporters like these have their options UI tucked away in the option box. These options will be passed prior to call to the plugin's writer(). You will need to export your options UI code (in a certain specific format) using MEL in another script file and specify the name of that file in the optionsScriptName param of the registerFileTranslator call. There is a communication protocol that needs to be followed for communication between this options UI and the writer plugin itself. RobTheBloke's awesome post illustrates this process. Ideally, the writer() method should have all the details it needs for computing and exporting without having to wait for user input. Alternatively, if you prefer to have your own UI window and more control over the flow of things, you could write the exporter not as a plugin, but as a simple MEL/Python module. You could still use the power of the API. Hope this helped!
Importing code from file into Python and adding file dialog box
I have a python script signalgen.py that plays audio using equations but I would like to be able to hard code the file where the equation is stored in eq1.txt or choose a file and import the equation. The problems I'm having are: 1) How can I hard code a file and it's path correctly so it will play the equation as audio I get an error Traceback (most recent call last): File "signalgen.py", line 484, in need_data v += (datum * self.sig_level) TypeError: can't multiply sequence by non-int of type 'float' The specific block of code which I believe is causing the issue def equation_import_function(self,t,f): fileobj=open("/home/rat/eq1.txt","r") eqdata =fileobj.read() #read whole file fileobj.close() #return math.tan(2.0*math.pi*f*t) return eqdata I have this line of code in the eq1.txt file-> math.tan(2.0*math.pi*f*t) 2) How can I add a file open dialog box to be able to choose a file and import the equation. PS I'm using Ubuntu 10.04 (Linux) and the equations will be several pages long this is the reason I would like to import them into python from text files Here's the entire code if you want to look at what I'm using below or seen on pastebin which includes line numbers http://pastebin.com/HZg0Jhaw #!/usr/bin/env python # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (C) 2011, Paul Lutus * # * * # * 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., * # * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * # *************************************************************************** # version date 01-12-2011 VERSION = '1.1' import re, sys, os import gobject gobject.threads_init() import gst import gtk gtk.gdk.threads_init() import time import struct import math import random import signal import webbrowser class Icon: icon = [ "32 32 17 1", " c None", ". c #2A2E30", "+ c #333739", "# c #464A4C", "# c #855023", "$ c #575A59", "% c #676A69", "& c #CC5B00", "* c #777A78", "= c #DB731A", "- c #8A8C8A", "; c #969895", "> c #F68C22", ", c #A5A7A4", "' c #F49D4A", ") c #B3B5B2", "! c #DEE0DD", " &&&&&&& ", " &&&===='''''& ", " &'''''====&'& ", " +++++&'&&&&& &'& ", " +#$%****&'&+ &'& ", " +#**%$#++#&'&*#+ &'& ", " +#**#+++++++&'&#**#+ &'& ", " +$*$+++++++++&'&++$*$+ &'& ", " #*#++++++++++&'&+++##&&&'& ", " +*#++++++++#&&&'&+++#=''''& ", " +*$++++++++#=''''&+++&'>>>'& ", " #*+++++++++&'>>>'&+++#='''= ", " +%$++++++++##='''=###++#&&&# ", " +*#+++++++####&&&######++#*+ ", " +*+++++++####++#$%$$####++*+ ", " +*++++++##+#;,,*##*$$$###+*+ ", " +*#++++###%!!!!,;#$*$$$###*+ ", " +%$++++##+)!!!),-*+-%$$$#$%+ ", " +#*+++###+-!!!,;-%#;%%$$+*#+ ", " +*#++####+$*-*%#+*-%%$##*+ ", " ++*#+###$$%#++#%;;*%%$#-$+ ", " +#%+###$$%*;;;;-*%%%#**+ ", " .+$%###$$$*******%$$*-+. ", " .+#%%##$$*#*#%%%$%-%+. ", " .++#%$$$$$$%%%%--#+. ", " +++##$%*****%+++ ", " +++++++++++++#. ", " #--%#++#$*-%+ ", " +%,))),;%+. ", " ++++++. ", " ", " " ] # this should be a temporary hack class WidgetFinder: def localize_widgets(self,parent,xmlfile): # an unbelievable hack made necessary by # someone unwilling to fix a year-old bug with open(xmlfile) as f: for name in re.findall('(?s) id="(.*?)"',f.read()): if re.search('^k_',name): obj = parent.builder.get_object(name) setattr(parent,name,obj) class ConfigManager: def __init__(self,path,dic): self.path = path self.dic = dic def read_config(self): if os.path.exists(self.path): with open(self.path) as f: for record in f.readlines(): se = re.search('(.*?)\s*=\s*(.*)',record.strip()) if(se): key,value = se.groups() if (key in self.dic): widget = self.dic[key] typ = type(widget) if(typ == list): widget[0] = value elif(typ == gtk.Entry): widget.set_text(value) elif(typ == gtk.HScale): widget.set_value(float(value)) elif(typ == gtk.Window): w,h = value.split(',') widget.resize(int(w),int(h)) elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton): widget.set_active(value == 'True') elif(typ == gtk.ComboBox): if(value in widget.datalist): i = widget.datalist.index(value) widget.set_active(i) else: print "ERROR: reading, cannot identify key %s with type %s" % (key,type(widget)) def write_config(self): with open(self.path,'w') as f: for key,widget in sorted(self.dic.iteritems()): typ = type(widget) if(typ == list): value = widget[0] elif(typ == gtk.Entry): value = widget.get_text() elif(typ == gtk.HScale): value = str(widget.get_value()) elif(typ == gtk.Window): _,_,w,h = widget.get_allocation() value = "%d,%d" % (w,h) elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton): value = ('False','True')[widget.get_active()] elif(typ == gtk.ComboBox): value = widget.get_active_text() else: print "ERROR: writing, cannot identify key %s with type %s" % (key,type(widget)) value = "Error" f.write("%s = %s\n" % (key,value)) def preset_combobox(self,box,v): if(v in box.datalist): i = box.datalist.index(v) box.set_active(i) else: box.set_active(0) def load_combobox(self,obj,data): if(len(obj.get_cells()) == 0): # Create a text cell renderer cell = gtk.CellRendererText () obj.pack_start(cell) obj.add_attribute (cell, "text", 0) obj.get_model().clear() for s in data: obj.append_text(s.strip()) setattr(obj,'datalist',data) class TextEntryController: def __init__(self,parent,widget): self.par = parent self.widget = widget widget.connect('scroll-event',self.scroll_event) widget.set_tooltip_text('Enter number or:\n\ Mouse wheel: increase,decrease\n\ Shift/Ctrl/Alt: faster change') def scroll_event(self,w,evt): q = (-1,1)[evt.direction == gtk.gdk.SCROLL_UP] # magnify change if shift,ctrl,alt pressed for m in (1,2,4): if(self.par.mod_key_val & m): q *= 10 s = self.widget.get_text() v = float(s) v += q v = max(0,v) s = self.par.format_num(v) self.widget.set_text(s) class SignalGen: M_AM,M_FM = range(2) W_SINE,W_TRIANGLE,W_SQUARE,W_SAWTOOTH,W_EQUATION_IMPORT = range(5) waveform_strings = ('Sine','Triangle','Square','Sawtooth', 'Equation_Import') R_48000,R_44100,R_22050,R_16000,R_11025,R_8000,R_4000 = range(7) sample_rates = ('48000','44100','22050','16000', '11025', '8000', '4000') def __init__(self): self.restart = False # exit correctly on system signals signal.signal(signal.SIGTERM, self.close) signal.signal(signal.SIGINT, self.close) # precompile struct operator self.struct_int = struct.Struct('i') self.max_level = (2.0**31)-1 self.gen_functions = ( self.sine_function, self.triangle_function, self.square_function, self.sawtooth_function, self.equation_import_function ) self.main_color = gtk.gdk.color_parse('#c04040') self.sig_color = gtk.gdk.color_parse('#40c040') self.mod_color = gtk.gdk.color_parse('#4040c0') self.noise_color = gtk.gdk.color_parse('#c040c0') self.pipeline = False self.count = 0 self.imod = 0 self.rate = 1 self.mod_key_val = 0 self.sig_freq = 440 self.mod_freq = 3 self.sig_level = 100 self.mod_level = 100 self.noise_level = 100 self.enable = True self.sig_waveform = SignalGen.W_SINE self.sig_enable = True self.sig_function = False self.mod_waveform = SignalGen.W_SINE self.mod_function = False self.mod_mode = SignalGen.M_AM self.mod_enable = False self.noise_enable = False self.sample_rate = SignalGen.R_22050 self.left_audio = True self.right_audio = True self.program_name = self.__class__.__name__ self.config_file = os.path.expanduser("~/." + self.program_name) self.builder = gtk.Builder() self.xmlfile = 'signalgen_gui.glade' self.builder.add_from_file(self.xmlfile) WidgetFinder().localize_widgets(self,self.xmlfile) self.k_quit_button.connect('clicked',self.close) self.k_help_button.connect('clicked',self.launch_help) self.k_mainwindow.connect('destroy',self.close) self.k_mainwindow.set_icon(gtk.gdk.pixbuf_new_from_xpm_data(Icon.icon)) self.title = self.program_name + ' ' + VERSION self.k_mainwindow.set_title(self.title) self.tooltips = { self.k_sample_rate_combobox : 'Change data sampling rate', self.k_left_checkbutton : 'Enable left channel audio', self.k_right_checkbutton : 'Enable right channel audio', self.k_sig_waveform_combobox : 'Select signal waveform', self.k_mod_waveform_combobox : 'Select modulation waveform', self.k_mod_enable_checkbutton : 'Enable modulation', self.k_sig_enable_checkbutton : 'Enable signal', self.k_noise_enable_checkbutton : 'Enable white noise', self.k_mod_am_radiobutton : 'Enable amplitude modulation', self.k_mod_fm_radiobutton : 'Enable frequency modulation', self.k_quit_button : 'Quit %s' % self.title, self.k_enable_checkbutton : 'Enable output', self.k_help_button : 'Visit the %s Web page' % self.title, } for k,v in self.tooltips.iteritems(): k.set_tooltip_text(v) self.config_data = { 'SampleRate' : self.k_sample_rate_combobox, 'LeftChannelEnabled' : self.k_left_checkbutton, 'RightChannelEnabled' : self.k_right_checkbutton, 'SignalWaveform' : self.k_sig_waveform_combobox, 'SignalFrequency' : self.k_sig_freq_entry, 'SignalLevel' : self.k_sig_level_entry, 'SignalEnabled' : self.k_sig_enable_checkbutton, 'ModulationWaveform' : self.k_mod_waveform_combobox, 'ModulationFrequency' : self.k_mod_freq_entry, 'ModulationLevel' : self.k_mod_level_entry, 'ModulationEnabled' : self.k_mod_enable_checkbutton, 'AmplitudeModulation' : self.k_mod_am_radiobutton, 'FrequencyModulation' : self.k_mod_fm_radiobutton, 'NoiseEnabled' : self.k_noise_enable_checkbutton, 'NoiseLevel' : self.k_noise_level_entry, 'OutputEnabled' : self.k_enable_checkbutton, } self.cm = ConfigManager(self.config_file,self.config_data) self.cm.load_combobox(self.k_sig_waveform_combobox,self.waveform_strings) self.k_sig_waveform_combobox.set_active(self.sig_waveform) self.cm.load_combobox(self.k_mod_waveform_combobox,self.waveform_strings) self.k_mod_waveform_combobox.set_active(self.mod_waveform) self.cm.load_combobox(self.k_sample_rate_combobox,self.sample_rates) self.k_sample_rate_combobox.set_active(self.sample_rate) self.k_sig_freq_entry.set_text(self.format_num(self.sig_freq)) self.k_sig_level_entry.set_text(self.format_num(self.sig_level)) self.k_mod_freq_entry.set_text(self.format_num(self.mod_freq)) self.k_mod_level_entry.set_text(self.format_num(self.mod_level)) self.k_noise_level_entry.set_text(self.format_num(self.noise_level)) self.k_main_viewport_border.modify_bg(gtk.STATE_NORMAL,self.main_color) self.k_sig_viewport_border.modify_bg(gtk.STATE_NORMAL,self.sig_color) self.k_mod_viewport_border.modify_bg(gtk.STATE_NORMAL,self.mod_color) self.k_noise_viewport_border.modify_bg(gtk.STATE_NORMAL,self.noise_color) self.sig_freq_cont = TextEntryController(self,self.k_sig_freq_entry) self.sig_level_cont = TextEntryController(self,self.k_sig_level_entry) self.mod_freq_cont = TextEntryController(self,self.k_mod_freq_entry) self.mod_level_cont = TextEntryController(self,self.k_mod_level_entry) self.noise_level_cont = TextEntryController(self,self.k_noise_level_entry) self.k_mainwindow.connect('key-press-event',self.key_event) self.k_mainwindow.connect('key-release-event',self.key_event) self.k_enable_checkbutton.connect('toggled',self.update_values) self.k_sig_freq_entry.connect('changed',self.update_entry_values) self.k_sig_level_entry.connect('changed',self.update_entry_values) self.k_sig_enable_checkbutton.connect('toggled',self.update_checkbutton_values) self.k_mod_freq_entry.connect('changed',self.update_entry_values) self.k_mod_level_entry.connect('changed',self.update_entry_values) self.k_noise_level_entry.connect('changed',self.update_entry_values) self.k_sample_rate_combobox.connect('changed',self.update_values) self.k_sig_waveform_combobox.connect('changed',self.update_values) self.k_mod_waveform_combobox.connect('changed',self.update_values) self.k_left_checkbutton.connect('toggled',self.update_checkbutton_values) self.k_right_checkbutton.connect('toggled',self.update_checkbutton_values) self.k_mod_enable_checkbutton.connect('toggled',self.update_checkbutton_values) self.k_noise_enable_checkbutton.connect('toggled',self.update_checkbutton_values) self.k_mod_am_radiobutton.connect('toggled',self.update_checkbutton_values) self.cm.read_config() self.update_entry_values() self.update_checkbutton_values() self.update_values() def format_num(self,v): return "%.2f" % v def get_widget_text(self,w): typ = type(w) if(typ == gtk.ComboBox): return w.get_active_text() elif(typ == gtk.Entry): return w.get_text() def get_widget_num(self,w): try: return float(self.get_widget_text(w)) except: return 0.0 def restart_test(self,w,pv): nv = w.get_active() self.restart |= (nv != pv) return nv def update_entry_values(self,*args): self.sig_freq = self.get_widget_num(self.k_sig_freq_entry) self.sig_level = self.get_widget_num(self.k_sig_level_entry) / 100.0 self.mod_freq = self.get_widget_num(self.k_mod_freq_entry) self.mod_level = self.get_widget_num(self.k_mod_level_entry) / 100.0 self.noise_level = self.get_widget_num(self.k_noise_level_entry) / 100.0 def update_checkbutton_values(self,*args): self.left_audio = self.k_left_checkbutton.get_active() self.right_audio = self.k_right_checkbutton.get_active() self.mod_enable = self.k_mod_enable_checkbutton.get_active() self.sig_enable = self.k_sig_enable_checkbutton.get_active() self.mod_mode = (SignalGen.M_FM,SignalGen.M_AM)[self.k_mod_am_radiobutton.get_active()] self.noise_enable = self.k_noise_enable_checkbutton.get_active() def update_values(self,*args): self.restart = (not self.sig_function) self.sample_rate = self.restart_test(self.k_sample_rate_combobox, self.sample_rate) self.enable = self.restart_test(self.k_enable_checkbutton,self.enable) self.mod_waveform = self.k_mod_waveform_combobox.get_active() self.mod_function = self.gen_functions[self.mod_waveform] self.sig_waveform = self.k_sig_waveform_combobox.get_active() self.sig_function = self.gen_functions[self.sig_waveform] self.k_sample_rate_combobox.set_sensitive(not self.enable) if(self.restart): self.init_audio() def make_and_chain(self,name): target = gst.element_factory_make(name) self.chain.append(target) return target def unlink_gst(self): if(self.pipeline): self.pipeline.set_state(gst.STATE_NULL) self.pipeline.remove_many(*self.chain) gst.element_unlink_many(*self.chain) for item in self.chain: item = False self.pipeline = False time.sleep(0.01) def init_audio(self): self.unlink_gst() if(self.enable): self.chain = [] self.pipeline = gst.Pipeline("mypipeline") self.source = self.make_and_chain("appsrc") rs = SignalGen.sample_rates[self.sample_rate] self.rate = float(rs) self.interval = 1.0 / self.rate caps = gst.Caps( 'audio/x-raw-int,' 'endianness=(int)1234,' 'channels=(int)2,' 'width=(int)32,' 'depth=(int)32,' 'signed=(boolean)true,' 'rate=(int)%s' % rs) self.source.set_property('caps', caps) self.sink = self.make_and_chain("autoaudiosink") self.pipeline.add(*self.chain) gst.element_link_many(*self.chain) self.source.connect('need-data', self.need_data) self.pipeline.set_state(gst.STATE_PLAYING) def key_event(self,w,evt): cn = gtk.gdk.keyval_name(evt.keyval) if(re.search('Shift',cn) != None): mod = 1 elif(re.search('Control',cn) != None): mod = 2 elif(re.search('Alt|Meta',cn) != None): mod = 4 else: return if(evt.type == gtk.gdk.KEY_PRESS): self.mod_key_val |= mod else: self.mod_key_val &= ~mod def sine_function(self,t,f): return math.sin(2.0*math.pi*f*t) def triangle_function(self,t,f): q = 4*math.fmod(t*f,1) q = (q,2-q)[q > 1] return (q,-2-q)[q < -1] def square_function(self,t,f): if(f == 0): return 0 q = 0.5 - math.fmod(t*f,1) return (-1,1)[q > 0] def sawtooth_function(self,t,f): return 2.0*math.fmod((t*f)+0.5,1.0)-1.0 def equation_import_function(self,t,f): fileobj=open("/home/rat/eq1.txt","r") eqdata =fileobj.read() #read whole file fileobj.close() #return math.tan(2.0*math.pi*f*t) return eqdata def need_data(self,src,length): bytes = "" # sending two channels, so divide requested length by 2 ld2 = length / 2 for tt in range(ld2): t = (self.count + tt) * self.interval if(not self.mod_enable): datum = self.sig_function(t,self.sig_freq) else: mod = self.mod_function(t,self.mod_freq) # AM mode if(self.mod_mode == SignalGen.M_AM): datum = 0.5 * self.sig_function(t,self.sig_freq) * (1.0 + (mod * self.mod_level)) # FM mode else: self.imod += (mod * self.mod_level * self.interval) datum = self.sig_function(t+self.imod,self.sig_freq) v = 0 if(self.sig_enable): v += (datum * self.sig_level) if(self.noise_enable): noise = ((2.0 * random.random()) - 1.0) v += noise * self.noise_level v *= self.max_level v = max(-self.max_level,v) v = min(self.max_level,v) left = (0,v)[self.left_audio] right = (0,v)[self.right_audio] bytes += self.struct_int.pack(left) bytes += self.struct_int.pack(right) self.count += ld2 src.emit('push-buffer', gst.Buffer(bytes)) def launch_help(self,*args): webbrowser.open("http://arachnoid.com/python/signalgen_program.html") def close(self,*args): self.unlink_gst() self.cm.write_config() gtk.main_quit() app=SignalGen() gtk.main()
The imp module will help you to cleanly load Python code chunks from arbitrary files. #!/usr/bin/env python # equation in equation-one.py def eqn(arg): return arg * 3 + 2 #!/usr/bin/env python # your code import imp path = "equation-one.py" eq_mod = imp.load_source("equation", path, open(path)) print("Oh the nice stuff in eq_mod: %s" % dir(eq_mod)) In your custom function definition, you can create a file selector dialog, get the selected file path, load the code using imp, and return the result of the function inside the imported module.
I was commenting before, but I stared at your code long enough and kinda realized what you were trying to do, so it was easier for me to post an answer. Please refer to cJ Zougloubs answer as I expand on his suggestion to use the imp module. Your equation files should implement a common interface: # equation1.py def eqn(*args): return sum(*args) Then you would load them in using cj Zougloubs suggestion, but with a common interface: # python_rt.py def equation_import_function(self, *args): filepath = '' # filepath = ... do file chooser dialog here ... eq_mod = imp.load_source("equation", filepath) eqdata = eq_mod.eqn(*args) return eqdata Now you have a function in your main code that takes any number of arguments, asks the user to pick the equation file, and gets the result for you. Edit To address your comment more specifically # equation1.py import math def eqn(*args): f = args[0] t = args[1] return math.tan(2.0*math.pi*f*t) And in your main tool, you would use imp.load_source to bring it in. Wherever you needed that equation for your audio, you could then do: eq_mod.eqn(f, t)