[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Bkchem-user] Error while opening file
From: |
Beda Kosata |
Subject: |
Re: [Bkchem-user] Error while opening file |
Date: |
Sat, 28 Feb 2004 20:42:06 +0100 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040113 |
Hi Sebastian,
thanks for your email.
This is a bug that occurs when you are using Python 2.3. It should have
been fixed in 0.5.1, however I have in fact not included the fix into
the release (please don't ask me how is it possible :)
I will quickly release a 0.5.2 with the fix included.
I send you a modified classes.py file. Simply copy it over the original.
It should be able to read the corrupted files and will save them right
again.
Thanks again for the info and sorry for the problems
BEDA
Sebastian "Marduk" Pölsterl wrote:
Hi!
I use the latest version 0.5.1 and worked fine until I drawed a radical.
I can draw it and save the file, but when I try to load the savec SVG
following error appears:
<paste>
Error: 1
ValueError Exception in Tk callback
Function: <bound method BKchem.load_CDML of <main.BKchem instance at
0x00919170>> (type: <type 'instancemethod'>)
Args: ()
Traceback (innermost last):
File
"D:\PROGRA~1\Python23\Lib\site-packages\Pmw\Pmw_1_2\lib\PmwBase.py",
line 1747, in __call__
return apply(self.func, args)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\main.py", line 356, in
load_CDML
if not self._load_CDML( file=file):
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\main.py", line 380, in
_load_CDML
return self._load_CDML_file( a)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\main.py", line 446, in
_load_CDML_file
self.paper.read_package( doc)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\paper.py", line 402, in
read_package
o = self.add_object_from_package( p)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\paper.py", line 663, in
add_object_from_package
o = classes.molecule( self, package=package)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\classes.py", line 81, in
__init__
self.read_package( package)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\classes.py", line 289,
in read_package
self.insert_atom( atom( self.paper, package=a, molecule=self))
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\classes.py", line 506,
in __init__
self.read_package( package)
File "D:\WEBDESIGN\python\bkchem-0.5.1\bkchem\classes.py", line 849,
in read_package
auto= int(auto))
ValueError: invalid literal for int(): True
</paste>
Is this a bug or is it my fault?
Greetings,
Sebastian Pölsterl
_______________________________________________
Bkchem-user mailing list
address@hidden
http://mail.nongnu.org/mailman/listinfo/bkchem-user
#--------------------------------------------------------------------------
# This file is part of BKchem - a chemical drawing program
# Copyright (C) 2002, 2003 Beda Kosata <address@hidden>
# 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.
# Complete text of GNU GPL can be found in the file gpl.txt in the
# main directory of the program
#--------------------------------------------------------------------------
#
# Last edited: $Date: 2004/02/10 21:31:53 $
#
#--------------------------------------------------------------------------
"""set of basic classes such as atom, bond, molecule, text etc."""
from __future__ import division
from __future__ import generators
from math import atan2, sin, cos, pi, sqrt
import misc
import time
import geometry
from warnings import warn
from ftext import ftext
import dom_extensions
import xml.dom.minidom as dom
import operator
import tkFont
import data
import periodic_table as PT
import groups_table as GT
import copy
import helper_graphics as hg
import marks
from parents import meta_enabled, simple_parent
### NOTE: now that all classes are children of meta_enabled, so the
read_standard_values method
### is called during their __init__ (in fact meta_enabled.__init__), therefor
these values are
### not set in __init__ itself
class molecule( simple_parent):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ
object_type = 'molecule'
# other meta infos
meta__is_container = 1
# undo meta infos
meta__undo_simple = ('name','id')
meta__undo_copy = ('atoms_map', 'bonds')
meta__undo_2d_copy = ('connect',)
meta__undo_children_to_record = ('atoms_map','bonds')
def __init__( self, paper, package = None):
self.paper = paper
self.atoms_map = [] # list of atoms
self.bonds = [] # list of bonds
self.connect = [] # matrix that shows conectivity of each atom
self.sign = -1
self.name = ''
self.id = ''
self._iterator = 0
self.t_bond_first = None # template
self.t_bond_second = None
self.t_atom = None
self.focus_item = None
if package:
self.read_package( package)
def __iter__( self):
return self
def next( self):
i = self._iterator
self._iterator += 1
try:
return self.atoms_map[ i]
except IndexError:
try:
return self.bonds[ i - len( self.atoms_map)]
except IndexError:
self._iterator = 0
raise StopIteration
def feed_data( self, atoms_map, bonds, connect):
"feed data from another molecule"
self.bonds += bonds
self.atoms_map += atoms_map
for line in self.connect:
line.extend( len(connect)*[0])
l = len( self.connect)
for i in range( len( connect)):
self.connect.append( l*[0])
self.connect[l+i].extend( connect[i])
map( lambda o: o.set_molecule( self), self)
def eat_molecule( self, mol):
"transfers everything from mol to self, now only calls feed_data"
self.feed_data( mol.atoms_map, mol.bonds, mol.connect)
def add_atom_to( self, a1, pos = None, bond_type=1):
"""adds new atom bound to atom id with bond, the position of new atom can
be specified in pos or is
decided calling find_place(),"""
if pos != None:
x, y = pos
else:
x, y = self.find_place( a1, self.paper.any_to_px(
self.paper.standard.bond_length))
a2 = self.create_new_atom( x, y)
b = self.create_new_bond( a1, a2, bond_type=bond_type)
return a2, b
def insert_bond( self, b):
self.connect[ self.atoms_map.index( b.atom1)][ self.atoms_map.index(
b.atom2)] = b
self.connect[ self.atoms_map.index( b.atom2)][ self.atoms_map.index(
b.atom1)] = b
self.bonds.append( b)
if not b.molecule:
b.set_molecule( self)
def find_place( self, a, distance):
"""tries to find accurate place for next atom around atom 'id',
returns x,y and list of ids of 'items' found there for overlap, those atoms
are not bound to id"""
ids_bonds = self.atoms_bound_to( a)
if len( ids_bonds) == 0:
x = a.get_x() + round( cos( pi/6) *distance)
y = a.get_y() - round( sin( pi/6) *distance)
elif len( ids_bonds) == 1:
x = a.get_x() + round( cos( self.get_angle( a, ids_bonds[0])
+self.sign*2*pi/3) *distance)
y = a.get_y() + round( sin( self.get_angle( a, ids_bonds[0])
+self.sign*2*pi/3) *distance)
self.sign = -self.sign
else:
x, y = self.find_least_crowded_place_around_atom( a, range=distance)
return x, y
def atoms_bonds( self, a):
return filter( lambda o: o, self.connect[ self.atoms_map.index( a)])
def atoms_bound_to( self, a):
"returns list of ids of atoms bound to atom with id = 'id'"
bonds = self.connect[ self.atoms_map.index( a)]
ret = []
for i in bonds:
if i:
ret.append( self.atoms_map[ bonds.index( i)])
return ret
def get_angle( self, a1, a2):
"what is the angle between horizontal line through i1 and i1-i2 line"
a = a2.get_x() - a1.get_x()
b = a2.get_y() - a1.get_y()
return atan2( b, a)
def delete_items( self, items):
"""deletes items and also makes cleaning of orphan bonds and atoms"""
if not items:
return items, [] # quick way to avoid costly evaluation
deleted = []+items
map( self.delete_bond, filter( lambda o: o.object_type == 'bond', items))
map( self.delete_atom, filter( lambda o: o.object_type == 'atom', items))
if self.connect:
# delete bonds that are not in connect anymore
bonds_in_connect = misc.filter_unique( reduce( lambda a,b: a+b,
self.connect))
deleted += map( self.delete_bond, filter( lambda o: o not in
bonds_in_connect, self.bonds))
# delete also orphan atoms
deleted += map( self.delete_atom, filter( lambda o: not self.atoms_bonds(
o), self.atoms_map))
# recalculation of second line of double bond position
[o.redraw( recalc_side=1) for o in self.bonds if o.type == 2 and o.item]
# recalculate marks positions
[o.reposition_marks() for o in self.atoms_map]
else:
deleted += map( self.delete_bond, self.bonds)
return deleted, self.check_integrity()
def delete_bond( self, item):
for i in range( len( self.connect)):
if item in self.connect[i]:
j = self.connect[i].index( item)
self.connect[i][j] = 0
self.connect[j][i] = 0
break
item.delete()
self.bonds.remove( item)
return item
def delete_atom( self, item):
"remove links to atom from molecule records"
del self.connect[ self.atoms_map.index( item)]
for i in range( len( self.connect)):
del self.connect[i][ self.atoms_map.index( item)]
self.atoms_map.remove( item)
item.delete()
if item == self.t_atom:
t_atom = None
if item == self.t_bond_first:
t_bond_first = None
if item == self.t_bond_second:
t_bond_second = None
return item
def create_new_atom( self, x, y):
a = atom( self.paper, xy=(x, y))
self.insert_atom( a)
a.draw()
return a
def insert_atom( self, at):
"inserts atom to molecule without any connections"
if len( self.connect):
for i in range( len( self.connect)):
self.connect[i].append( 0)
self.connect.append( (len( self.connect)+1) *[0])
else:
self.connect = [[0]]
self.atoms_map.append( at)
at.set_molecule( self)
def create_new_bond( self, a1, a2, bond_type=1):
b = bond( self.paper, atoms=(a1, a2), type=bond_type)
self.insert_bond( b)
b.draw()
return b
def check_integrity( self):
"""after deleting atoms or bonds it is important to see if it's needed to
divide molecule to fragments
and return them in form of list of new molecules"""
if self.connect == []:
return []
old_map = self.atoms_map[:]
# first distribute atoms to new_maps
def walker( a):
mmap.append( a)
for i in self.atoms_bound_to( a):
if i not in mmap:
walker( i)
new_maps = []
while old_map:
mmap = []
walker( old_map[0])
new_maps.append( mmap)
old_map = misc.difference( old_map, mmap)
if len( new_maps) == 1:
return []
# reorder connectivity matrixes
new_cons = []
for mmap in new_maps:
con = []
for i in range( len( mmap)):
con.append( len( mmap)*[0])
for i in range( len( mmap)):
for j in range( i+1, len( mmap)):
con[i][j] = self.connect[ self.atoms_map.index( mmap[i])][
self.atoms_map.index( mmap[j])]
con[j][i] = con[i][j]
new_cons.append( con)
#
new_bonds = []
for mol in new_cons:
new_bonds.append( misc.filter_unique( filter( lambda o: o, reduce( lambda
a,b: a+b, mol))))
ret = []
for i in range( len( new_cons)):
ret.append( molecule( self.paper))
ret[i].feed_data( new_maps[i], new_bonds[i], new_cons[i])
return ret
def is_empty( self):
return (self.connect == [])
def get_atoms( self):
return self.atoms_map
def get_bonds( self):
return self.bonds
def read_package( self, package):
self.name = package.getAttribute( 'name')
self.id = package.getAttribute( 'id')
for a in package.getElementsByTagName( 'atom'):
self.insert_atom( atom( self.paper, package=a, molecule=self))
self._id_map = map( lambda a: a.get_cdml_id(), self.atoms_map)
for b in package.getElementsByTagName( 'bond'):
self.insert_bond( bond( self.paper, package = b, molecule=self))
temp = package.getElementsByTagName('template')
if temp:
temp = temp[0]
self.t_atom = self.get_atom_with_cdml_id( temp.getAttribute( 'atom'))
if temp.getAttribute('bond_first') and temp.getAttribute('bond_second'):
self.t_bond_first = self.get_atom_with_cdml_id( temp.getAttribute(
'bond_first'))
self.t_bond_second = self.get_atom_with_cdml_id( temp.getAttribute(
'bond_second'))
self.next_to_t_atom = self.atoms_bound_to( self.t_atom)[0]
def get_package( self, doc):
mol = doc.createElement('molecule')
mol.setAttribute( 'name', self.name)
mol.setAttribute( 'id', self.id)
if self.t_atom:
if self.t_bond_second and self.t_bond_first:
dom_extensions.elementUnder( mol, 'template', ( ('atom', str(
self.t_atom.get_cdml_id())),
('bond_first', str(
self.t_bond_first.get_cdml_id())),
('bond_second', str(
self.t_bond_second.get_cdml_id()))))
else:
dom_extensions.elementUnder( mol, 'template', ( ('atom', str(
self.t_atom.get_cdml_id())),))
for i in self:
mol.appendChild( i.get_package( doc))
return mol
def draw( self):
map( lambda a: a.draw(), self)
def bond_between( self, a1, a2):
"returns id of bond between atoms i1 and i2"
return self.connect[ self.atoms_map.index( a1)][ self.atoms_map.index( a2)]
def handle_overlap( self):
"deletes one of overlaping atoms and updates the bonds"
to_delete = []
for a in self.atoms_map:
for b in self.atoms_map:
if a != b:
x1, y1 = a.get_xy()
x2, y2 = b.get_xy()
if (abs( x1-x2) < 4) and (abs( y1-y2) <4):
if a not in to_delete:
map( lambda x: x.change_atoms( b, a), self.atoms_bonds( b))
to_delete.append( b)
deleted = misc.filter_unique( to_delete)
map( self.delete_atom, deleted)
# after all is done, find and delete orphan bonds and update the others
for b in self.bonds[:]:
i1, i2 = map( self.atoms_map.index, b.get_atoms())
if self.connect[i1][i2] and self.connect[i1][i2] != b:
self.delete_bond( b)
deleted.append( b)
#self.bonds.remove( b)
elif not self.connect[i1][i2]:
self.connect[i1][i2] = b
self.connect[i2][i1] = b
for b in self.bonds:
b.redraw()
return deleted
def move( self, dx, dy):
"""moves the whole molecule"""
for o in self.atoms_map +self.bonds:
o.move( dx, dy)
def bbox( self):
items = []
for a in self.atoms_map:
items.append( a.item)
return self.paper.list_bbox( items)
def get_atom_with_cdml_id( self, id):
return self.atoms_map[ self._id_map.index( id)]
def set_atom_cdml_id( self, id, atom):
pass
def delete( self):
[o.delete() for o in self.bonds+self.atoms_map]
def redraw( self):
[o.redraw() for o in self.bonds+self.atoms_map]
def get_atoms_valency( self, atom):
val = 0
for b in self.atoms_bonds( atom):
if b.type == 1 or b.type == 4 or b.type == 5:
val += 1
else:
val += b.type
return val
def get_formula_dict( self):
comp = PT.formula_dict()
for a in self.atoms_map:
comp += a.get_formula_dict()
return comp
def expand_groups( self, atoms=[]):
"""expands all group atoms; optional atoms selects atoms to expand - all
used if not present"""
names = self.paper.gm.get_template_names()
if not atoms:
map = copy.copy( self.atoms_map) # need to do that because the
self.atoms_map gets changed during the cycle
else:
map = atoms # only selected atoms
for a in map:
if a.type == "group":
if a.name in names:
a2 = self.atoms_bound_to( a)[0]
x1, y1 = a2.get_xy()
x2, y2 = a.get_xy()
t = self.paper.gm.get_transformed_template( names.index( a.name),
(x1,y1,x2,y2), type='atom1')
t.draw()
self.eat_molecule( t)
self.move_bonds_between_atoms( a, t.next_to_t_atom)
self.delete_items( [a])
else:
print "unknown group %s" % a.name
elif a.type == "chain":
p = PT.formula_dict( a.name)
n = p['C']
a.set_name( 'C')
a.redraw()
for i in range( n-1):
a,b = self.add_atom_to( a) #,
bond_type=self.__mode_to_bond_type())[0]]
def move_bonds_between_atoms( self, a1, a2):
"""transfers all bonds from one atom to the other; both atoms must be in
self"""
for b in self.atoms_bonds( a1):
b.change_atoms( a1, a2)
i = self.atoms_map.index( a1)
l = self.atoms_map.index( a2)
for j in range( len( self.connect)):
for k in range( len( self.connect)):
if self.connect[j][k]:
if j == i:
self.connect[l][k] = self.connect[j][k]
self.connect[j][k] = 0
elif k == i:
self.connect[j][l] = self.connect[j][k]
self.connect[j][k] = 0
def lift( self):
[o.lift() for o in self.bonds + self.atoms_map]
def find_least_crowded_place_around_atom( self, a, range=10):
atms = self.atoms_bound_to( a)
x, y = a.get_xy()
angles = [geometry.clockwise_angle_from_east( at.x-a.x, at.y-a.y) for at in
atms]
angles.append( 2*pi + min( angles))
angles.sort()
angles.reverse()
diffs = misc.list_difference( angles)
i = diffs.index( max( diffs))
angle = (angles[i] +angles[i+1]) / 2
return x +range*cos( angle), y +range*sin( angle)
def get_shape_defining_children( self):
for i in self.atoms_map:
yield i
def flush_graph_to_file( self, name="/home/beda/oasa/graph/mol.graph"):
f = file( name, 'w')
for a in self.atoms_map:
f.write('v')
f.write('\n')
for b in self.bonds:
f.write('%d %d\n' % (self.atoms_map.index( b.atom1),
self.atoms_map.index( b.atom2)))
f.close()
### Class ATOM --------------------------------------------------
class atom( meta_enabled):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ
object_type = 'atom'
# these values will be automaticaly read from paper.standard on __init__
meta__used_standard_values =
['line_color','area_color','font_size','font_family']
# undo meta infos
meta__undo_simple = ('x', 'y', 'z', 'pos', 'show', 'name', 'molecule',
'font_family',
'font_size', 'charge', 'show_hydrogens', 'type',
'line_color', 'area_color')
meta__undo_copy = ('marks',)
meta__undo_children_to_record = ('marks',)
def __init__( self, paper, xy = (), package = None, molecule = None):
meta_enabled.__init__( self, paper)
# basic attrs
self.molecule = molecule
# presentation attrs
self.selector = None
self._selected = 0 #with ftext self.selector can no longer be used to
determine if atom is selected
self.item = None
self.ftext = None
if xy:
self.set_xy( xy[0], xy[1])
self.z = 0
self.pos = None
self.focus_item = None
# chemistry attrs
# self.number = 0
# self.show_number = 0
self.show_hydrogens = 0
self.show = 0
self.charge = 0
self.marks = {'radical': None, 'biradical': None, 'electronpair': None,
'plus': None, 'minus': None}
if package:
self.read_package( package)
else:
self.set_name( 'C')
def set_name( self, name, interpret=1, check_valency=1):
# every time name is set the charge should be set to zero
self.charge = 0
# name should not be interpreted
if not interpret:
self.name = name
self.show_hydrogens = 0
self.type = 'text'
return
# try to interpret name
if name.lower() != 'c':
self.show = 1
else:
self.show = 0
if name.capitalize() in PT.periodic_table:
# name is element symbol
self.name = name.capitalize()
self.show_hydrogens = 0
self.type = 'element'
elif (name.lower() in GT.groups_table) and ( not check_valency or
self.molecule.get_atoms_valency( self) == 1):
# name is a known group
self.name = GT.groups_table[ name.lower()]['name']
self.show_hydrogens = 0
self.type = 'group'
else:
# try other possibilities such as alkyl chain or atom with hydrogens
# try if name is hydrogenated form of an element
form = PT.text_to_hydrogenated_atom( name)
if form:
# it is!
a = form.keys()
a.remove( 'H')
valency = self.molecule.get_atoms_valency( self)
if form['H'] in [i-valency+self.charge for i in
PT.periodic_table[a[0]]['valency']]:
self.name = a[0]
self.show_hydrogens = 1
self.type = 'element'
return
# try if the name is an alkyl chain such as c6h13
form = PT.formula_dict( name.upper())
if form.is_saturated_alkyl_chain():
self.name = str( form)
self.show_hydrogens = 1
self.type = 'chain'
return
# its nothing interesting - just text
self.name = name
self.show_hydrogens = 0
self.type = 'text'
def get_text( self):
if self.type in ('text', 'group'):
return self.name
elif self.type == 'element' and not self.show:
return self.name
elif self.type == 'element' and self.show:
ret = self.name
# hydrogens
if self.show_hydrogens:
v = self.get_free_valency()
if v:
h = 'H'
else:
h = ''
if v > 1:
h += '%d' % v
if self.pos == 'center-last':
ret = h + ret
else:
ret = ret + h
# charge
if self.charge:
ch = ''
if abs( self.charge) > 1:
ch += str( abs( self.charge))
if self.charge > 0:
ch += '+'
else:
ch += '-'
else:
ch = ''
if self.pos == 'center-last':
return ch + ret
else:
return ret + ch
elif self.type == 'chain':
return PT.formula_dict( self.name).__str__(
reverse=(self.pos=='center-last'))
def get_ftext( self):
if self.type == 'text':
return self.name
elif self.type == 'group':
if self.pos == 'center-first':
return GT.groups_table[ self.name.lower()]['textf']
else:
return GT.groups_table[ self.name.lower()]['textb']
elif self.type == 'element':
ret = self.name
# hydrogens
if self.show_hydrogens:
v = self.get_free_valency()
if v:
h = 'H'
else:
h = ''
if v > 1:
h += '<sub>%d</sub>' % v
if self.pos == 'center-last':
ret = h + ret
else:
ret = ret + h
# charge
if self.charge:
ch = ''
if abs( self.charge) > 1:
ch += str( abs( self.charge))
if self.charge > 0:
ch = '<sup>%s+</sup>' % ch
else:
ch = '<sup>%s-</sup>' % ch
else:
ch = ''
if self.pos == 'center-last':
return ch + ret
else:
return ret + ch
elif self.type == 'chain':
return PT.formula_dict( self.name).get_html_repr_as_string(
reverse=(self.pos=='center-last'))
# properties
#name = property( get_name, set_name)
def set_molecule( self, molecule):
self.molecule = molecule
def set_xy( self, x, y):
self.x = x #round( x, 2)
self.y = y #round( y, 2)
def decide_pos( self):
as = self.molecule.atoms_bound_to( self)
p = 0
for a in as:
if a.get_x() < self.x:
p -= 1
elif a.get_x() > self.x:
p += 1
if p > 0:
self.pos = 'center-last'
else:
self.pos = 'center-first'
def draw( self):
"draws atom with respect to its properties"
if self.item:
warn( "drawing atom that is probably drawn", UserWarning, 2)
x, y = self.x, self.y
if self.show:
self.update_font()
if not self.pos:
self.decide_pos()
parsed_name = dom.parseString( '<ftext>%s</ftext>' %
self.get_ftext()).childNodes[0]
self.ftext = ftext( self.paper, xy=(self.x, self.y), dom=parsed_name,
font=self.font, pos=self.pos, fill=self.line_color)
self.ftext.draw()
x1, y1, x2, y2 = self.ftext.bbox()
self.item = self.paper.create_rectangle( x1, y1, x2, y2, fill='',
outline='', tags=('atom'))
## shrink the selector to improve appearance (y2-(y2-y1)//4+1)
self.selector = self.paper.create_rectangle( x1, y1, x2, y2-(y2-y1)//4+1,
fill=self.area_color, outline='',tags='helper_a')
self.ftext.lift()
self.paper.lift( self.item)
else:
self.item = self.paper.create_line( x, y, x, y, tags=("atom", 'nonSVG'))
self.selector = None
[m.draw() for m in self.marks.itervalues() if m]
self.paper.register_id( self.item, self)
def redraw( self):
self.update_font()
# at first we delete everything...
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
if self.selector:
self.paper.delete( self. selector)
if self.ftext:
self.ftext.delete()
self.item = None # to ensure that warning in draw() is not triggered when
redrawing
[m.delete() for m in self.marks.itervalues() if m]
# ...then we draw it again
self.draw()
if self._selected:
self.select()
else:
self.unselect()
def focus( self):
if self.show:
self.paper.itemconfig( self.selector, fill='grey')
else:
x, y = self.x, self.y
self.focus_item = self.paper.create_oval( x-4, y-4, x+4, y+4,
tags='helper_f')
self.paper.lift( self.item)
def unfocus( self):
if self.show:
self.paper.itemconfig( self.selector, fill=self.area_color)
if self.focus_item:
self.paper.delete( self.focus_item)
self.focus_item = None
def select( self):
if self.show:
self.paper.itemconfig( self.selector, outline='black')
else:
x, y = self.x, self.y
if self.selector:
self.paper.coords( self.selector, x-2, y-2, x+2, y+2)
else:
self.selector = self.paper.create_rectangle( x-2, y-2, x+2, y+2)
self.paper.lower( self.selector)
self._selected = 1
def unselect( self):
if self.show:
self.paper.itemconfig( self.selector, outline='')
#self.paper.lower( self.selector)
else:
self.paper.delete( self.selector)
self.selector = None
self._selected = 0
def move( self, dx, dy):
"""moves object with his selector (when present)"""
self.x += dx
self.y += dy
self.paper.move( self.item, dx, dy)
if self.selector:
self.paper.move( self.selector, dx, dy)
if self.ftext:
self.ftext.move( dx, dy)
for m in self.marks:
if self.marks[m]:
self.marks[m].move( dx, dy)
def move_to( self, x, y):
dx = x - self.x
dy = y - self.y
#self.set_xy( x, y)
self.move( dx, dy)
def get_x( self):
return self.x
def get_y( self):
return self.y
def get_xy( self):
return self.x, self.y
def get_xyz( self, real=0):
"""returns atoms coordinates, default are screen coordinates, real!=0
changes it to real coordinates (these two are usually different for
imported molecules)"""
if real:
x, y = self.paper.screen_to_real_coords( (self.x, self.y))
z = self.z *self.paper.screen_to_real_ratio()
return x, y, z
else:
return self.x, self.y, self.z
def round_coords( self, precision=0):
self.x = round( self.x, precision)
self.y = round( self.y, precision)
def delete( self):
for m in self.marks:
if self.marks[m]:
self.marks[m].delete()
if self.focus_item:
self.unfocus()
if self.selector:
self.unselect()
if self.show:
self.paper.delete( self.selector)
self.selector = None
self._selected = 0
if self.item:
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
self.item = None
if self.ftext:
self.ftext.delete()
return self
def read_package( self, package):
a = ['no','yes']
on_off = ['off','on']
self._cdml_id = package.getAttribute( 'id')
#self.show_number = a.index( package.getAttribute( 'show_number'))
self.pos = package.getAttribute( 'pos')
position = package.getElementsByTagName( 'point')[0]
# reading of coords regardless of their unit
x, y, z = self.paper.read_xml_point( position)
if z != None:
self.z = z* self.paper.real_to_screen_ratio()
# needed to support transparent handling of molecular size
x, y = self.paper.real_to_screen_coords( (x, y))
self.set_xy( x, y)
ft = package.getElementsByTagName('ftext')
if ft:
self.set_name( reduce( operator.add, [e.toxml() for e in
ft[0].childNodes], ''), check_valency=0, interpret=0)
else:
self.set_name( package.getAttribute( 'name'), check_valency=0)
if package.getAttribute( 'hydrogens'):
self.show_hydrogens = on_off.index( package.getAttribute('hydrogens'))
else:
self.show_hydrogens = 0
# font and fill color
fnt = package.getElementsByTagName('font')
if fnt:
fnt = fnt[0]
self.font_size = int( fnt.getAttribute( 'size'))
self.font_family = fnt.getAttribute( 'family')
if fnt.getAttribute( 'color'):
self.line_color = fnt.getAttribute( 'color')
# show
if package.getAttribute( 'show'):
self.show = a.index( package.getAttribute( 'show'))
else:
self.show = (self.name!='C')
# background color
if package.getAttribute( 'background-color'):
self.area_color = package.getAttribute( 'background-color')
# marks
for m in package.getElementsByTagName( 'mark'):
auto = (m.getAttribute( 'auto') != None and m.getAttribute( 'auto')) or 0
if auto == 'True':
auto = 1
type = m.getAttribute( 'type')
x, y, z = self.paper.read_xml_point( m)
self.marks[ type] = marks.__dict__[ type]( self.paper,
x, y,
atom=self,
auto= int(auto))
def get_package( self, doc):
y = ['no','yes']
on_off = ['off','on']
a = doc.createElement('atom')
a.setAttribute( 'id', str( self.get_cdml_id()))
#show attribute is set only when non default
if (self.show and self.name=='C') or (not self.show and self.name!='C'):
a.setAttribute('show', y[ self.show])
if self.show:
a.setAttribute( 'pos', self.pos)
if self.font_size != 12 or self.font_family != 'helvetica' or
self.line_color != '#000':
font = dom_extensions.elementUnder( a, 'font', attributes=(('size', str(
self.font_size)), ('family', self.font_family)))
if self.line_color != '#000':
font.setAttribute( 'color', self.line_color)
if self.type == 'text':
a.appendChild( dom.parseString( '<ftext>%s</ftext>' %
self.name).childNodes[0])
else:
a.setAttribute( 'name', self.name)
if self.show_hydrogens:
a.setAttribute('hydrogens', on_off[self.show_hydrogens])
if self.area_color != "#ffffff":
a.setAttribute( 'background-color', self.area_color)
# needed to support transparent handling of molecular size
x, y, z = map( self.paper.px_to_text_with_unit, self.get_xyz( real=1))
if self.z:
dom_extensions.elementUnder( a, 'point', attributes=(('x', x), ('y', y),
('z', z)))
else:
dom_extensions.elementUnder( a, 'point', attributes=(('x', x), ('y', y)))
for m, o in self.marks.items():
if self.marks[m]:
x ,y = map( self.paper.px_to_text_with_unit, (o.x, o.y))
dom_extensions.elementUnder( a, 'mark', attributes=(('type', m),
('x', x),
('y', y),
('auto', str( int(
o.auto)))))
return a
def get_cdml_id( self):
if self.item:
self._cdml_id = 'a'+str( self.item)
return self._cdml_id
def toggle_center( self, mode = 0):
"""toggles the centering of text between 'center-first' and
'center-last'(mode=0)
or sets it strictly - mode=-1, mode=1"""
if not mode:
if self.pos == 'center-last':
self.pos = 'center-first'
else:
self.pos = 'center-last'
elif mode == -1:
self.pos = 'center-first'
else:
self.pos = 'center-last'
self.redraw()
def update_font( self):
self.font = tkFont.Font( family=self.font_family, size=self.font_size)
def scale_font( self, ratio):
"""scales font of atom. does not redraw !!"""
self.font_size = int( round( self.font_size * ratio))
self.update_font()
def get_free_valency( self):
"""returns free valency of atom."""
if self.type != 'element':
return 0
valency = self.molecule.get_atoms_valency( self)
if self.name in PT.periodic_table:
vals = PT.periodic_table[ self.name]['valency']
for v in vals:
# should we increase or decrease valency with charge ?
if self.charge:
if abs( self.charge) > 1:
# charges higher than one should always decrease valency
charge = abs( self.charge)
elif (self.name in PT.accept_cation) and (self.charge == 1) and
(valency-1 <= PT.accept_cation[self.name]):
# elements that can accept cations to increase their valency (NH4+)
charge = -1
elif (self.name in PT.accept_anion) and (self.charge == -1) and
(valency-1 <= PT.accept_anion[self.name]):
# elements that can accept anions to increase their valency (BH4-)
charge = -1
else:
# otherwise charge reduces valency
charge = abs( self.charge)
else:
charge = 0
if valency+charge <= v:
return v-valency-charge
# if valency is exceeded return lowest possible negative value
return max( PT.periodic_table[ self.name]['valency']) - valency - charge
# if unsuccessful return 0
return 0
def get_formula_dict( self):
"""returns formula as dictionary that can
be passed to functions in periodic_table"""
if self.type == 'text':
return PT.formula_dict()
elif self.type == 'group':
return PT.formula_dict( GT.groups_table[
self.name.lower()]['composition'])
elif self.type == 'element':
ret = PT.formula_dict( self.name)
free_val = self.get_free_valency()
if free_val > 0:
ret['H'] = free_val
return ret
elif self.type == 'chain':
return PT.formula_dict( self.name)
def atoms_bound_to( self):
"""just link to molecule.atoms_bound_to()"""
return self.molecule.atoms_bound_to( self)
def lift( self):
for m in self.marks.itervalues():
if m:
m.lift()
if self.selector:
self.paper.lift( self.selector)
if self.ftext:
self.ftext.lift()
if self.item:
self.paper.lift( self.item)
def set_mark( self, mark='radical', toggle=1, angle='auto'):
if mark in self.marks:
if toggle:
if self.marks[ mark]:
self.marks[ mark].delete()
self.marks[ mark] = None
else:
self.create_mark( mark=mark, angle=angle)
else:
if not self.marks[ mark]:
self.create_mark( mark=mark, angle=angle)
def create_mark( self, mark='radical', angle='auto'):
if self.show:
dist = self.font_size/2 + round( marks.__dict__[ mark].standard_size/2) +
2
else:
dist = 5 + round( marks.__dict__[ mark].standard_size / 2)
if angle == 'auto':
x, y = self.molecule.find_least_crowded_place_around_atom( self,
range=dist)
#ang = round( geometry.clockwise_angle_from_east( x -self.x, y -self.y))
else:
x = self.x + round( cos( angle) *dist)
y = self.y + round( sin( angle) *dist)
#ang = angle
self.marks[ mark] = marks.__dict__[ mark]( self.paper, x, y,
atom = self,
auto=(angle=='auto'))
self.marks[ mark].draw()
def reposition_marks( self):
for m in self.marks.itervalues():
if m and m.auto:
if self.show:
dist = self.font_size/2 + round( m.size/2) + 2
else:
dist = 5 + round( m.size / 2)
x, y = self.molecule.find_least_crowded_place_around_atom( self,
range=dist)
m.move_to( x, y)
# class BOND--------------------------------------------------
class bond( meta_enabled):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ
object_type = 'bond'
# these values will be automaticaly read from paper.standard on __init__
# bond_width couldn't be because it has sign that is important
# widths need to be calculated therefore are also not here (to be fixed)
meta__used_standard_values = ['line_color','double_length_ratio']
# undo related metas
meta__undo_simple = ('atom1', 'atom2', 'type', 'line_width', 'center',
'bond_width',
'molecule', 'line_color','double_length_ratio')
def __init__( self, paper, atoms=(), package=None, molecule=None, type=1):
self.type = type
meta_enabled.__init__( self, paper)
self.item = None
self.second = None
self.third = None
self.items = []
self.molecule = molecule
if atoms:
self.atom1, self.atom2 = atoms
self.selector = None
# implicit values
self.center = 0
if package:
self.read_package( package)
def read_standard_values( self, old_standard=None):
meta_enabled.read_standard_values( self, old_standard=old_standard)
# wedge width or ...
if self.type in (4,5):
if not old_standard or (self.paper.standard.wedge_width !=
old_standard.wedge_width):
self.bond_width = self.paper.any_to_px( self.paper.standard.wedge_width)
# ... bond width
else:
if not old_standard or (self.paper.standard.bond_width !=
old_standard.bond_width):
if 'bond_width' in self.__dict__:
self.bond_width = misc.signum( self.bond_width)
*self.paper.any_to_px( self.paper.standard.bond_width)
else:
self.bond_width = self.paper.any_to_px(
self.paper.standard.bond_width)
# line width
if not old_standard or (self.paper.standard.line_width !=
old_standard.line_width):
self.line_width = self.paper.any_to_px( self.paper.standard.line_width)
def set_molecule( self, molecule):
self.molecule = molecule
def draw( self):
"""call the appropriate draw method"""
if self.item:
warn( "drawing bond that is probably drawn already", UserWarning, 2)
type = data.bond_type_remap[ self.type]
self.__class__.__dict__[ '_draw_'+type]( self)
def _draw_1s( self):
x1, y1 = self.atom1.get_xy()
x2, y2 = self.atom2.get_xy()
#MB# x1, y1, x2, y2 = map( round, [x1, y1, x2, y2])
# main item
self.item = self.paper.create_line( (x1, y1, x2, y2), tags=('bond',),
width=self.line_width, fill=self.line_color, capstyle="round")
# draw helper items
self.second = self.third = None
self.paper.register_id( self.item, self)
return x1,y1,x2,y2
def _draw_2s( self):
x1,y1,x2,y2 = self._draw_1s()
if self.center == None or self.bond_width == None:
self._decide_distance_and_center()
d = self.bond_width
# double
if self.center:
self.paper.itemconfig( self.item, fill='')
d = int( round( d/3))
x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, d)
# shortening of the second bond
dx = x-x0
dy = y-y0
if self.center:
_k = 0
else:
_k = (1-self.double_length_ratio)/2
self.second = self.paper.create_line( x-_k*dx, y-_k*dy, x0+_k*dx, y0+_k*dy,
width=self.line_width, fill=self.line_color)
if self.center:
self.third = self.paper.create_line( 2*x1-x, 2*y1-y, 2*x2-x0, 2*y2-y0,
width=self.line_width, fill=self.line_color)
def _draw_3s( self):
x1,y1,x2,y2 = self._draw_1s()
if self.center == None or self.bond_width == None:
self._decide_distance_and_center()
d = self.bond_width
_k = (1-self.double_length_ratio)/2
x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, d*3/4)
dx = x-x0
dy = y-y0
self.second = self.paper.create_line( x-_k*dx, y-_k*dy, x0+_k*dx, y0+_k*dy,
width=self.line_width, fill=self.line_color)
self.third = self.paper.create_line( 2*x1-x-_k*dx, 2*y1-y-_k*dy,
2*x2-x0+_k*dx, 2*y2-y0+_k*dy, width=self.line_width, fill=self.line_color)
def _draw_1h( self):
x1,y1,x2,y2 = self._draw_1s()
# main item
self.paper.itemconfig( self.item, fill='')
# the small lines
step_size = 5
x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.bond_width)
d = sqrt( (x1-x2)**2 + (y1-y2)**2) # length of the bond
dx1 = -(x1 - x0)/d
dy1 = -(y1 - y0)/d
dx2 = -(x1 -2*x2 +x0)/d
dy2 = -(y1 -2*y2 +y0)/d
for i in range( 1, int( round( d/ step_size))+1):
coords = [x1+dx1*i*step_size, y1+dy1*i*step_size, x1+dx2*i*step_size,
y1+dy2*i*step_size]
coords = map( round, coords)
if coords[0] == coords[2] and coords[1] == coords[3]:
if (dx1+dx2) > (dy1+dy2):
coords[0] += 1
else:
coords[1] += 1
self.items.append( self.paper.create_line( coords, width=self.line_width,
fill=self.line_color))
def _draw_1w( self):
x1, y1 = self.atom1.get_xy()
x2, y2 = self.atom2.get_xy()
x1, y1, x2, y2 = map( round, [x1, y1, x2, y2])
# main item
x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.bond_width)
self.item = self.paper.create_polygon( (x1, y1, x0, y0, 2*x2-x0, 2*y2-y0),
tags=('bond',), outline=self.line_color, fill=self.line_color,
joinstyle="miter")
# draw helper items
self.second = self.third = None
self.paper.register_id( self.item, self)
def redraw( self, recalc_side=0):
if recalc_side:
self._decide_distance_and_center()
sel = self.selector
if self.item:
self.delete()
self.draw()
# reselect
if sel:
self.select()
def simple_redraw( self):
"""very fast redraw that draws only a simple line instead of the bond,
used in 3d rotation only (as for bkchem 0.5.0)"""
if self.second:
self.paper.delete( self.second)
self.second = None
if self.third:
self.paper.delete( self.third)
self.third = None
if self.items:
map( self.paper.delete, self.items)
self.items = []
x1, y1 = self.atom1.get_xy()
x2, y2 = self.atom2.get_xy()
x1, y1, x2, y2 = map( round, [x1, y1, x2, y2])
self.paper.coords( self.item, x1, y1, x2, y2)
self.paper.itemconfig( self.item, width = self.line_width,
fill=self.line_color)
def focus( self):
if self.type in (1,2,3):
items = [self.item]
if self.second:
items += [self.second]
if self.third:
items += [self.third]
[self.paper.itemconfig( item, width = self.line_width+2) for item in
items]
elif self.type == 5:
[self.paper.itemconfig( item, width = self.line_width+2) for item in
self.items]
elif self.type == 4:
self.paper.itemconfigure( self.item, fill='white')
def unfocus( self):
if self.type in (1,2,3):
if not self.item:
return
items = [self.item]
if self.second:
items += [self.second]
if self.third:
items += [self.third]
[self.paper.itemconfig( item, width = self.line_width) for item in items]
elif self.type == 5:
[self.paper.itemconfig( item, width = self.line_width) for item in
self.items]
elif self.type == 4:
self.paper.itemconfigure( self.item, fill=self.line_color)
def select( self):
x1, y1 = self.atom1.get_xy()
x2, y2 = self.atom2.get_xy()
x = ( x1 + x2) / 2
y = ( y1 + y2) / 2
if self.selector:
self.paper.coords( self.selector, x-2, y-2, x+2, y+2)
else:
self.selector = self.paper.create_rectangle( x-2, y-2, x+2, y+2)
self.paper.lower( self.selector)
def unselect( self):
self.paper.delete( self.selector)
self.selector = None
def move( self, dx, dy):
"""moves object with his selector (when present)"""
#self.redraw() # changed for speed, reduces time needed to move objects to
1/2
b = [self.item]
if self.second:
b.append( self.second)
if self.third:
b.append( self.third)
if self.selector:
b.append( self.selector)
if self.items:
b.extend( self.items)
[self.paper.move( o, dx, dy) for o in b]
def delete( self):
self.unselect()
items = []
if self.item:
items += [self.item]
self.paper.unregister_id( self.item)
self.item = None
if self.second:
items += [self.second]
self.second = None
if self.third:
items += [self.third]
self.third = None
if self.items:
items.extend( self.items)
self.items = []
map( self.paper.delete, items)
self.item = None
return self
def read_package( self, package):
b = ['no', 'yes']
type = package.getAttribute( 'type')
if type:
if type == 'forth':
self.type = 4
else:
for type_set in ('bond_types', 'alternative_bond_types',
'numbered_bond_types'):
if type in data.__dict__[type_set]:
self.type = data.__dict__[type_set].index( type)
else:
self.type = 1
# implied
if package.getAttribute( 'distance'):
self.bond_width = float( package.getAttribute( 'distance')) *
self.paper.real_to_screen_ratio()
else:
self.bond_width = None
if package.getAttribute( 'width'):
self.line_width = float( package.getAttribute( 'width'))
if package.getAttribute( 'center'):
self.center = b.index( package.getAttribute( 'center'))
else:
self.center = None
if package.getAttribute( 'color'):
self.line_color = package.getAttribute( 'color')
if package.getAttribute( 'double_ratio'):
self.double_length_ratio = float( package.getAttribute( 'double_ratio'))
# end of implied
self.atom1 = self.molecule.get_atom_with_cdml_id( package.getAttribute(
'start'))
self.atom2 = self.molecule.get_atom_with_cdml_id( package.getAttribute(
'end'))
def get_package( self, doc):
a = data.alternative_bond_types
b = ['no', 'yes']
bnd = doc.createElement('bond')
dom_extensions.setAttributes( bnd, (('type', a[self.type]),
('width', str( self.line_width)),
('start', self.atom1.get_cdml_id()),
('end', self.atom2.get_cdml_id()),
('double_ratio', str(
self.double_length_ratio))))
if self.type != 1:
bnd.setAttribute( 'distance', str( self.bond_width *
self.paper.screen_to_real_ratio()))
if self.type == 2:
bnd.setAttribute( 'center', b[ self.center])
if self.line_color != '#000':
bnd.setAttribute( 'color', self.line_color)
return bnd
def toggle_type( self, only_shift = 0, to_type='normal'):
if to_type == 'wedge':
if self.type == 4:
# if already wedge - change the start and end
self.atom1, self.atom2 = self.atom2, self.atom1
else:
# toggle to up
if self.type != 5:
self.bond_width = self.paper.any_to_px(
self.paper.standard.wedge_width)
self.type = 4
elif to_type == 'hatch':
if self.type == 5:
# if already hatch - change the start and end
self.atom1, self.atom2 = self.atom2, self.atom1
else:
# toggle to back
if self.type != 4:
self.bond_width = self.paper.any_to_px(
self.paper.standard.wedge_width)
self.type = 5
else:
if self.type not in (1,2,3):
self.type = 1
self.bond_width = self.paper.any_to_px( self.paper.standard.bond_width)
else:
if only_shift:
if self.center:
self.bond_width = -self.bond_width
self.center = 0
elif self.bond_width > 0:
self.bond_width = -self.bond_width
else:
self.center = 1
else:
if self.type == 3:
# was type 3
self.type = 1
elif self.type == 1:
# was type 1
self.type += 1
self._decide_distance_and_center()
else:
# was type 2
self.type = self.type +1
self.redraw()
def _decide_distance_and_center( self):
"""according to molecular geometry decide what bond.center and
bond.bond_width should be"""
atms = self.molecule.atoms_bound_to( self.atom1) +
self.molecule.atoms_bound_to( self.atom2)
atms = misc.difference( atms, [self.atom1, self.atom2])
coords = [a.get_xy() for a in atms]
line = self.atom1.get_xy() + self.atom2.get_xy()
if not self.bond_width:
length = sqrt((line[0]-line[2])**2 + (line[1]-line[3])**2)
self.bond_width = round( length / 5, 1)
# does not need to go further if the bond is not double
# the str is to support the future notation for bond types
if not '2' in str( self.type):
return
# searching for circles
plus_side1 = [a for a in self.atom1.atoms_bound_to() if
geometry.on_which_side_is_point( line, a.get_xy()) == 1 and a!=self.atom2]
plus_side2 = [a for a in self.atom2.atoms_bound_to() if
geometry.on_which_side_is_point( line, a.get_xy()) == 1 and a!=self.atom1]
minus_side1 = [a for a in self.atom1.atoms_bound_to() if
geometry.on_which_side_is_point( line, a.get_xy()) == -1 and a!=self.atom2]
minus_side2 = [a for a in self.atom2.atoms_bound_to() if
geometry.on_which_side_is_point( line, a.get_xy()) == -1 and a!=self.atom1]
plus_side = ( plus_side1, plus_side2)
minus_side = ( minus_side1, minus_side2)
circles = 0
if ( len( plus_side1) and len( plus_side2)) or ( len( minus_side1) and len(
minus_side2)):
# only when there are enough atoms in neighborhood we need to search for
circles
import copy
def accessible( a1, a2, d):
"""is a2 accessible from a1 through d?"""
if a1 == a2:
return 1
d.remove( a1)
if a2 in a1.molecule.atoms_bound_to( a1):
return 1
else:
for a in a1.molecule.atoms_bound_to( a1):
if a in d and accessible( a, a2, d):
return 1
return 0
def get_circles_for_side( side):
res = 0
side1, side2 = side
while len( side1):
a1 = side1.pop(0)
for a2 in side2:
atoms = copy.copy( self.molecule.atoms_map)
atoms.remove( self.atom1)
atoms.remove( self.atom2)
if accessible( a1, a2, atoms):
res += 1
return res
circles = get_circles_for_side( plus_side) - get_circles_for_side(
minus_side)
# end of circles search
if circles:
side = circles
else:
sides = [geometry.on_which_side_is_point( line, xy) for xy in coords]
side = reduce( operator.add, sides, 0)
# on which side to put the second line
if side == 0 and (len( self.molecule.atoms_bound_to( self.atom1)) == 1 or
len( self.molecule.atoms_bound_to( self.atom2)) == 1):
# maybe we should center, but this is usefull only when one of the atoms
has no other substitution
self.center = 1
else:
if not circles:
# recompute side with weighting of atom types
for i in range( len( sides)):
if sides[i] and atms[i].name == 'H':
sides[i] *= 0.1 # this discriminates H
elif sides[i] and atms[i].name != 'C':
sides[i] *= 0.2 # this makes "non C" less then C but more then H
side = reduce( operator.add, sides, 0)
if side < 0:
self.center = 0
self.bond_width = -abs( self.bond_width)
else:
self.center = 0
self.bond_width = abs( self.bond_width)
def get_atoms( self):
return self.atom1, self.atom2
def change_atoms( self, a1, a2):
"""used in overlap situations, it replaces reference to atom a1 with
reference to atom a2"""
if self.atom1 == a1:
self.atom1 = a2
elif self.atom2 == a1:
self.atom2 = a2
else:
warn("not bonds' atom in bond.change_atoms()", UserWarning, 2)
def bbox( self):
return self.paper.bbox( self.item)
def lift( self):
[self.paper.lift( i) for i in self.items]
if self.selector:
self.paper.lift( self.selector)
if self.second:
self.paper.lift( self.second)
if self.third:
self.paper.lift( self.third)
if self.item:
self.paper.lift( self.item)
##-------------------- STANDARD CLASS ------------------------------
class standard:
def __init__( self):
# common
self.line_width = '1px'
self.font_size = 12
self.font_family = 'helvetica'
self.line_color = "#000"
self.area_color = '#ffffff'
# bond
self.bond_length = '1cm'
self.bond_width = '6px'
self.wedge_width = '3px'
self.double_length_ratio = 0.75
# arrow
self.arrow_length = '1.6cm'
# paper
self.paper_type = 'A4'
self.paper_orientation = 'portrait'
self.paper_crop_svg = 0
def __eq__( self, other):
for (k,v) in self.__dict__.iteritems():
if str( v) != str( other.__dict__[ k]):
return 0
return 1
def __ne__( self, other):
return not self.__eq__( other)
def get_package( self, doc):
ret = doc.createElement( 'standard')
dom_extensions.setAttributes( ret, (('line_width', str( self.line_width)),
('font_size', str( self.font_size)),
('font_family', str( self.font_family)),
('line_color', self.line_color),
('area_color', self.area_color),
('paper_type', self.paper_type),
('paper_orientation',
self.paper_orientation),
('paper_crop_svg', str(
self.paper_crop_svg))))
dom_extensions.elementUnder( ret, 'bond', (('length', str(
self.bond_length)),
('width', str( self.bond_width)),
('wedge-width', str(
self.wedge_width)),
('double-ratio', str(
self.double_length_ratio))))
dom_extensions.elementUnder( ret, 'arrow', (('length', str(
self.arrow_length)),))
return ret
def read_package( self, p):
for attr in ('line_width', 'font_size', 'font_family',
'line_color','area_color',
'paper_crop_svg','paper_orientation','paper_type'):
if p.getAttribute( attr):
self.__dict__[ attr] = p.getAttribute( attr)
self.font_size = int( self.font_size)
self.paper_crop_svg = int( self.paper_crop_svg)
b = dom_extensions.getFirstChildNamed( p, 'bond')
if b:
self.bond_length = b.getAttribute( 'length') or self.bond_length
self.bond_width = b.getAttribute( 'width') or self.bond_width
self.double_length_ratio = b.getAttribute( 'double-ratio') or
self.double_length_ratio
self.double_length_ratio = float( self.double_length_ratio)
self.wedge_width = b.getAttribute( 'wedge-width') or self.wedge_width
a = dom_extensions.getFirstChildNamed( p, 'arrow')
if a:
self.arrow_length = a.getAttribute( 'length')
##-------------------- ARROW CLASS ------------------------------
class arrow( meta_enabled):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ (are not non-empty)
_pins = ['none', 'last', 'first', 'both']
object_type = 'arrow'
# these values will be automaticaly read from paper.standard on __init__
meta__used_standard_values = ['line_color']
# other meta infos
meta__is_container = 1
# undo related metas
meta__undo_simple = ('pin', 'spline', 'line_width', 'line_color')
meta__undo_copy = ('points',)
meta__undo_children_to_record = ('points',)
def __init__( self, paper, points=[], shape=(8,10,3), pin=1, spline=0,
package=None, fill="#000"):
meta_enabled.__init__( self, paper)
self.points = []
self.spline = spline
self.paper = paper
self.shape = shape
self.item = None
self.pin = 1
if points:
for p in points:
pnt = point( self.paper, p[0], p[1], arrow=self)
self.points.append( pnt)
if package:
self.read_package( package)
def read_standard_values( self, old_standard=None):
meta_enabled.read_standard_values( self, old_standard=old_standard)
if not old_standard or (self.paper.standard.line_width !=
old_standard.line_width):
self.line_width = self.paper.any_to_px( self.paper.standard.line_width)
def draw( self):
if len( self.points) > 1:
#type = self.spline and 'circle' or 'invisible'
type = 'invisible'
for p in self.points:
p.type = type
[pnt.draw() for pnt in self.points]
ps = reduce( operator.add, map( lambda b: b.get_xy(), self.points))
self.item = self.paper.create_line( ps, tags='arrow', arrow=self._pins[
self.pin], arrowshape=self.shape,\
width=self.line_width,
smooth=self.spline, fill=self.line_color)
self.paper.register_id( self.item, self)
def redraw( self):
if not self.item:
self.draw()
else:
if len( self.points) > 1:
#type = self.spline and 'circle' or 'invisible'
type = 'invisible'
[pnt.change_type( type) for pnt in self.points]
ps = reduce( operator.add, map( lambda b: b.get_xy(), self.points))
self.paper.coords( self.item, ps)
self.paper.itemconfig( self.item, arrow=self._pins[ self.pin],
arrowshape=self.shape,\
width=self.line_width, smooth=self.spline,
fill=self.line_color)
def focus( self):
self.paper.itemconfig( self.item, width = self.line_width+2)
def unfocus( self):
self.paper.itemconfig( self.item, width = self.line_width)
# def get_id( self):
# return self.id
def select( self):
#self.selector = hg.selection_rect( self.paper, self, coords=self.bbox())
[pnt.select() for pnt in self.points]
def unselect( self):
#self.selector.delete()
[pnt.unselect() for pnt in self.points]
def create_new_point( self, x, y, position=-1):
"creates new point, position specifies relative position of point in
points, usualy -1 or 0"
pnt = point( self.paper, xy=(x,y), arrow=self)
if position < 0:
self.points.append( pnt)
else:
try:
self.points.insert( position, pnt)
except IndexError:
self.points.append( pnt)
warn( "bad position for adding point in arrow", UserWarning, 2)
return pnt
def delete_point( self, pnt):
try:
self.points.remove( pnt)
except IndexError:
warn( "trying to remove nonexisting point from arrow")
pnt.delete()
def delete( self):
[p.delete() for p in self.points]
self.points = []
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
self.item = None
def is_empty_or_single_point( self):
return len( self.points) < 2
def move( self, dx, dy):
[p.move( dx, dy) for p in self.points]
self.redraw()
def read_package( self, package):
a = ['no', 'yes']
start = a.index( package.getAttribute( 'start'))
end = a.index( package.getAttribute( 'end'))
if start and end:
self.pin = 3
elif start:
self.pin = 2
elif end:
self.pin = 1
else:
self.pin = 0
self.spline = a.index( package.getAttribute( 'spline'))
self.line_width = float( package.getAttribute( 'width'))
#self.shape = package.getAttribute( 'shape')
self.line_color = package.getAttribute( 'color')
for p in package.getElementsByTagName( 'point'):
self.points.append( point( self.paper, arrow=self, package=p))
def get_package( self, doc):
a = ['no', 'yes']
arr = doc.createElement('arrow')
start, end = 0, 0
if self.pin == 2 or self.pin == 3:
start = 1
if self.pin == 1 or self.pin ==3:
end = 1
dom_extensions.setAttributes( arr, (('shape', str( self.shape)),
('spline', a[self.spline]),
('width', str( self.line_width)),
('start', a[start]),
('end', a[end]),
('color', str( self.line_color))))
for p in self.points:
arr.appendChild( p.get_package( doc))
return arr
def change_direction( self):
self.pin += 1
if self.pin > 3:
self.pin = 0
self.redraw()
def bbox( self):
return self.paper.bbox( self.item)
def set_pins( self, start=None, end=None):
st, en = self.get_pins()
if start != None:
st = start
if end != None:
en = end
self.pin = en + 2*st
def get_pins( self):
"""returns tuple of boolean values (start, end)"""
return divmod( self.pin, 2)
def lift( self):
if self.item:
self.paper.lift( self.item)
[o.lift() for o in self.points]
def get_shape_defining_children( self):
for i in self.points:
yield i
## -------------------- POINT CLASS ------------------------------
class point( simple_parent):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ (are not non-empty)
object_type = 'point'
# undo related metas
meta__undo_simple = ('x','y')
def __init__( self, paper, xy=(), arrow=None, package=None, type='invisible'):
if xy:
self.x, self.y = xy
self.paper = paper
self.item = None
self.focus_item = None
self.selector = None
self.type = type
if arrow:
self.arrow = arrow
if package:
self.read_package( package)
def set_arrow( self, arrow):
self.arrow = arrow
def draw( self):
if self.item:
self.redraw()
else:
if self.type == 'invisible':
self.item = self.paper.create_line( self.x, self.y, self.x, self.y,
tags='point')
elif self.type == 'circle':
self.item = self.paper.create_oval( self.x-2, self.y-2, self.x+2,
self.y+2, fill='grey', outline='grey', tags='point')
else:
warn( 'unknown point type')
return
self.paper.register_id( self.item, self)
def redraw( self):
if not self.item:
self.draw()
else:
self.paper.delete( self.item)
self.item = None
self.draw()
if self.selector:
self.paper.coords( self.selector, self.x-2, self.y-2, self.x+2,
self.y+2)
def move( self, dx, dy):
self.x += dx
self.y += dy
self.paper.move( self.item, dx, dy)
if self.selector:
self.paper.move( self.selector, dx, dy)
def move_to( self, x, y):
if not self.item:
self.x = x
self.y = y
self.draw()
else:
dx = x -self.x
dy = y -self.y
self.move( dx, dy)
def focus( self):
self.focus_item = self.paper.create_oval( self.x-4, self.y-4, self.x+4,
self.y+4)
if self.item:
self.paper.lift( self.item)
def unfocus( self):
if self.focus_item:
self.paper.delete( self.focus_item)
self.focus_item = None
def select( self):
if not self.selector:
self.selector = self.paper.create_rectangle( self.x-2, self.y-2,
self.x+2, self.y+2)
self.paper.lower( self.selector)
def unselect( self):
if self.selector:
self.paper.delete( self.selector)
self.selector = None
def get_xy( self):
return self.x, self.y
def delete( self):
self.unselect()
self.unfocus()
if self.item:
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
self.item = None
def read_package( self, package):
x, y, z = self.paper.read_xml_point( package)
self.x, self.y = self.paper.real_to_screen_coords( (x,y))
#self.z = int( package.getAttribute( 'z') )
def get_package( self, doc):
pnt = doc.createElement('point')
x, y = map( self.paper.px_to_text_with_unit,
self.paper.screen_to_real_coords( (self.x, self.y)))
dom_extensions.setAttributes( pnt, (('x', x),
('y', y)))
return pnt
def lift( self):
if self.selector:
self.paper.lift( self.selector)
if self.item:
self.paper.lift( self.item)
def change_type( self, type):
self.type = type
self.redraw()
##-------------------- PLUS CLASS ------------------------------
class plus( meta_enabled):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ (are not non-empty)
object_type = 'plus'
# these values will be automaticaly read from paper.standard on __init__
meta__used_standard_values = ['line_color','area_color','font_family']
# undo related metas
meta__undo_simple = ('x', 'y', 'font_size', 'font_family',
'line_color','area_color')
def __init__( self, paper, xy=(), package=None):
meta_enabled.__init__( self, paper)
self.x = self.y = None
self.focus_item = None
self.selector = None
self._selected = 0
if package:
self.read_package( package)
if xy:
self.x, self.y = xy
# standard values
self.font_size = 20
self.update_font()
def draw( self):
self.update_font()
self.item = self.paper.create_text( self.x, self.y, text='+', tags='plus',
font = self.font, fill=self.line_color)
self.paper.register_id( self.item, self)
self.selector = self.paper.create_rectangle( self.paper.bbox( self.item),
fill=self.area_color, outline=self.area_color)
self.paper.lift( self.item)
def redraw( self):
self.update_font()
self.paper.coords( self.item, self.x, self.y)
self.paper.itemconfig( self.item, font = self.font, fill=self.line_color)
if self.selector:
self.paper.coords( self.selector, self.paper.bbox( self.item))
self.paper.itemconfig( self.selector, fill=self.area_color,
outline=self.area_color)
def focus( self):
if self.selector:
self.paper.itemconfig( self.selector, fill='grey')
def unfocus( self):
if self.selector:
self.paper.itemconfig( self.selector, fill=self.area_color)
def get_id( self):
return self.item
def select( self):
if self.selector:
self.paper.itemconfig( self.selector, outline='black')
self._selected = 1
def unselect( self):
if self.selector:
self.paper.itemconfig( self.selector, outline=self.area_color)
self._selected = 0
def move( self, dx, dy):
self.x += dx
self.y += dy
self.paper.move( self.item, dx, dy)
if self.selector:
self.paper.move( self.selector, dx, dy)
def move_to( self, x, y):
dx = x - self.x
dy = y - self.y
self.move( dx, dy)
def read_package( self, package):
pnt = package.getElementsByTagName( 'point')[0]
self.x, self.y, z = self.paper.read_xml_point( pnt)
if package.getAttribute( 'color'):
self.line_color = package.getAttribute( 'color')
if package.getAttribute( 'background-color'):
self.area_color = package.getAttribute( 'background-color')
def get_package( self, doc):
pls = doc.createElement('plus')
x, y = self.paper.px_to_text_with_unit( (self.x, self.y))
dom_extensions.elementUnder( pls, 'point', (('x', x),
('y', y)))
if self.line_color != '#000':
pls.setAttribute( 'color', self.line_color)
if self.area_color != '#ffffff':
pls.setAttribute( 'background-color', self.area_color)
return pls
def delete( self):
self.paper.delete( self.selector)
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
def get_xy( self):
return self.x, self.y
def bbox( self):
return self.paper.bbox( self.item)
def scale_font( self, ratio):
"""scales font of plus. does not redraw !!"""
self.font_size = int( round( self.font_size * ratio))
self.update_font()
def update_font( self):
self.font = tkFont.Font( family=self.font_family, size=self.font_size)
def lift( self):
if self.selector:
self.paper.lift( self.selector)
if self.item:
self.paper.lift( self.item)
##--------------------TEXT CLASS--------------------
class text( meta_enabled):
# note that all children of simple_parent have default meta infos set
# therefor it is not necessary to provide them for all new classes if they
# don't differ (are not non-empty)
object_type = 'text'
# these values will be automaticaly read from paper.standard on __init__
meta__used_standard_values =
['line_color','area_color','font_size','font_family']
# undo related metas
meta__undo_simple = ('x', 'y', 'text', 'font_size', 'font_family',
'line_color', 'area_color')
def __init__( self, paper, xy=(), text='', package=None):
meta_enabled.__init__( self, paper)
self.selector = None
self._selected = 0
self.ftext = None
if xy:
self.set_xy( xy[0], xy[1])
self.set_text( text)
self.item = None
if package:
self.read_package( package)
self.focus_item = None
# public methods
def set_xy( self, x, y):
self.x = round( x, 2)
self.y = round( y, 2)
def draw( self):
"draws text"
self.update_font()
self.ftext = ftext( self.paper, xy=(self.x, self.y), dom=self.parsed_text,
font=self.font, fill=self.line_color)
self.ftext.draw()
x1, y1, x2, y2 = self.ftext.bbox()
self.item = self.paper.create_rectangle( x1, y1, x2, y2, fill='',
outline='', tags=('text'))
self.selector = self.paper.create_rectangle( x1, y1, x2, y2,
fill=self.area_color, outline='', tags='helper_a')
self.ftext.lift()
self.paper.lift( self.item)
self.paper.register_id( self.item, self)
def redraw( self):
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
if self.selector:
self.paper.delete( self.selector)
if self.ftext:
self.ftext.delete()
self.draw()
if self._selected:
self.select()
def focus( self):
if self.selector:
self.paper.itemconfig( self.selector, fill='gray')
def unfocus( self):
if self.selector:
self.paper.itemconfig( self.selector, fill=self.area_color)
def select( self):
if self.selector:
self.paper.itemconfig( self.selector, outline='black')
self._selected = 1
def unselect( self):
if self.selector:
self.paper.itemconfig( self.selector, outline=self.area_color)
self._selected = 0
def move( self, dx, dy):
"""moves object with his selector (when present)"""
self.x += dx
self.y += dy
self.paper.move( self.item, dx, dy)
if self.selector:
self.paper.move( self.selector, dx, dy)
if self.ftext:
self.ftext.move( dx, dy)
def move_to( self, x, y):
dx = x - self.x
dy = y - self.y
self.set_xy( x, y)
self.paper.move( self.item, dx, dy)
if self.selector:
self.paper.move( self.selector, dx, dy)
if self.ftext:
self.ftext.move_to( x, y)
def get_x( self):
return self.x
def get_y( self):
return self.y
def get_xy( self):
return self.x, self.y
def delete( self):
if self.focus_item:
self.unfocus()
if self.selector:
self.paper.delete( self.selector)
self.selector = None
if self.item:
self.paper.unregister_id( self.item)
self.paper.delete( self.item)
if self.ftext:
self.ftext.delete()
return self
def read_package( self, package):
pos = package.getElementsByTagName( 'point')[0]
x, y, z = self.paper.read_xml_point( pos)
self.set_xy( x, y)
ft = package.getElementsByTagName('ftext')
self.parsed_text = ft[0]
self.text = reduce( operator.add, [e.toxml() for e in ft[0].childNodes], '')
fnt = package.getElementsByTagName('font')
if fnt:
fnt = fnt[0]
self.font_size = int( fnt.getAttribute( 'size'))
self.font_family = fnt.getAttribute( 'family')
if fnt.getAttribute( 'color'):
self.line_color = fnt.getAttribute( 'color')
if package.getAttribute( 'background-color'):
self.area_color = package.getAttribute( 'background-color')
def get_package( self, doc):
a = doc.createElement('text')
if self.area_color != '#ffffff':
a.setAttribute( 'background-color', self.area_color)
if self.font_size != 12 or self.font_family != 'helvetica' or
self.line_color != '#000':
font = dom_extensions.elementUnder( a, 'font', attributes=(('size', str(
self.font_size)), ('family', self.font_family)))
if self.line_color != '#000':
font.setAttribute( 'color', self.line_color)
x, y = self.paper.px_to_text_with_unit( (self.x, self.y))
dom_extensions.elementUnder( a, 'point', attributes=(('x', x),('y', y)))
a.appendChild( self.parsed_text)
return a
def set_text( self, text):
self.text = text
self.parsed_text = dom.parseString(
'<ftext>'+self.text+'</ftext>').childNodes[0]
def get_text( self):
return self.text
def bbox( self):
return self.ftext.bbox()
def update_font( self):
#if 'font_family' in self.__dict__ and 'font_size' in self.__dict__:
self.font = tkFont.Font( family=self.font_family, size=self.font_size)
def scale_font( self, ratio):
"""scales font of text. does not redraw !!"""
self.font_size = int( round( self.font_size * ratio))
self.update_font()
def lift( self):
if self.selector:
self.paper.lift( self.selector)
if self.ftext:
self.ftext.lift()
if self.item:
self.paper.lift( self.item)