FieldGraph¶
A PyObjC Example without documentation
Sources¶
CGraphController.py¶
import Cocoa
import objc
from fieldMath import degToRad, radToDeg
# ____________________________________________________________
class CGraphController(Cocoa.NSObject):
graphModel = objc.IBOutlet()
graphView = objc.IBOutlet()
fieldNormalizeCheck = objc.IBOutlet()
settingDrawer = objc.IBOutlet()
fieldSlider0 = objc.IBOutlet()
fieldSlider1 = objc.IBOutlet()
fieldSlider2 = objc.IBOutlet()
phaseSlider0 = objc.IBOutlet()
phaseSlider1 = objc.IBOutlet()
phaseSlider2 = objc.IBOutlet()
spacingSlider = objc.IBOutlet()
fieldDisplay0 = objc.IBOutlet()
fieldDisplay1 = objc.IBOutlet()
fieldDisplay2 = objc.IBOutlet()
phaseDisplay0 = objc.IBOutlet()
phaseDisplay1 = objc.IBOutlet()
phaseDisplay2 = objc.IBOutlet()
RMSGainDisplay = objc.IBOutlet()
spacingDisplay = objc.IBOutlet()
# ____________________________________________________________
# Update GUI display and control values
def awakeFromNib(self):
self.mapImage = Cocoa.NSImage.imageNamed_("Map")
self.graphView.setMapImage(self.mapImage)
self.drawGraph()
def drawGraph(self):
self.spacingDisplay.setFloatValue_(radToDeg(self.graphModel.getSpacing()))
self.spacingSlider.setFloatValue_(radToDeg(self.graphModel.getSpacing()))
self.fieldDisplay0.setFloatValue_(self.graphModel.getField(0))
self.fieldDisplay1.setFloatValue_(self.graphModel.getField(1))
self.fieldDisplay2.setFloatValue_(self.graphModel.getField(2))
self.fieldSlider0.setFloatValue_(self.graphModel.getField(0))
self.fieldSlider1.setFloatValue_(self.graphModel.getField(1))
self.fieldSlider2.setFloatValue_(self.graphModel.getField(2))
self.phaseDisplay0.setFloatValue_(radToDeg(self.graphModel.getPhase(0)))
self.phaseDisplay1.setFloatValue_(radToDeg(self.graphModel.getPhase(1)))
self.phaseDisplay2.setFloatValue_(radToDeg(self.graphModel.getPhase(2)))
self.phaseSlider0.setFloatValue_(radToDeg(self.graphModel.getPhase(0)))
self.phaseSlider1.setFloatValue_(radToDeg(self.graphModel.getPhase(1)))
self.phaseSlider2.setFloatValue_(radToDeg(self.graphModel.getPhase(2)))
totalField = (
self.graphModel.getField(0)
+ self.graphModel.getField(1)
+ self.graphModel.getField(2)
)
RMSGain = self.graphModel.fieldGain()
self.graphView.setGain(RMSGain, totalField)
self.RMSGainDisplay.setFloatValue_(RMSGain * 100.0)
path, maxMag = self.graphModel.getGraph()
self.graphView.setPath(path, maxMag)
# ____________________________________________________________
# Handle GUI values
@objc.IBAction
def fieldDisplay0_(self, sender):
self.setNormalizedField(0, sender.floatValue())
self.drawGraph()
@objc.IBAction
def fieldDisplay1_(self, sender):
self.setNormalizedField(1, sender.floatValue())
self.drawGraph()
@objc.IBAction
def fieldDisplay2_(self, sender):
self.setNormalizedField(2, sender.floatValue())
self.drawGraph()
@objc.IBAction
def fieldSlider0_(self, sender):
self.setNormalizedField(0, sender.floatValue())
self.drawGraph()
@objc.IBAction
def fieldSlider1_(self, sender):
self.setNormalizedField(1, sender.floatValue())
self.drawGraph()
@objc.IBAction
def fieldSlider2_(self, sender):
self.setNormalizedField(2, sender.floatValue())
self.drawGraph()
@objc.python_method
def setNormalizedField(self, t, v):
if self.fieldNormalizeCheck.intValue():
f = [0, 0, 0]
cft = 0
for i in range(3):
f[i] = self.graphModel.getField(i)
cft += f[i]
aft = cft - v
if aft < 0.001:
v = cft - 0.001
aft = 0.001
f[t] = v
nft = 0
for i in range(3):
nft += f[i]
r = aft / (nft - f[t])
for i in range(3):
self.graphModel.setField(i, f[i] * r)
self.graphModel.setField(t, v)
else:
self.graphModel.setField(t, v)
@objc.IBAction
def phaseDisplay0_(self, sender):
self.graphModel.setPhase(0, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def phaseDisplay1_(self, sender):
self.graphModel.setPhase(1, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def phaseDisplay2_(self, sender):
self.graphModel.setPhase(2, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def phaseSlider0_(self, sender):
self.graphModel.setPhase(0, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def phaseSlider1_(self, sender):
self.graphModel.setPhase(1, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def phaseSlider2_(self, sender):
self.graphModel.setPhase(2, degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def spacingDisplay_(self, sender):
self.graphModel.setSpacing(degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def spacingSlider_(self, sender):
self.graphModel.setSpacing(degToRad(sender.floatValue()))
self.drawGraph()
@objc.IBAction
def settingDrawerButton_(self, sender):
self.settingDrawer.toggle_(self)
CGraphModel.py¶
from math import cos, hypot, pi, sin, sqrt
import objc
from AppKit import NSBezierPath
from fieldMath import bessel, degToRad, polarToRect
from Foundation import NSObject
# ____________________________________________________________
class CGraphModel(NSObject):
def init(self):
self.field = [1.0, 1.12, 0.567]
self.phase = [degToRad(0), degToRad(152.6), degToRad(312.9 - 360)]
self.RMSGain = 0
self.spacing = degToRad(90)
return self
def getGraph(self):
path = NSBezierPath.bezierPath()
maxMag = 0
mag = self.fieldValue(0)
maxMag = max(maxMag, mag)
path.moveToPoint_(polarToRect((mag, 0)))
for deg in range(1, 359, 1):
r = (deg / 180.0) * pi
mag = self.fieldValue(r)
maxMag = max(maxMag, mag)
path.lineToPoint_(polarToRect((mag, r)))
path.closePath()
return path, maxMag
@objc.python_method
def fieldGain(self):
gain = 0
Et = self.field[0] + self.field[1] + self.field[2]
if Et: # Don't want to divide by zero in the pathological case
spacing = [0, self.spacing, 2 * self.spacing]
# This could easily be optimized--but this is just anexample :-)
for i in range(3):
for j in range(3):
gain += (
self.field[i]
* self.field[j]
* cos(self.phase[j] - self.phase[i])
* bessel(spacing[j] - spacing[i])
)
gain = sqrt(gain) / Et
self.RMSGain = gain
return gain
@objc.python_method
def fieldValue(self, a):
# The intermedate values are used to more closely match
# standard field equations nomenclature
E0 = self.field[0]
E1 = self.field[1]
E2 = self.field[2]
B0 = self.phase[0]
B1 = self.phase[1] + self.spacing * cos(a)
B2 = self.phase[2] + 2 * self.spacing * cos(a)
phix = sin(B0) * E0 + sin(B1) * E1 + sin(B2) * E2
phiy = cos(B0) * E0 + cos(B1) * E1 + cos(B2) * E2
mag = hypot(phix, phiy)
return mag
@objc.python_method
def setField(self, tower, field):
self.field[tower] = field
@objc.python_method
def getField(self, tower):
return self.field[tower]
@objc.python_method
def setPhase(self, tower, phase):
self.phase[tower] = phase
@objc.python_method
def getPhase(self, tower):
return self.phase[tower]
@objc.python_method
def setSpacing(self, spacing):
self.spacing = spacing
@objc.python_method
def getSpacing(self):
return self.spacing
CGraphView.py¶
from math import cos, pi, sin
import Cocoa
import objc
from fieldMath import degToRad
from objc import super # noqa: A004
# Convenience global variables
x, y = 0, 1
llc, sze = 0, 1 # Left Lower Corner, Size
BLACK = Cocoa.NSColor.blackColor()
BLUE = Cocoa.NSColor.blueColor()
GREEN = Cocoa.NSColor.greenColor()
class CGraphView(Cocoa.NSView):
azmuthSlider = objc.IBOutlet()
mapOffsetEWSlider = objc.IBOutlet()
mapOffsetNSSlider = objc.IBOutlet()
mapScaleSlider = objc.IBOutlet()
mapVisibleSlider = objc.IBOutlet()
azmuthDisplay = objc.IBOutlet()
mapOffsetEWDisplay = objc.IBOutlet()
mapOffsetNSDisplay = objc.IBOutlet()
mapScaleDisplay = objc.IBOutlet()
def initWithFrame_(self, frame):
super().initWithFrame_(frame)
self.setGridColor()
self.setRmsColor()
self.setGraphColor()
self.graphMargin = 2
self.mapImage = 0
self.mapRect = 0
self.mapVisible = 0.70
self.mapScale = 3.0
self.mapOffsetEW = 0.27
self.mapOffsetNS = 0.40
self.mapBaseRadius = 200
self.lines = 2
self.gain = 0.5
return self
def awakeFromNib(self):
self.setCrossCursor()
self.mapVisibleSlider.setFloatValue_(self.mapVisible)
self.setAzmuth_(125)
self.setMapRect()
@objc.python_method
def setCrossCursor(self):
crosshairImage = Cocoa.NSImage.imageNamed_("CrossCursor")
self.crossCursor = Cocoa.NSCursor.alloc().initWithImage_hotSpot_(
crosshairImage, (8, 8)
)
self.trackingRect = self.addTrackingRect_owner_userData_assumeInside_(
self.bounds(), self, 0, 0
)
@objc.python_method
def setGridColor(self, color=GREEN):
self.gridColor = color
@objc.python_method
def setRmsColor(self, color=BLUE):
self.rmsColor = color
@objc.python_method
def setGraphColor(self, color=BLACK):
self.graphColor = color
@objc.python_method
def setGain(self, gain, total):
self.gain = gain
self.totalField = total
@objc.python_method
def setLines(self, lines):
self.lines = lines
@objc.python_method
def setMapImage(self, mapImage):
self.mapImage = mapImage
self.mapRect = ((0, 0), mapImage.size())
@objc.python_method
def setPath(self, path, maxMag):
self.path = path
self.maxMag = maxMag
self.setNeedsDisplay_(1)
def drawRect_(self, rect):
frame = self.frame()
self.origin = frame[0]
self.graphCenter = (frame[sze][x] / 2, frame[sze][y] / 2)
self.graphRadius = (min(frame[sze][x], frame[sze][y]) / 2) - self.graphMargin
Cocoa.NSColor.whiteColor().set()
Cocoa.NSRectFill(self.bounds())
self.drawMap()
self.drawGrid()
self.drawRMS()
self.drawField()
def drawMap(self):
if self.mapImage == 0:
return
scale = (
self.mapScale
* (self.graphRadius / self.mapBaseRadius)
* self.gain
/ self.totalField
)
xImageSize = scale * self.mapRect[sze][x]
yImageSize = scale * self.mapRect[sze][y]
xCenterMove = self.graphCenter[x] - self.graphRadius
yCenterMove = self.graphCenter[y] - self.graphRadius
xOffset = -((1 - self.mapOffsetEW) / 2) * xImageSize
yOffset = -((1 + self.mapOffsetNS) / 2) * yImageSize
xOffset += self.graphRadius + xCenterMove
yOffset += self.graphRadius + yCenterMove
drawInRect = ((xOffset, yOffset), (xImageSize, yImageSize))
self.mapImage.drawInRect_fromRect_operation_fraction_(
drawInRect, self.mapRect, Cocoa.NSCompositeSourceOver, self.mapVisible
)
def drawGrid(self):
self.gridColor.set()
self.drawCircle_(1.0)
self.drawAxisLines()
def drawCircle_(self, scale):
center = self.graphCenter
radius = self.graphRadius * scale
x, y = 0, 1
if radius >= 1:
dotRect = (
(center[x] - radius, center[y] - radius),
(2 * radius, 2 * radius),
)
path = Cocoa.NSBezierPath.bezierPathWithOvalInRect_(dotRect)
path.stroke()
def drawRMS(self):
self.rmsColor.set()
self.drawCircle_(self.gain)
def drawAxisLines(self):
center = self.graphCenter
radius = self.graphRadius
x, y = 0, 1
path = Cocoa.NSBezierPath.bezierPath()
for i in range(1, self.lines + 1):
iR = pi / i
cosR = cos(iR) * radius
sinR = sin(iR) * radius
path.moveToPoint_((center[x] - cosR, center[y] - sinR))
path.lineToPoint_((center[x] + cosR, center[y] + sinR))
path.closePath()
path.stroke()
def drawField(self):
if self.maxMag: # Don't want to divide by zero in the pathological case
self.graphColor.set()
path = self.path.copy()
transform = Cocoa.NSAffineTransform.transform()
transform.rotateByRadians_(-(pi / 2.0) - self.azmuth)
path.transformUsingAffineTransform_(transform)
transform = Cocoa.NSAffineTransform.transform()
center = self.graphCenter
transform.translateXBy_yBy_(center[0], center[1])
transform.scaleBy_(self.graphRadius / self.maxMag)
path.transformUsingAffineTransform_(transform)
path.stroke()
# ____________________________________________________________
# Handle GUI values
@objc.IBAction
def mapVisibleSlider_(self, sender):
self.mapVisible = sender.floatValue()
self.setNeedsDisplay_(1)
@objc.IBAction
def azmuthDisplay_(self, sender):
self.setAzmuth_(sender.floatValue())
@objc.IBAction
def azmuthSlider_(self, sender):
self.setAzmuth_(sender.floatValue())
def setAzmuth_(self, value):
self.azmuth = degToRad(value)
self.azmuthSlider.setFloatValue_(value)
self.azmuthDisplay.setFloatValue_(value)
self.setNeedsDisplay_(1)
@objc.IBAction
def mapScaleDisplay_(self, sender):
self.mapScale = sender.floatValue()
self.setMapRect()
@objc.IBAction
def mapScaleSlider_(self, sender):
self.mapScale = sender.floatValue()
self.setMapRect()
@objc.IBAction
def mapOffsetNSDisplay_(self, sender):
self.mapOffsetNS = sender.floatValue()
self.setMapRect()
@objc.IBAction
def mapOffsetNSSlider_(self, sender):
self.mapOffsetNS = sender.floatValue()
self.setMapRect()
@objc.IBAction
def mapOffsetEWDisplay_(self, sender):
self.mapOffsetEW = sender.floatValue()
self.setMapRect()
@objc.IBAction
def mapOffsetEWSlider_(self, sender):
self.mapOffsetEW = sender.floatValue()
self.setMapRect()
def mouseUp_(self, event):
loc = event.locationInWindow()
xLoc = loc[x] - self.origin[x]
yLoc = loc[y] - self.origin[y]
xDelta = self.graphCenter[x] - xLoc
yDelta = self.graphCenter[y] - yLoc
scale = (
0.5
* self.mapScale
* (self.gain / self.totalField)
* (self.graphRadius / self.mapBaseRadius)
)
xOffset = xDelta / (scale * self.mapRect[sze][x])
yOffset = yDelta / (scale * self.mapRect[sze][y])
self.mapOffsetEW += xOffset
self.mapOffsetNS -= yOffset
self.setMapRect()
def mouseDown_(self, event):
self.crossCursor.set()
def setMapRect(self):
self.mapScaleDisplay.setFloatValue_(self.mapScale)
self.mapScaleSlider.setFloatValue_(self.mapScale)
self.mapOffsetEWDisplay.setFloatValue_(self.mapOffsetEW)
self.mapOffsetEWSlider.setFloatValue_(self.mapOffsetEW)
self.mapOffsetNSDisplay.setFloatValue_(self.mapOffsetNS)
self.mapOffsetNSSlider.setFloatValue_(self.mapOffsetNS)
self.setNeedsDisplay_(1)
def mouseEntered_(self, event):
print("CGraphView: mouseEntered_")
def mouseExited_(self, event):
print("CGraphView: mouseExited_")
Main.py¶
import CGraphController # noqa: F401
import CGraphModel # noqa: F401
import CGraphView # noqa: F401
from PyObjCTools import AppHelper
AppHelper.runEventLoop()
fieldMath.py¶
from math import cos, pi, sin
# Math functions
def degToRad(deg):
return (deg / 180.0) * pi
def radToDeg(rad):
return (rad / pi) * 180.0
def polarToRect(polar):
r = polar[0]
theta = polar[1]
return (r * cos(theta), r * sin(theta))
def bessel(z, t=0.00001):
j = 1
jn = 1
zz4 = z * z / 4
for k in range(1, 100):
jn *= -1 * zz4 / (k * k)
j += jn
if jn < 0:
if jn > t:
break
else:
if jn < t:
break
return j
setup.py¶
"""
Script for building the example.
Usage:
python3 setup.py py2app
"""
from setuptools import setup
plist = {"CFBundleName": "FieldGraph"}
setup(
name="FieldGraph",
app=["Main.py"],
data_files=["English.lproj", "CrossCursor.tiff", "Map.png"],
options={"py2app": {"plist": plist}},
setup_requires=["py2app", "pyobjc-framework-Cocoa"],
)