Showing posts with label #IronPython. Show all posts
Showing posts with label #IronPython. Show all posts
Tuesday, November 25, 2014
Polygon Offset Using Vector Math in IronPython
The other day I saw a something retweeted by @leppie (I think) about an experimental hyper-fast vector math driven 3D engine for the dot Net Framework. This led me to investigate whether there is a default implementation of vector math in the dot Net Framework. As it turns out, there is.
This is of interest because (I think) this would make IronPython the only Python implementation that has vector math included without having to install a third party library. Java has a utils.Vector object, but it has nothing to do with vector math (it's a specialized array). You do need to use the dot Net Framework instead of standard Python modules, but if you're running IronPython, you should have access to that anyway.
The whole, or at least a big part of the idea of running a Python implementation against the dotNet Framework is that you can leverage the power of that big library collection with a language that's fairly dense, easy, and doesn't require compilation.
This was pretty easy on Windows. The only confusing part is that there are two namespaces in dot Net called System.Windows. You want the one that references the WindowsBase dll. This is the one that has our Vector object in it.
The code (including the plotting by Gnuplot - I had to download the Windows version; I did leave out the monastery.py file with the original shape points in it; also, the writetofile.py file is almost exactly like the one from the previous post except that for a Vector object, the x and y names are capitalized):
# vecipy.py
"""
Polygon offset problem using
dot Net Framework.
"""
import clr
WINX = 'WindowsBase'
clr.AddReference(WINX)
from System.Windows import Vector
import math
import copy
import monastery as pic
OFFSET = 0.15
def scaleadd(origin, offset, vectorx):
"""
From a Vector representing the origin,
a scalar offset, and a Vector, returns
a Vector object representing a point
offset from the origin.
(Multiply vectorx by offset and add to origin.)
"""
# Multiply method that takes scalar and Vector.
multx = Vector.Multiply(vectorx, offset)
return Vector.Add(multx, origin)
def getinsetpoint(pt1, pt2, pt3):
"""
Given three points that form a corner (pt1, pt2, pt3),
returns a point offset distance OFFSET to the right
of the path formed by pt1-pt2-pt3.
pt1, pt2, and pt3 are two tuples.
Returns a Vector object.
"""
origin = Vector(*pt2)
v1 = Vector(pt1[0] - pt2[0], pt1[1] - pt2[1])
v1.Normalize()
v2 = Vector(pt3[0] - pt2[0], pt3[1] - pt2[1])
v2.Normalize()
v3 = copy.copy(v1)
v1 = Vector.CrossProduct(v1, v2)
v3 = Vector.Add(v3, v2)
v3.Normalize()
# In dotNet - Vector.Multiply is overloaded.
# When it gets two Vector objects as arguments
# it returns a dot product.
cs = Vector.Multiply(v3, v2)
# Again multiplication is overloaded.
# Here it gets a scalar and a Vector
# as arguments.
a1 = Vector.Multiply(cs, v2)
a2 = Vector.Subtraction(v3, a1)
if cs > 0:
alpha = math.sqrt(a2.LengthSquared)
else:
alpha =- math.sqrt(a2.LengthSquared)
if v1 < 0.0:
return scaleadd(origin, -1.0 * OFFSET/alpha, v3)
else:
return scaleadd(origin, OFFSET/alpha, v3)
def generatepoints():
"""
Create list of offset points
for points inset from polygon.
Return list.
"""
polyinset = []
lenpolygon = len(pic.MONASTERY)
i = 0
poly = pic.MONASTERY
while i < lenpolygon - 2:
polyinset.append(getinsetpoint(poly[i],
poly[i + 1], poly[i + 2]))
i += 1
polyinset.append(getinsetpoint(poly[-2],
poly[0], poly[1]))
polyinset.append(getinsetpoint(poly[0],
poly[1], poly[2]))
return polyinset
# writetofile.py
"""
Write vector points to file.
Show in gnuplot.
"""
import vecipy as vecx
import os
# We're using gnuplot.
# It doesn't like commas, so
# we'll use whitespace (6).
FMT = '{0:30.28f} {1:30.28f}'
FILEX = 'points'
ORIGSHAPE = 'originalshape'
PLOTCMD = 'set xrange[0.0:6.0]\n'
PLOTCMD += 'set yrange[0.0:6.0]\n'
PLOTCMD += 'plot "{0:s}" with lines lt rgb "red" lw 4, '
PLOTCMD += '"{1:s}" with lines lt rgb "blue" lw 4'
GNUPLOTFILE = 'plotfile'
GNUPLOT = 'gnuplot -p {:s}'.format(GNUPLOTFILE)
pts = vecx.generatepoints()
f = open(FILEX, 'w')
i = 1
for ptx in pts:
print('Printing point {0:d} . . .'.format(i))
print >> f, FMT.format(ptx.X, ptx.Y)
i += 1
f.close()
# Plot original as well.
i = 0
f = open(ORIGSHAPE, 'w')
for ptx in vecx.pic.MONASTERY:
print('Printing point {0:d} of original shape . . .'.format(i))
print >> f, FMT.format(*ptx)
i += 1
f.close()
f = open(GNUPLOTFILE, 'w')
print >> f, PLOTCMD.format(ORIGSHAPE, FILEX)
f.close()
os.system(GNUPLOT)
The result (shown in previous post):
I run OpenBSD on my laptop at home. So I would be using mono in my cross-platform experiment.
Microsoft just recently (Fall 2014) announced the open sourcing of the dotNet Framework and cross platform capability for it. The mono project responded very positively to this announcement. I would imagine this as being good news for IronPython too.
OpenBSD has a package for mono. From there, I just needed to download the IronPython binaries and run mono against them, or so I thought . . .
As it turns out, my script kept crashing on the overloaded Vector.Multiply method - NotImplementedError. I tried to research things, wasn't having any luck, and brute forced the problem by wrapping the method in a class in C# class I called vecx:
Note (26NOV2014): I hacked this C# module up a bit too quickly and didn't have performance or elegance in mind. If you declare those Multiply methods as static you can save yourself the trouble of instantiating a new instance of the class each time you want to call them. In fact, you can do the same thing with all the Vector methods you want to use (Add, CrossProduct, etc.). I was just too hurried and too lazy. CBT
using System;
public class vecx
{
public System.Windows.Vector vectorx;
public vecx()
{
System.Windows.Vector vectorx = new System.Windows.Vector(0.0, 0.0);
this.vectorx = vectorx;
}
public vecx(double x, double y)
{
System.Windows.Vector vectorx = new System.Windows.Vector(x, y);
this.vectorx = vectorx;
}
public Double Multiply(System.Windows.Vector a, System.Windows.Vector b)
{
return System.Windows.Vector.Multiply(a, b);
}
public System.Windows.Vector Multiply(Double a, System.Windows.Vector b)
{
return System.Windows.Vector.Multiply(a, b);
}
public System.Windows.Vector Multiply(System.Windows.Vector a, Double b)
{
return System.Windows.Vector.Multiply(a, b);
}
}
The command line (your paths will probably be different) text for compiling this under mono was:
$ mcs -r:/usr/local/lib/mono/4.5/WindowsBase.dll -target:library vecx.cs
The code using this faux Vector class was a little bit different (and hackish):
"""
Polygon offset problem using
dot Net Framework.
Modified for use with mono.
"""
import clr
# Hacked C# module.
VECX = '/home/carl/vectormath/IronPython/mono/vecx.dll'
clr.AddReference(VECX)
import vecx
import math
import copy
import monastery as pic
OFFSET = 0.15
def scaleadd(origin, offset, vectorx):
"""
From a Vector representing the origin,
a scalar offset, and a Vector, returns
a Vector object representing a point
offset from the origin.
(Multiply vectorx by offset and add to origin.)
"""
# Generic vector for use of Vector type.
vecgeneric = vecx().vectorx
# Multiply method that takes scalar and Vector.
# Using cs module compiled to dll for Multiply
# methods in mono.
multx = vecx().Multiply(vectorx, offset)
return vecgeneric.Add(multx, origin)
def getinsetpoint(pt1, pt2, pt3):
"""
Given three points that form a corner (pt1, pt2, pt3),
returns a point offset distance OFFSET to the right
of the path formed by pt1-pt2-pt3.
pt1, pt2, and pt3 are two tuples.
Returns a Vector object.
"""
# Generic vector for use of type.
vecgeneric = vecx().vectorx
origin = vecx(*pt2).vectorx
v1 = vecx(pt1[0] - pt2[0], pt1[1] - pt2[1]).vectorx
v1.Normalize()
v2 = vecx(pt3[0] - pt2[0], pt3[1] - pt2[1]).vectorx
v2.Normalize()
v3 = copy.copy(v1)
v1 = vecgeneric.CrossProduct(v1, v2)
v3 = vecgeneric.Add(v3, v2)
v3.Normalize()
# In dotNet - Vector.Multiply is overloaded.
# When it gets two Vector objects as arguments
# it returns a dot product.
# Using cs module compiled to dll for Multiply
# methods in mono.
cs = vecx().Multiply(v3, v2)
# Again multiplication is overloaded.
# Here it gets a scalar and a Vector
# as arguments.
# Using cs module compiled to dll for Multiply
# methods in mono.
a1 = vecx().Multiply(cs, v2)
a2 = vecgeneric.Subtract(v3, a1)
if cs > 0:
alpha = math.sqrt(a2.LengthSquared)
else:
alpha =- math.sqrt(a2.LengthSquared)
if v1 < 0.0:
return scaleadd(origin, -1.0 * OFFSET/alpha, v3)
else:
return scaleadd(origin, OFFSET/alpha, v3)
def generatepoints():
"""
Create list of offset points
for points inset from polygon.
Return list.
"""
polyinset = []
lenpolygon = len(pic.MONASTERY)
i = 0
poly = pic.MONASTERY
while i < lenpolygon - 2:
polyinset.append(getinsetpoint(poly[i],
poly[i + 1], poly[i + 2]))
i += 1
polyinset.append(getinsetpoint(poly[-2],
poly[0], poly[1]))
polyinset.append(getinsetpoint(poly[0],
poly[1], poly[2]))
return polyinset
Any port in a storm or whatever it takes, as they say.
Thanks again to Mr. Rafsanjani whom I referenced in my previous post. His methodology and detection of a former bug got me back on track.
And thank you for stopping by.
Labels:
#Csharp,
#dotNet,
#IronPython,
#mono,
#polygonoffset,
#Vector,
#vectormaath
Friday, October 31, 2014
Gtk.TreeView (grid view) with mono, gtk-sharp, and IronPython
The post immediately prior to this one was an attempt to reproduce Windows.Forms Calendar controls in Gtk for cross platform (Windows/*nix) effective rendering.
This time I am attempting to get familiar with gtk-sharp/Gtk's version of a grid view - the Gtk.TreeView object. Some of the gtk-sharp documentation suggests the NodeView object would be easier to use. I had some trouble instantiating the objects associated with the NodeView and went with the TreeView instead in the hopes of getting more control.
The Windows.Forms GridView I did years ago is here. It became apparent to me shortly after embarking on this journey that I would be hard pressed to recreate all the functionality of that script in a timely manner. I settled for a tabular view of drillhole data (fabricated, mock data) with some custom formatting.
Aside: this is typically how mineral exploration drillhole data (core, reverse circulation drilling) is presented in tabular format - a series of from-to intervals with assay values. Assuming the assays are all separate elements, the reported weight percents should not sum more than 100%, and never do unless someone fat fingers a decimal place. I've projected a couple screaming hot polymetallic drill holes that end near surface (lack of funding for drilling), but show enough promise that the new mining town of Trachteville (the drill hole name CBT-BNZA stands for CBT-Bonanza) will spring up there at any moment . . . one can dream.
The data store object for the grid view Gtk.ListStore object would not instantiate in IronPython. I was not the only person to have experienced this problem (I cannot locate the link to the mailing list thread or forum reference, but like the big fish that got away, I swear I saw it). I didn't want to drop the effort just because of that, so I hacked and compiled some C# code:
public class storex
{
public Gtk.ListStore drillhole =
// 7 columns
// drillhole id
new Gtk.ListStore (typeof (string),
// from
typeof (double),
// to
typeof (double),
// assay1
typeof (double),
// assay2
typeof (double),
// assay3
typeof (double),
// assay4
typeof (double));
}
The mono command on Windows was
C:\UserPrograms\Mono-3.2.3>mcs -pkg:gtk-sharp-2.0 /target:library C:\UserPrograms\IronPythonGUI\storex.cs
Those are my file paths; locations depend on where you install things like mono and IronPython.
Anyway, I got my dll and I was off to the races. Getting to know the Gtk and gtk-sharp object model proved challenging for me. I'm glad I got some familiarity with it, but it would take me longer to do something in Gtk than it did with Windows.Forms. The most fun and gratifying part of the project was getting the custom formatting to work with a Gtk.TreeCellDataFunc. I used a function that yielded specific functions for each column - something that's really easy to do in Python.
Anyway, here are a couple screenshots and the IronPython code:
The OpenBSD one below turned out pretty good, but the Windows one had a little double line underneath the first row - it looked as though it was still trying to select that row when I told it specifically not to. I'm not a design perfectionist Steve Jobs type, but niggling nits like that drive me batty. For now, though it's best I publish the code and move on.
#!/usr/local/bin/mono /home/carl/IronPython-2.7.4/ipy64.exe
import clr
GTKSHARP = 'gtk-sharp'
PANGO = 'pango-sharp'
# Mock store C#
STOREX = 'storex'
clr.AddReference(GTKSHARP)
clr.AddReference(PANGO)
# C# module compiled for this project.
# Problems with Gtk.ListStore in IronPython.
clr.AddReference(STOREX)
import Gtk
import Pango
import storex
TITLE = 'Gtk.TreeView Demo (Drillholes)'
MARKUP = '<span font="Courier New" size="14" weight="bold">{:s}</span>'
MARKEDUPTITLE = MARKUP.format(TITLE)
CENTERED = 0.5
RIGHT = 1.0
WINDOWWIDTH = 350
COURFONTREGULAR = 'Courier New 12'
COURFONTBOLD = 'Courier New Bold 12'
DHNAME = 'DH_CBTBNZA-{:>02d}'
DHNAMELABEL = 'drillhole'
FROM = 'from'
TO = 'to'
ASSAY1 = 'assay1'
ASSAY2 = 'assay2'
ASSAY3 = 'assay3'
ASSAY4 = 'assay4'
FP1FMT = '{:>5.1f}'
FP2FMT = '{:>4.2f}'
DHDATAX = {(DHNAME.format(1), 0.0):{TO:8.7,
ASSAY1:22.27,
ASSAY2:4.93,
ASSAY3:18.75,
ASSAY4:35.18},
(DHNAME.format(1), 8.7):{TO:15.3,
ASSAY1:0.27,
ASSAY2:0.09,
ASSAY3:0.03,
ASSAY4:0.22},
(DHNAME.format(1), 15.3):{TO:25.3,
ASSAY1:2.56,
ASSAY2:11.34,
ASSAY3:0.19,
ASSAY4:13.46},
(DHNAME.format(2), 0.0):{TO:10.0,
ASSAY1:0.07,
ASSAY2:1.23,
ASSAY3:4.78,
ASSAY4:5.13},
(DHNAME.format(2), 10.0):{TO:20.0,
ASSAY1:44.88,
ASSAY2:12.97,
ASSAY3:0.19,
ASSAY4:0.03}}
FIELDS = [DHNAMELABEL, FROM, TO, ASSAY1, ASSAY2, ASSAY3, ASSAY4]
BOLDEDCOLUMNS = [DHNAMELABEL, FROM, TO]
NONKEYFIELDS = FIELDS[2:]
BLAZINGCUTOFF = 10.0
def genericfloatformat(floatfmt, index):
"""
For cell formatting in Gtk.TreeView.
Returns a function to format floats
and to format floats' foreground color
based on cutoff value.
floatfmt is a format string.
index is an int that indicates the
column being formatted.
"""
def setfloatfmt(treeviewcolumn, cellrenderer, treemodel, treeiter):
cellrenderer.Text = floatfmt.format(treemodel.GetValue(treeiter, index))
# If it is one of the assay value columns.
# XXX - not generic.
if index > 2:
if treemodel.GetValue(treeiter, index) > BLAZINGCUTOFF:
cellrenderer.Foreground = 'red'
else:
cellrenderer.Foreground = 'black'
return Gtk.TreeCellDataFunc(setfloatfmt)
class TreeViewTest(object):
def __init__(self):
Gtk.Application.Init()
self.window = Gtk.Window('')
# DeleteEvent - copied from Gtk demo on internet.
self.window.DeleteEvent += self.DeleteEvent
# Frame property provides a frame and title.
self.frame = Gtk.Frame(MARKEDUPTITLE)
self.tree = Gtk.TreeView()
self.tree.EnableGridLines = Gtk.TreeViewGridLines.Both
self.frame.Add(self.tree)
# Fonts for formatting.
self.fdregular = Pango.FontDescription.FromString(COURFONTREGULAR)
self.fdbold = Pango.FontDescription.FromString(COURFONTBOLD)
# C# module
self.store = storex().drillhole
self.makecolumns()
self.adddata()
self.tree.Model = self.store
self.formatcolumns()
self.formatcells()
self.prettyup()
self.window.Add(self.frame)
self.window.ShowAll()
# Keep text viewable - size no smaller than intended.
self.window.AllowShrink = False
# XXX - hack to keep lack of gridlines on edges of
# table from showing.
self.window.AllowGrow = False
# Unselect everything for this demo.
self.tree.Selection.UnselectAll()
Gtk.Application.Run()
def makecolumns(self):
"""
Fill in columns for TreeView.
"""
self.columns = {}
for fieldx in FIELDS:
self.columns[fieldx] = Gtk.TreeViewColumn()
self.columns[fieldx].Title = fieldx
self.tree.AppendColumn(self.columns[fieldx])
def formatcolumns(self):
"""
Make custom labels for columnn headers.
Get each column properly justified (all
are right justified,floating point numbers
except for the drillhole 'number' -
actually a string).
"""
self.customlabels = {}
for fieldx in FIELDS:
# This centers the labels at the top.
self.columns[fieldx].Alignment = CENTERED
self.customlabels[fieldx] = Gtk.Label(self.columns[fieldx].Title)
self.customlabels[fieldx].ModifyFont(self.fdbold)
# 120 is about right for from, to, and assay columns.
self.columns[fieldx].MinWidth = 120
self.customlabels[fieldx].ShowAll()
self.columns[fieldx].Widget = self.customlabels[fieldx]
# ShowAll required for new label to take.
self.columns[fieldx].Widget.ShowAll()
def formatcells(self):
"""
Add and format cell renderers.
"""
self.cellrenderers = {}
for fieldx in FIELDS:
self.cellrenderers[fieldx] = Gtk.CellRendererText()
self.columns[fieldx].PackStart(self.cellrenderers[fieldx], True)
# Drillhole 'number' (string)
if fieldx == FIELDS[0]:
self.cellrenderers[fieldx].Xalign = CENTERED
self.columns[fieldx].AddAttribute(self.cellrenderers[fieldx],
'text', 0)
else:
self.cellrenderers[fieldx].Xalign = RIGHT
try:
self.columns[fieldx].AddAttribute(self.cellrenderers[fieldx],
'text', FIELDS.index(fieldx))
except ValueError:
print('\n\nProblem with field definitions; field not found.\n\n')
for fieldx in BOLDEDCOLUMNS:
self.cellrenderers[fieldx].Font = COURFONTBOLD
self.columns[fieldx].Widget.ShowAll()
# XXX - not very generic, but better than doing them one by one.
# from, to columns.
for x in xrange(1, 3):
self.columns[FIELDS[x]].SetCellDataFunc(self.cellrenderers[FIELDS[x]],
genericfloatformat(FP1FMT, x))
# assay<x> columns.
for x in xrange(3, 7):
self.columns[FIELDS[x]].SetCellDataFunc(self.cellrenderers[FIELDS[x]],
genericfloatformat(FP2FMT, x))
def usemarkup(self):
"""
Refreshes UseMarkup property on widgets (labels)
so that they display properly and without
markup text.
"""
# Have to refresh this property each time.
self.frame.LabelWidget.UseMarkup = True
def prettyup(self):
"""
Get Gtk objects looking the way we
intended.
"""
# Try to get Courier New on treeview.
self.tree.ModifyFont(self.fdregular)
# Get rid of line.
self.frame.Shadow = Gtk.ShadowType.None
self.usemarkup()
def adddata(self):
"""
Put data into store.
"""
# XXX - difficulty figuring out sorting
# function for TreeView. Hack it
# with dictionary here.
keytuples = [key for key in DHDATAX]
keytuples.sort()
datax = []
for tuplex in keytuples:
# XXX - side effect comprehension.
# Not great for readability,
# but compact.
[datax.append(x) for x in tuplex]
for fieldx in NONKEYFIELDS:
datax.append(DHDATAX[tuplex][fieldx])
self.store.AppendValues(*datax)
# Reinitiialize data row list.
datax = []
def DeleteEvent(self, widget, event):
Gtk.Application.Quit()
if __name__ == '__main__':
TreeViewTest()
Thanks for stopping by.
This time I am attempting to get familiar with gtk-sharp/Gtk's version of a grid view - the Gtk.TreeView object. Some of the gtk-sharp documentation suggests the NodeView object would be easier to use. I had some trouble instantiating the objects associated with the NodeView and went with the TreeView instead in the hopes of getting more control.
The Windows.Forms GridView I did years ago is here. It became apparent to me shortly after embarking on this journey that I would be hard pressed to recreate all the functionality of that script in a timely manner. I settled for a tabular view of drillhole data (fabricated, mock data) with some custom formatting.
Aside: this is typically how mineral exploration drillhole data (core, reverse circulation drilling) is presented in tabular format - a series of from-to intervals with assay values. Assuming the assays are all separate elements, the reported weight percents should not sum more than 100%, and never do unless someone fat fingers a decimal place. I've projected a couple screaming hot polymetallic drill holes that end near surface (lack of funding for drilling), but show enough promise that the new mining town of Trachteville (the drill hole name CBT-BNZA stands for CBT-Bonanza) will spring up there at any moment . . . one can dream.
The data store object for the grid view Gtk.ListStore object would not instantiate in IronPython. I was not the only person to have experienced this problem (I cannot locate the link to the mailing list thread or forum reference, but like the big fish that got away, I swear I saw it). I didn't want to drop the effort just because of that, so I hacked and compiled some C# code:
public class storex
{
public Gtk.ListStore drillhole =
// 7 columns
// drillhole id
new Gtk.ListStore (typeof (string),
// from
typeof (double),
// to
typeof (double),
// assay1
typeof (double),
// assay2
typeof (double),
// assay3
typeof (double),
// assay4
typeof (double));
}
The mono command on Windows was
C:\UserPrograms\Mono-3.2.3>mcs -pkg:gtk-sharp-2.0 /target:library C:\UserPrograms\IronPythonGUI\storex.cs
Those are my file paths; locations depend on where you install things like mono and IronPython.
Anyway, I got my dll and I was off to the races. Getting to know the Gtk and gtk-sharp object model proved challenging for me. I'm glad I got some familiarity with it, but it would take me longer to do something in Gtk than it did with Windows.Forms. The most fun and gratifying part of the project was getting the custom formatting to work with a Gtk.TreeCellDataFunc. I used a function that yielded specific functions for each column - something that's really easy to do in Python.
Anyway, here are a couple screenshots and the IronPython code:
The OpenBSD one below turned out pretty good, but the Windows one had a little double line underneath the first row - it looked as though it was still trying to select that row when I told it specifically not to. I'm not a design perfectionist Steve Jobs type, but niggling nits like that drive me batty. For now, though it's best I publish the code and move on.
#!/usr/local/bin/mono /home/carl/IronPython-2.7.4/ipy64.exe
import clr
GTKSHARP = 'gtk-sharp'
PANGO = 'pango-sharp'
# Mock store C#
STOREX = 'storex'
clr.AddReference(GTKSHARP)
clr.AddReference(PANGO)
# C# module compiled for this project.
# Problems with Gtk.ListStore in IronPython.
clr.AddReference(STOREX)
import Gtk
import Pango
import storex
TITLE = 'Gtk.TreeView Demo (Drillholes)'
MARKUP = '<span font="Courier New" size="14" weight="bold">{:s}</span>'
MARKEDUPTITLE = MARKUP.format(TITLE)
CENTERED = 0.5
RIGHT = 1.0
WINDOWWIDTH = 350
COURFONTREGULAR = 'Courier New 12'
COURFONTBOLD = 'Courier New Bold 12'
DHNAME = 'DH_CBTBNZA-{:>02d}'
DHNAMELABEL = 'drillhole'
FROM = 'from'
TO = 'to'
ASSAY1 = 'assay1'
ASSAY2 = 'assay2'
ASSAY3 = 'assay3'
ASSAY4 = 'assay4'
FP1FMT = '{:>5.1f}'
FP2FMT = '{:>4.2f}'
DHDATAX = {(DHNAME.format(1), 0.0):{TO:8.7,
ASSAY1:22.27,
ASSAY2:4.93,
ASSAY3:18.75,
ASSAY4:35.18},
(DHNAME.format(1), 8.7):{TO:15.3,
ASSAY1:0.27,
ASSAY2:0.09,
ASSAY3:0.03,
ASSAY4:0.22},
(DHNAME.format(1), 15.3):{TO:25.3,
ASSAY1:2.56,
ASSAY2:11.34,
ASSAY3:0.19,
ASSAY4:13.46},
(DHNAME.format(2), 0.0):{TO:10.0,
ASSAY1:0.07,
ASSAY2:1.23,
ASSAY3:4.78,
ASSAY4:5.13},
(DHNAME.format(2), 10.0):{TO:20.0,
ASSAY1:44.88,
ASSAY2:12.97,
ASSAY3:0.19,
ASSAY4:0.03}}
FIELDS = [DHNAMELABEL, FROM, TO, ASSAY1, ASSAY2, ASSAY3, ASSAY4]
BOLDEDCOLUMNS = [DHNAMELABEL, FROM, TO]
NONKEYFIELDS = FIELDS[2:]
BLAZINGCUTOFF = 10.0
def genericfloatformat(floatfmt, index):
"""
For cell formatting in Gtk.TreeView.
Returns a function to format floats
and to format floats' foreground color
based on cutoff value.
floatfmt is a format string.
index is an int that indicates the
column being formatted.
"""
def setfloatfmt(treeviewcolumn, cellrenderer, treemodel, treeiter):
cellrenderer.Text = floatfmt.format(treemodel.GetValue(treeiter, index))
# If it is one of the assay value columns.
# XXX - not generic.
if index > 2:
if treemodel.GetValue(treeiter, index) > BLAZINGCUTOFF:
cellrenderer.Foreground = 'red'
else:
cellrenderer.Foreground = 'black'
return Gtk.TreeCellDataFunc(setfloatfmt)
class TreeViewTest(object):
def __init__(self):
Gtk.Application.Init()
self.window = Gtk.Window('')
# DeleteEvent - copied from Gtk demo on internet.
self.window.DeleteEvent += self.DeleteEvent
# Frame property provides a frame and title.
self.frame = Gtk.Frame(MARKEDUPTITLE)
self.tree = Gtk.TreeView()
self.tree.EnableGridLines = Gtk.TreeViewGridLines.Both
self.frame.Add(self.tree)
# Fonts for formatting.
self.fdregular = Pango.FontDescription.FromString(COURFONTREGULAR)
self.fdbold = Pango.FontDescription.FromString(COURFONTBOLD)
# C# module
self.store = storex().drillhole
self.makecolumns()
self.adddata()
self.tree.Model = self.store
self.formatcolumns()
self.formatcells()
self.prettyup()
self.window.Add(self.frame)
self.window.ShowAll()
# Keep text viewable - size no smaller than intended.
self.window.AllowShrink = False
# XXX - hack to keep lack of gridlines on edges of
# table from showing.
self.window.AllowGrow = False
# Unselect everything for this demo.
self.tree.Selection.UnselectAll()
Gtk.Application.Run()
def makecolumns(self):
"""
Fill in columns for TreeView.
"""
self.columns = {}
for fieldx in FIELDS:
self.columns[fieldx] = Gtk.TreeViewColumn()
self.columns[fieldx].Title = fieldx
self.tree.AppendColumn(self.columns[fieldx])
def formatcolumns(self):
"""
Make custom labels for columnn headers.
Get each column properly justified (all
are right justified,floating point numbers
except for the drillhole 'number' -
actually a string).
"""
self.customlabels = {}
for fieldx in FIELDS:
# This centers the labels at the top.
self.columns[fieldx].Alignment = CENTERED
self.customlabels[fieldx] = Gtk.Label(self.columns[fieldx].Title)
self.customlabels[fieldx].ModifyFont(self.fdbold)
# 120 is about right for from, to, and assay columns.
self.columns[fieldx].MinWidth = 120
self.customlabels[fieldx].ShowAll()
self.columns[fieldx].Widget = self.customlabels[fieldx]
# ShowAll required for new label to take.
self.columns[fieldx].Widget.ShowAll()
def formatcells(self):
"""
Add and format cell renderers.
"""
self.cellrenderers = {}
for fieldx in FIELDS:
self.cellrenderers[fieldx] = Gtk.CellRendererText()
self.columns[fieldx].PackStart(self.cellrenderers[fieldx], True)
# Drillhole 'number' (string)
if fieldx == FIELDS[0]:
self.cellrenderers[fieldx].Xalign = CENTERED
self.columns[fieldx].AddAttribute(self.cellrenderers[fieldx],
'text', 0)
else:
self.cellrenderers[fieldx].Xalign = RIGHT
try:
self.columns[fieldx].AddAttribute(self.cellrenderers[fieldx],
'text', FIELDS.index(fieldx))
except ValueError:
print('\n\nProblem with field definitions; field not found.\n\n')
for fieldx in BOLDEDCOLUMNS:
self.cellrenderers[fieldx].Font = COURFONTBOLD
self.columns[fieldx].Widget.ShowAll()
# XXX - not very generic, but better than doing them one by one.
# from, to columns.
for x in xrange(1, 3):
self.columns[FIELDS[x]].SetCellDataFunc(self.cellrenderers[FIELDS[x]],
genericfloatformat(FP1FMT, x))
# assay<x> columns.
for x in xrange(3, 7):
self.columns[FIELDS[x]].SetCellDataFunc(self.cellrenderers[FIELDS[x]],
genericfloatformat(FP2FMT, x))
def usemarkup(self):
"""
Refreshes UseMarkup property on widgets (labels)
so that they display properly and without
markup text.
"""
# Have to refresh this property each time.
self.frame.LabelWidget.UseMarkup = True
def prettyup(self):
"""
Get Gtk objects looking the way we
intended.
"""
# Try to get Courier New on treeview.
self.tree.ModifyFont(self.fdregular)
# Get rid of line.
self.frame.Shadow = Gtk.ShadowType.None
self.usemarkup()
def adddata(self):
"""
Put data into store.
"""
# XXX - difficulty figuring out sorting
# function for TreeView. Hack it
# with dictionary here.
keytuples = [key for key in DHDATAX]
keytuples.sort()
datax = []
for tuplex in keytuples:
# XXX - side effect comprehension.
# Not great for readability,
# but compact.
[datax.append(x) for x in tuplex]
for fieldx in NONKEYFIELDS:
datax.append(DHDATAX[tuplex][fieldx])
self.store.AppendValues(*datax)
# Reinitiialize data row list.
datax = []
def DeleteEvent(self, widget, event):
Gtk.Application.Quit()
if __name__ == '__main__':
TreeViewTest()
Thanks for stopping by.
Thursday, October 30, 2014
Mono gtk-sharp IronPython CalendarView
A number of years ago I did a post on the IronPython Cookbook site about the Windows.Forms Calendar control. I could never get the thing to render nicely on *nix operating systems (BSD family). It sounds as though Windows.Forms development for mono (and in general) is kind of dead, so there is not much hope that solution/example will ever render nicely on *nix. Recently I've been playing with mono and decided to give gtk-sharp a shot with IronPython.
Quick disclaimers:
1) I suspect from the examples I've seen on the internet that PyGtk is a little easier to deal with than gtk-sharp. That's OK; I wanted to use IronPython and have the rest of the mono/dotNet framework available, so I went through the extra trouble to forego CPython and PyGtk and go with IronPython and gtk-sharp instead.
2) The desktop is not the most cutting edge or sexy platform in 2014. Nonetheless, where I work it is alive and well. When I no longer see engineers hacking solutions in Excel and VBA, I'll consider the possibility of outliving the desktop. Right now I'm not hopeful :-\
The results aren't bad, at least as far as rendering goes. I couldn't get the Courier font to take on OpenBSD, but the Gtk Calendar control looks acceptable. All in all, I was OK with the results on both Windows and OpenBSD. I've heard Gtk doesn't do quite as well on Apple products, but I don't own a Mac to test with. Here are a couple screenshots:
I run the cwm window manager on OpenBSD and have it set up to cut out borders on windows, hence the more minimalist look to the control there.
IronPython output on *nix has always come out in yellow or white - it doesn't show up on a white background, which I prefer. In order to get around this, I run an xterm with a black background:
xterm -bg black -fg white
Here is the code for the gtk-sharp Gtk.Calendar control:
#!/usr/local/bin/mono /home/carl/IronPython-2.7.4/ipy64.exe
import clr
GTKSHARP = 'gtk-sharp'
PANGO = 'pango-sharp'
clr.AddReference(GTKSHARP)
clr.AddReference(PANGO)
import Gtk
import Pango
import datetime
TITLE = 'Gtk.Calendar Demo'
MARKUP = '<span font="Courier New" size="14" weight="bold">{:s}</span>'
MARKEDUPTITLE = MARKUP.format(TITLE)
INFOMSG = '<span font="Courier New 12">\n\n Program set to run for:\n\n '
INFOMSG += '{:%Y-%m-%d}\n\n</span>'
DATEDIFFMSG = '<span font="Courier New 12">\n\n '
DATEDIFFMSG += 'There are {0:d} days between the\n'
DATEDIFFMSG += ' beginning of the epoch and\n'
DATEDIFFMSG += ' {1:%Y-%m-%d}.\n\n</span>'
ALIGNMENTPARAMS = (0.0, 0.5, 0.0, 0.0)
WINDOWWIDTH = 350
CALENDARFONT = 'Courier New Bold 12'
class CalendarTest(object):
inthebeginning = datetime.datetime.fromtimestamp(0)
# Debug info - make sure beginning of epoch really
# is +midnight, Jan 1, 1970 GMT.
print(inthebeginning)
def __init__(self):
Gtk.Application.Init()
self.window = Gtk.Window(TITLE)
# DeleteEvent - copied from Gtk demo on internet.
self.window.DeleteEvent += self.DeleteEvent
# Frame property provides a frame and title.
self.frame = Gtk.Frame(MARKEDUPTITLE)
self.calendar = Gtk.Calendar()
# Handles date selection event.
self.calendar.DaySelected += self.dateselect
# Sets up text for labels.
self.getcaltext()
# Puts little box around text.
self.datelabelframe = Gtk.Frame()
# Try to get datelabel to align with other label.
self.datelabelalignment = Gtk.Alignment(*ALIGNMENTPARAMS)
self.datelabel = Gtk.Label(self.caltext)
self.datelabelalignment.Add(self.datelabel)
self.datelabelframe.Add(self.datelabelalignment)
# Puts little box around text.
self.datedifflabelframe = Gtk.Frame()
self.datedifflabelalignment = Gtk.Alignment(*ALIGNMENTPARAMS)
self.datedifflabel = Gtk.Label(self.timedifftext)
self.datedifflabelalignment.Add(self.datedifflabel)
self.datedifflabelframe.Add(self.datedifflabelalignment)
self.vbox = Gtk.VBox()
self.vbox.PackStart(self.datelabelframe)
self.vbox.PackStart(self.datedifflabelframe)
self.vbox.PackStart(self.calendar)
self.frame.Add(self.vbox)
self.window.Add(self.frame)
self.prettyup()
self.window.ShowAll()
# Keep text viewable - size no smaller than intended.
self.window.AllowShrink = False
Gtk.Application.Run()
def getcaltext(self):
"""
Get messages for run date.
"""
# Calendar month is 0 based.
yearmonthday = self.calendar.Year, self.calendar.Month + 1, self.calendar.Day
chosendate = datetime.datetime(*yearmonthday)
self.caltext = INFOMSG.format(chosendate)
# For reporting of number of days since beginning of epoch.
timediff = chosendate - CalendarTest.inthebeginning
self.timedifftext = DATEDIFFMSG.format(timediff.days, chosendate)
def usemarkup(self):
"""
Refreshes UseMarkup property on widgets (labels)
so that they display properly and without
markup text.
"""
# Have to refresh this property each time.
self.frame.LabelWidget.UseMarkup = True
self.datelabel.UseMarkup = True
self.datedifflabel.UseMarkup = True
def prettyup(self):
"""
Get Gtk objects looking the way we
intended.
"""
# Try to make frame wider.
# XXX
# Works nicely on Windows - try on Unix.
# Allows bold, etc.
self.usemarkup()
self.frame.SetSizeRequest(WINDOWWIDTH, -1)
# Get rid of line in middle of text on title.
self.frame.Shadow = Gtk.ShadowType.None
# Try to get Courier New on calendar.
fd = Pango.FontDescription.FromString(CALENDARFONT)
self.calendar.ModifyFont(fd)
self.datelabel.Justify = Gtk.Justification.Left
self.datedifflabel.Justify = Gtk.Justification.Left
self.window.Title = ''
self.usemarkup()
def dateselect(self, widget, event):
self.getcaltext()
self.datelabel.Text = self.caltext
self.datedifflabel.Text = self.timedifftext
self.prettyup()
def DeleteEvent(self, widget, event):
Gtk.Application.Quit()
if __name__ == '__main__':
CalendarTest()
Thanks for stopping by.
Quick disclaimers:
1) I suspect from the examples I've seen on the internet that PyGtk is a little easier to deal with than gtk-sharp. That's OK; I wanted to use IronPython and have the rest of the mono/dotNet framework available, so I went through the extra trouble to forego CPython and PyGtk and go with IronPython and gtk-sharp instead.
2) The desktop is not the most cutting edge or sexy platform in 2014. Nonetheless, where I work it is alive and well. When I no longer see engineers hacking solutions in Excel and VBA, I'll consider the possibility of outliving the desktop. Right now I'm not hopeful :-\
The results aren't bad, at least as far as rendering goes. I couldn't get the Courier font to take on OpenBSD, but the Gtk Calendar control looks acceptable. All in all, I was OK with the results on both Windows and OpenBSD. I've heard Gtk doesn't do quite as well on Apple products, but I don't own a Mac to test with. Here are a couple screenshots:
I run the cwm window manager on OpenBSD and have it set up to cut out borders on windows, hence the more minimalist look to the control there.
IronPython output on *nix has always come out in yellow or white - it doesn't show up on a white background, which I prefer. In order to get around this, I run an xterm with a black background:
xterm -bg black -fg white
Here is the code for the gtk-sharp Gtk.Calendar control:
#!/usr/local/bin/mono /home/carl/IronPython-2.7.4/ipy64.exe
import clr
GTKSHARP = 'gtk-sharp'
PANGO = 'pango-sharp'
clr.AddReference(GTKSHARP)
clr.AddReference(PANGO)
import Gtk
import Pango
import datetime
TITLE = 'Gtk.Calendar Demo'
MARKUP = '<span font="Courier New" size="14" weight="bold">{:s}</span>'
MARKEDUPTITLE = MARKUP.format(TITLE)
INFOMSG = '<span font="Courier New 12">\n\n Program set to run for:\n\n '
INFOMSG += '{:%Y-%m-%d}\n\n</span>'
DATEDIFFMSG = '<span font="Courier New 12">\n\n '
DATEDIFFMSG += 'There are {0:d} days between the\n'
DATEDIFFMSG += ' beginning of the epoch and\n'
DATEDIFFMSG += ' {1:%Y-%m-%d}.\n\n</span>'
ALIGNMENTPARAMS = (0.0, 0.5, 0.0, 0.0)
WINDOWWIDTH = 350
CALENDARFONT = 'Courier New Bold 12'
class CalendarTest(object):
inthebeginning = datetime.datetime.fromtimestamp(0)
# Debug info - make sure beginning of epoch really
# is +midnight, Jan 1, 1970 GMT.
print(inthebeginning)
def __init__(self):
Gtk.Application.Init()
self.window = Gtk.Window(TITLE)
# DeleteEvent - copied from Gtk demo on internet.
self.window.DeleteEvent += self.DeleteEvent
# Frame property provides a frame and title.
self.frame = Gtk.Frame(MARKEDUPTITLE)
self.calendar = Gtk.Calendar()
# Handles date selection event.
self.calendar.DaySelected += self.dateselect
# Sets up text for labels.
self.getcaltext()
# Puts little box around text.
self.datelabelframe = Gtk.Frame()
# Try to get datelabel to align with other label.
self.datelabelalignment = Gtk.Alignment(*ALIGNMENTPARAMS)
self.datelabel = Gtk.Label(self.caltext)
self.datelabelalignment.Add(self.datelabel)
self.datelabelframe.Add(self.datelabelalignment)
# Puts little box around text.
self.datedifflabelframe = Gtk.Frame()
self.datedifflabelalignment = Gtk.Alignment(*ALIGNMENTPARAMS)
self.datedifflabel = Gtk.Label(self.timedifftext)
self.datedifflabelalignment.Add(self.datedifflabel)
self.datedifflabelframe.Add(self.datedifflabelalignment)
self.vbox = Gtk.VBox()
self.vbox.PackStart(self.datelabelframe)
self.vbox.PackStart(self.datedifflabelframe)
self.vbox.PackStart(self.calendar)
self.frame.Add(self.vbox)
self.window.Add(self.frame)
self.prettyup()
self.window.ShowAll()
# Keep text viewable - size no smaller than intended.
self.window.AllowShrink = False
Gtk.Application.Run()
def getcaltext(self):
"""
Get messages for run date.
"""
# Calendar month is 0 based.
yearmonthday = self.calendar.Year, self.calendar.Month + 1, self.calendar.Day
chosendate = datetime.datetime(*yearmonthday)
self.caltext = INFOMSG.format(chosendate)
# For reporting of number of days since beginning of epoch.
timediff = chosendate - CalendarTest.inthebeginning
self.timedifftext = DATEDIFFMSG.format(timediff.days, chosendate)
def usemarkup(self):
"""
Refreshes UseMarkup property on widgets (labels)
so that they display properly and without
markup text.
"""
# Have to refresh this property each time.
self.frame.LabelWidget.UseMarkup = True
self.datelabel.UseMarkup = True
self.datedifflabel.UseMarkup = True
def prettyup(self):
"""
Get Gtk objects looking the way we
intended.
"""
# Try to make frame wider.
# XXX
# Works nicely on Windows - try on Unix.
# Allows bold, etc.
self.usemarkup()
self.frame.SetSizeRequest(WINDOWWIDTH, -1)
# Get rid of line in middle of text on title.
self.frame.Shadow = Gtk.ShadowType.None
# Try to get Courier New on calendar.
fd = Pango.FontDescription.FromString(CALENDARFONT)
self.calendar.ModifyFont(fd)
self.datelabel.Justify = Gtk.Justification.Left
self.datedifflabel.Justify = Gtk.Justification.Left
self.window.Title = ''
self.usemarkup()
def dateselect(self, widget, event):
self.getcaltext()
self.datelabel.Text = self.caltext
self.datedifflabel.Text = self.timedifftext
self.prettyup()
def DeleteEvent(self, widget, event):
Gtk.Application.Quit()
if __name__ == '__main__':
CalendarTest()
Thanks for stopping by.
Labels:
#Gtk,
#gtk-sharp,
#Gtk.Calendar,
#IronPython,
#mono
Subscribe to:
Posts (Atom)


