Drawables.py :  » Development » Frowns » frowns » Depict » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Frowns 
Frowns » frowns » Depict » Drawables.py
"""Generate the drawables for a molecule

generateDrawables(atoms, bonds) -> (points, lines, boundingbox)

points are in the form ((x,y,symbol)...)
lines are in the form ((bondType, (x1,y1), (x2,y2))...
bounding box is in the form (minx, maxx, miny, maxy)

"""
import math
from LineGenerator import make_double_line
from frowns.Canonicalization.SmilesTokens import Atom
ORGANIC_SUBSET = ['B', 'C', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I']

def name_atom(atom):
    """->symbol
    Return the atom symbol for a depiction.  Carbons atoms
    in general are not returned"""

    symbol = "%s"%(atom.symbol,)
    weight = atom.weight
    charge = atom.charge
    hcount = atom.hcount
    explicit_hcount = atom.explicit_hcount

    out_symbol = symbol

    # pyrole like nitrogens
    if atom.aromatic and symbol == "N" and charge == 0 and \
       weight == 0 and explicit_hcount == 1 and hcount == 0:
        # XXX Fix Me
        # There should only be one of these per five membered
        # aromatic ring
        return "nH"


    if symbol in ORGANIC_SUBSET and atom.valences and \
       not weight and charge == 0:
        sumOrders = atom.sumBondOrders()
        hcount = atom.hcount
        sum = int(hcount + sumOrders)
        for valence in atom.valences:
            if valence == sum:
                if symbol == "C":
                    return ""
                else:
                    return symbol

    if not weight: weight = ""

    if charge == -1: charge = "-"
    elif charge == 1: charge = "+"
    elif charge > 1: charge = "+%s"%charge
    else: charge = ""

    if hcount == 1: hcount = "H"
    elif hcount == 0: hcount = ""
    elif hcount > 1: hcount = "H%s"%hcount
    else:
        raise "Negative hcount!!!"
    output = "%s%s%s%s"%(weight, out_symbol, hcount, charge)

    return output

def _recurse(atom, visited):
    """_recursion routine for detecting overlapping
    fragments"""
    
    for bond in atom.bonds:
        for next in bond.atoms:
            if not visited.has_key(next):
                visited[next] = 1
                _recurse(next, visited)    
                
def getOverlappingFragments(atoms):
    """given a list of atoms, return the overlapping
    components in a dictionary
    key = tuple of coordinates
    value = number of overlaps
    """
    _atoms = {}
    for atom in atoms:
        _atoms[atom] = 1

    components = {}

    while _atoms:
        start = _atoms.keys()[0]
        visited = {start:1}
        _recurse(start, visited)
        coords = []
        for atom in visited.keys():
            coords.append((atom.x, atom.y))
            del _atoms[atom]
        coords.sort()
        coords = tuple(coords)
        components[tuple(coords)] = components.get(coords,0)+1

    for key, val in components.items():
        if val == 1:
            del components[key]

    return components

def generateDrawables(atoms, bonds, drawAromatic=1, name_atom=name_atom):
    """(atoms, bonds, drawAromatic=1)->generate drawables from a collection of
    atoms and bonds.  The reason we aren't using a full molecule
    is that we would like to draw a portion of a molecule
    if necessary"""
    atomCoords = {}
    points = []
    lines = []
    wedges = []
    filledWedges = []
    minx = maxx = miny = maxy = None
    atomIndices = {}

    # find the min and max for the x and y coordinates
    for atom in atoms:
        x,y = atom.x, atom.y
        if minx is None or x<minx:
            minx = x
        if maxx is None or x>maxx:
            maxx = x
        if miny is None or y<miny:
            miny=y
        if maxy is None or y>maxy:
            maxy=y

        
    for atom in atoms:
        x,y = atomCoords[atom.handle] = atom.x, maxy-atom.y
        symbol = name_atom(atom)
        
        points.append((symbol, x, y))

    if maxy != None:
        maxy = maxy-miny
        miny = 0
    
    aromaticRings = {}

    minBondDist = 10e99
    
    for bond in bonds:
        atom1, atom2 = bond.atoms
        coord1, coord2 = atomCoords[atom1.handle], atomCoords[atom2.handle]

        if bond.stereo == "DOWN":
            x2, y2 = coord2
            x1, y1 = coord1
        else:
            x1, y1 = coord1
            x2, y2 = coord2
            
        dx = x1-x2
        dy = y1-y2
        d = math.sqrt(dx*dx+dy*dy)
        if d < minBondDist:
            minBondDist = d

        if bond.stereo:
            assert bond.bondtype == 1
            theta = -math.atan2(x2-x1, y2-y1)
            cosTHETA = math.cos(theta)
            sinTHETA = math.sin(theta)
            # XXX FIX ME, we should really know the
            #  font sizes here for proper drawing.
            if bond.atoms[1].symbol != "C":
                # shrink away from the endpoint
                offset = d * 0.8
            else:
                offset = d * 0.95

            if bond.atoms[0].symbol != "C":
                # shrink away from the start point
                dp = d * 0.1
                ox1, oy1 = x1 + dp*cosTHETA, y1 + dp*sinTHETA
            else:
                ox1, oy1 = x1, y1
                
            if bond.stereo == "UP":
                # make a filled polygon
                # coord1, coord2, coord3, filled 1/0
                # translate to "origin"

                length = d * 0.1

                wx1 = (length*cosTHETA - offset*sinTHETA) + x1
                wy1 = (length*sinTHETA + offset*cosTHETA) + y1

                wx2 = (-length*cosTHETA - offset*sinTHETA) + x1
                wy2 = (-length*sinTHETA + offset*cosTHETA) + y1
                wedges.append( (bond.bondorder, bond, (ox1,oy1), (wx1,wy1), (wx2,wy2), bond.stereo=="UP") )
            else:
                # assume bond stereo is down
                # we will make a cross hatch perpindicular to the bond
                for widthFactor in [0.2, 0.4, 0.6, 0.8, 1.0]:
                    newoffset = offset * widthFactor
                    length = 0.2 * newoffset
                    wx1 = (length*cosTHETA - newoffset*sinTHETA) + x1
                    wy1 = (length*sinTHETA + newoffset*cosTHETA) + y1
                    
                    wx2 = (-length*cosTHETA - newoffset*sinTHETA) + x1
                    wy2 = (-length*sinTHETA + newoffset*cosTHETA) + y1
                     
                    lines.append((bond.bondorder, bond, ((wx1, wy1), (wx2, wy2))))
            #lines.append((bond.bondorder, bond, (coord1, coord2)))
        elif not drawAromatic:
            lines.append((bond.bondorder, bond, (coord1, coord2)))
        else:
            if bond.bondtype == 4:
                atom1, atom2 = bond.atoms
                for ring in atom1.rings:
                    if not ring.aromatic:
                        continue
                    coords = aromaticRings.get(ring, {})
                    coords.update({coord1:ring})
                    aromaticRings[ring] = coords
                                        
                for ring in atom2.rings:
                    if not ring.aromatic:
                        continue
                    coords = aromaticRings.get(ring, {})
                    coords.update({coord2:ring})
                    aromaticRings[ring] = coords

            lines.append((bond.bondtype, bond, (coord1, coord2)))

    if minBondDist == 10e99:
        minBondDist = 1
    
    # XXX FIX Me, the circles can go outside
    # the lines occasionally
    circles = []
    for ring, ringAtoms in aromaticRings.items():
        coords = ringAtoms.keys()
        mx = 0.0
        my = 0.0
        for x, y in coords:
            mx += x
            my += y
        mx /= len(coords)
        my /= len(coords)

        # now get 90% of the smallest distance
        mindist = 10e99
        for x, y in coords:
            dx = x-mx
            dy = y-my
            dist = math.sqrt(dx*dx + dy*dy)
            if dist < mindist: mindist = dist

        circles.append((mx, my, mindist*.7))

    return points, lines, circles, wedges, (minx, maxx, miny, maxy), minBondDist
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.