# -*- coding: iso-8859-1 -*- """ #------------------------------------------------------------------------------- # # Graphics.py # some graphic design functions # #------------------------------------------------------------------------------- # Functions to create complexe graphic component : # ------------------------------------------------ # build_zinc_item (realize a zinc item from description hash table # management of enhanced graphics functions) # # repeat_zinc_item (duplication of given zinc item) # # Function to compute complexe geometrical forms : # (text header of functions explain options for each form, # function return curve coords using control points of cubic curve) # ----------------------------------------------------------------- # rounded_rectangle_coords (return curve coords of rounded rectangle) # hippodrome_coords (return curve coords of circus form) # ellipse_coords (return curve coords of ellipse form) # polygon_coords (return curve coords of regular polygon) # roundedcurve_coords (return curve coords of rounded curve) # polyline_coords (return curve coords of polyline) # shiftpath_coords (return curve coords of shifting path) # tabbox_coords (return curve coords of tabbox's pages) # pathline_coords (return triangles coords of pathline) # # Function to compute 2D 1/2 relief and shadow : # function build zinc items (triangles and curve) to simulate this # ----------------------------------------------------------------- # graphicitem_relief (return triangle items simulate relief of given item) # polyline_relief_params (return triangle coords # and lighting triangles color list) # graphicitem_shadow (return triangles and curve items # simulate shadow of given item)) # polyline_shadow_params (return triangle and curve coords # and shadow triangles color list)) # # #------------------------------------------------------------------------------- # Authors: Jean-Luc Vinot # PM2PY: Guillaume Vidon # # $Id$ #------------------------------------------------------------------------------- """ VERSION = "1.0" __revision__ = "0.1" import logging import types import re from geometry import * from pictorial import * from math import pi, radians, atan2, sqrt, sin, cos graphiclogger = logging.getLogger('Graphics') debug = graphiclogger.debug error = graphiclogger.error exception = graphiclogger.exception log = lambda msg : graphiclogger.log(logging.DEBUG, msg) # constante facteur point directeur (conique -> quadratique) const_ptd_factor = .5523 def transdic(**dict): newdic={} for key, val in dict.items(): if (type(val) is types.ListType): newdic[key] = tuple(val) else: newdic[key] = val return newdic def is_flat_list(apts): if reduce(lambda x, y : x and (type(y) in ( types.FloatType, types.IntType)), apts): if len(apts) % 2: raise ValueError("Not a valid Coords list") else : return True else : return False def is_point(apoint): if (type(apoint) in ( types.TupleType, types.ListType)): if len(apoint) == 2 : if reduce(lambda x, y : x and (type(y) in ( types.FloatType, types.IntType)), apts): return True else : return False elif reduce(lambda x, y : x and (type(y) in ( types.FloatType, types.IntType)), apts[:-1])\ and type(apts[-1]) in ('c', 'n'): return True else : return False else : return False # def is_tuple_list(apts): # if reduce(lambda x, y : x \ # and is_point(x)), # apts): def lpts2coords(lpts): coords = [] if (type(lpts) in ( types.TupleType, types.ListType )): for point in lpts : coords.append(tuple(point)) return tuple(coords) def coords2lpts(coords): lpts = [] if (type(coords) in ( types.TupleType, types.ListType )): for point in coords : lpts.append(list(point)) else : raise ValueError("Invalid Coords %s "%coords) return lpts def build_zinc_item(widget, pgroup = 1, **options): """ #--------------------------------------------------------------------------- # Graphics::build_zinc_item # Création d'un objet Zinc de représentation #--------------------------------------------------------------------------- # types d'items valides : # les items natifs zinc : group, rectangle, arc, curve, text, icon # les items ci-après permettent de spécifier des curves 'particulières' : # -roundedrectangle : rectangle à coin arrondi # -hippodrome : hippodrome # -ellipse : ellipse un centre 2 rayons # -polygone : polygone régulier à n cotés (convexe ou en étoile) # -roundedcurve : curve multicontours à coins arrondis (rayon unique) # -polyline : curve multicontours à coins arrondis (le rayon pouvant # être défini # spécifiquement pour chaque sommet) # -pathline : création d'une ligne 'épaisse' avec l'item Zinc # triangles décalage par rapport à un chemin donné # (largeur et sens de décalage) # dégradé de couleurs de la ligne (linéaire, transversal # ou double) #--------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget Zinc # parentgroup : identifiant du group parent # # options : # -itemtype : type de l'item à construire (type zinc ou metatype) # -coords : coordonnées de l'item # -metacoords : calcul de coordonnées par type # d'item différent de itemtype # -contours : paramètres multi-contours # -params : arguments spécifiques de l'item à passer # au widget # -addtags : [list of specific tags] to add to params -tags # -texture : ajout d'une texture à l'item # -pattern : ajout d'un pattern à l'item # -relief : création d'un relief à l'item invoque la fonction # graphicitem_relief() # -shadow : création d'une ombre portée à l'item invoque # la fonction graphicitem_shadow() # -scale : application # d'une transformation zinc->scale à l'item # -translate : <[delta_x,delta_y]> application d'un transformation zinc->translate # à l'item. # -rotate : application d'une transformation zinc->rotate # (en degré) à l'item # -name : nom de l'item # spécifiques item group : # -clip : paramètres de clipping d'un item group # (coords ou item) # -items : appel récursif de la fonction permettant # d'inclure des items au groupe #--------------------------------------------------------------------------- # #--------------------------------------------------------------------------- """ if options.has_key('parentgroup'): parentgroup = options['parentgroup'] else : parentgroup = pgroup try: itemtype = options['itemtype'] except KeyError: raise ValueError("Must have itemtype option") try: coords = options['coords'] except KeyError: try: coords = options['metacoords'] except KeyError : raise ValueError("Must have coords or metacoords option") if options.has_key('params'): params = options['params'] else: params = {} #return unless($widget and $itemtype #and ($coords or $options{'-metacoords'})) try: name = options['name'] except KeyError: name = None item = None metatype = None items = [] reliefs = [] shadows = [] tags = [] #-------------------- # GEOMETRIE DES ITEMS # gestion des types d'items particuliers et à raccords circulaires if (itemtype in ( 'roundedrectangle', 'hippodrome', 'polygone', 'ellipse', 'roundedcurve', 'polyline', 'curveline')): # par défaut la curve sera fermée -closed = 1 if not params.has_key('closed'): params['closed'] = 1 metatype = itemtype itemtype = 'curve' # possibilité de définir les coordonnées initiales par metatype if (options.has_key('metacoords')) : options['coords'] = meta_coords( **options['metacoords']) # création d'une pathline à partir d'item zinc triangles elif (itemtype == 'pathline') : itemtype = 'triangles' if (options.has_key('metacoords')) : coords = meta_coords( **options['metacoords']) if (options.has_key('graduate')) : numcolors = len(coords) lcolors = path_graduate(widget, numcolors, options['graduate']) params['colors'] = tuple(lcolors) if options.has_key('coords'): coords = pathline_coords(**options) else: coords = pathline_coords(coords, **options) # création d'une boite à onglet elif (itemtype == 'tabbox') : return build_tabboxitem(widget, parentgroup, **options) # calcul des coordonnées finales de la curve if (metatype is not None): coords = meta_coords(type = metatype, **options) # gestion du multi-contours (accessible pour tous les types # d'items géometriques) if (options.has_key('contours') and (metatype is not None)) : lcontours = options['contours'] contours=[] numcontours = len(contours) for contour in lcontours: # radius et corners peuvent être défini # spécifiquement pour chaque contour (atype, way, addcoords,) = contour[:3] if len(contour) >= 4: radius = contour[3] else : radius = None if len(contour) >= 5: corners = contour[4] else: corners = None if len(contour) >= 6: corners_radius = contour[5] else : corners_radius = None if (radius is None): if options.has_key('radius'): radius = options['radius'] else : raise ValueError("radius option requiered") newcoords = meta_coords(type = metatype, coords = addcoords, radius = radius, corners = corners, corners_radius = corners_radius ) contours.append((atype, way, newcoords)) options['contours'] = contours #---------------------- # REALISATION DES ITEMS # ITEM GROUP # gestion des coordonnées et du clipping if (itemtype == 'group') : item = widget.add(itemtype, parentgroup, **params) widget.addtag_withtag(name, item) if coords: widget.coords(item, tuple(coords)) # clipping du groupe par item ou par géometrie if (options.has_key('clip')) : clipbuilder = options['clip'] clip = None # création d'un item de clipping if (type(clipbuilder) is types.DictType and clipbuilder.has_key('itemtype')): clip = build_zinc_item(widget, item, **clipbuilder) elif (type(clipbuilder) in (types.TupleType, types.ListType) or widget.type(clipbuilder)) : clip = clipbuilder if (clip): widget.itemconfigure(item, clip = clip) # créations si besoin des items contenus dans le groupe if (options.has_key('items') and type(options['items']) is types.DictType) : for (itemname, itemstyle) in options['items'].items() : if not itemstyle.has_key('name'): itemstyle['name'] = itemname build_zinc_item(widget, item, **itemstyle) # ITEM TEXT ou ICON elif (itemtype in ('text', 'icon')) : imagefile = None if (itemtype == 'icon') : imagefile = params['image'] image = get_image(widget, imagefile) if (image) : params['image'] = image else: params['image'] = "" item = widget.add(itemtype, parentgroup, position = coords, **params ) if imagefile: params['image'] = imagefile # ITEMS GEOMETRIQUES -> CURVE else : nparams=params item = widget.add(itemtype, parentgroup, lpts2coords(coords), **params ) if (itemtype == 'curve' and options.has_key('contours')) : for contour in options['contours'] : contour = list(contour) contour[2] = tuple(contour[2]) widget.contour(item, *contour) # gestion du mode norender if (options.has_key('texture')) : texture = get_texture(widget, options['texture']) if texture: widget.itemconfigure(item, tile = texture) if (options.has_key('pattern')) : bitmap = get_pattern(**options['pattern']) if bitmap: widget.itemconfigure(item, fillpattern = bitmap) # gestion des tags spécifiques if (options.has_key('addtags')) : tags = options['addtags'] params_tags = params['tags'] if params_tags: tags.extend(params_tags) widget.itemconfigure(item, tags = tags) #------------------------------- # TRANSFORMATIONS ZINC DE L'ITEM # transformation scale de l'item si nécessaire if (options.has_key('scale')) : scale = options['scale'] if (type(scale) is not types.TupleType) : scale = (scale, scale) widget.scale(item, scale) # transformation rotate de l'item si nécessaire if (options.has_key('rotate')): widget.rotate(item, radians(options['rotate'])) # transformation translate de l'item si nécessaire if (options.has_key('translate')): widget.translate(item, options['translate']) # répétition de l'item if (options.has_key('repeat')) : items.extend((item, repeat_zinc_item(widget, item, **options['repeat']))) #----------------------- # RELIEF ET OMBRE PORTEE # gestion du relief if (options.has_key('relief')) : if (len(items)) : target = items else: target = item reliefs.extend(graphicitem_relief(widget, target, **options['relief'])) # gestion de l'ombre portée if (options.has_key('shadow')) : if (len(items)) : target = items else: target = item shadows.extend(graphicitem_shadow(widget, target, **options['shadow'])) if len(reliefs): items.extend(reliefs) if len(shadows): items.extend(shadows) if len(items): return items else: return item def repeat_zinc_item(widget, item, num = 2, dxy = (0,0), angle = None, params = None, copytag = None) : """ #--------------------------------------------------------------------------- # Graphics::repeat_zinc_item # Duplication (clonage) d'un objet Zinc de représentation #--------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget zinc # item : identifiant de l'item source # options : # -num : nombre d'item total (par defaut 2) # -dxy : <[delta_x, delta_y]> translation entre 2 duplications (par defaut [0,0]) # -angle : rotation entre 2 duplications # -copytag : ajout d'un tag indexé pour chaque copie # -params : {clef => [value list]}> valeur de paramètre # de chaque copie #--------------------------------------------------------------------------- """ clones = [] delta_x, delta_y = dxy # duplication d'une liste d'items -> appel récursif if (type(item) in (types.TupleType, types.ListType)) : for part in item : clones.append(repeat_zinc_item(widget, part, dxy, angle, params, copytag)) return clones tags = [] if (copytag) : tags = widget.itemcget(item, 'tags') widget.itemconfigure(item, tags = tags + ("%s0"%copytag,)) for i in xrange(1, num) : clone = None if (copytag) : clone = widget.clone(item, tags = tags + ("%s%s"%(copytag, i),)) else : clone = widget.clone(item) clones.append(clone) widget.translate(clone, delta_x*i, delta_y*i) if angle : widget.rotate(clone, radians(angle*i)) if (params is not None ) : widget.itemconfigure(clone, **params ) return clones #MUST BE TESTED def meta_coords( type, coords, **options ): """ #--------------------------------------------------------------------------- # FONCTIONS GEOMETRIQUES #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- # Graphics::meta_coords # retourne une liste de coordonnées en utilisant la fonction du type # d'item spécifié #--------------------------------------------------------------------------- # paramètres : (passés par %options) # -type : type de primitive utilisée # -coords : coordonnées nécessitée par la fonction [type]_coords # # les autres options spécialisées au type seront passés # à la fonction [type]coords #--------------------------------------------------------------------------- """ pts = None if (type == 'roundedrectangle'): log('Coords for roundedrectangle') pts = rounded_rectangle_coords(coords, **options) elif (type == 'hippodrome') : log('Coords for hippodrome') pts = hippodrome_coords(coords, **options) elif (type == 'ellipse') : log('Coords for ellipse') pts = ellipse_coords(coords, **options) elif (type == 'roundedcurve') : log('Coords for roundedcurve') pts = roundedcurve_coords(coords, **options) elif (type == 'polygone') : log('Coords for polygone') pts = polygon_coords(coords, **options) elif (type == 'polyline') : log('Coords for polyline') pts = polyline_coords(coords, **options) elif (type == 'curveline') : log('Coords for curveline') pts = curveline_coords(coords, **options) return pts def zincitem_2_curvecoords( widget, item, linear = 0, realcoords = 0, adjust = 1, ): """ #-------------------------------------------------------------------------- # Graphics::zincitem_2_curvecoords # retourne une liste des coordonnées 'Curve' d'un l'item Zinc # rectangle, arc ou curve #-------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget zinc # item : identifiant de l'item source # options : # -linear : réduction à des segments non curviligne # (par défaut 0) # -realcoords : coordonnées à transformer dans le groupe père # (par défaut 0) # -adjust : ajustement de la courbe de bezier (par défaut 1) #-------------------------------------------------------------------------- """ itemtype = widget.type(item) if not itemtype : raise ValueError("Not a Valid Item %s" % item) itemcoords = widget.coords(item) coords = None multi = [] if (itemtype == 'rectangle') : coords = rounded_rectangle_coords(itemcoords, radius = 0) elif (itemtype == 'arc') : coords = ellipse_coords(itemcoords) if linear : coords = curve2polyline_coords(coords, adjust) elif (itemtype == 'curve') : numcontours = widget.contour(item) if (numcontours < 2) : if linear: coords = curve2polyline_coords(itemcoords, adjust) else : if (linear) : multi = curveitem2polyline_coords(widget, item) else : for contour in xrange(0, numcontours): points = widget.coords(item, contour) multi.extend(points) coords = multi if (realcoords) : parentgroup = widget.group(item) if (len(multi)) : newcoords = [] for points in multi : transcoords = widget.transform(item, parentgroup, points) newcoords.extend(transcoords) coords = newcoords else : transcoords = widget.transform(item, parentgroup, coords) coords = transcoords return coords def rounded_rectangle_coords( coords, **options): """ #--------------------------------------------------------------------------- # Graphics::rounded_rectangle_coords # calcul des coords du rectangle à coins arrondis #--------------------------------------------------------------------------- # paramètres : # coords : coordonnées bbox (haut-gauche et bas-droite) # du rectangle # options : # -radius : rayon de raccord d'angle # -corners : liste des raccords de sommets # [0 (aucun raccord)|1] par défaut [1,1,1,1] #--------------------------------------------------------------------------- """ (x_0, y_0, x_n, y_n) = (coords[0][0], coords[0][1], coords[1][0], coords[1][1]) if (options.has_key('radius')): radius = options['radius'] else: radius = None if (options.has_key('corners')): corners = options['corners'] else: corners = [1, 1, 1, 1] # attention aux formes 'négatives' if (x_n < x_0) : (x_0, x_n) = (x_n, x_0) if (y_n < y_0) : (y_0, y_n) = (y_n, y_0) height = min(x_n -x_0, y_n - y_0) #radius non defini dans les parametres if (radius is None) : radius = int(height/10) radius = max(radius, 3) #radius defini mais trop petit if ( radius < 2) : return ((x_0, y_0), (x_0, y_n), (x_n, y_n), (x_n, y_0)) # correction de radius si necessaire max_rad = height #CODE BIZARRE #Comment corners ne peut être non défini #a ce niveau ? # max_rad /= 2 if (!defined corners) if (corners is None): max_rad /= 2 radius = min(max_rad, radius) # points remarquables ptd_delta = radius * const_ptd_factor (x_2, x_3) = (x_0 + radius, x_n - radius) (x_1, x_4) = (x_2 - ptd_delta, x_3 + ptd_delta) (y_2, y_3) = (y_0 + radius, y_n - radius) (y_1, y_4) = (y_2 - ptd_delta, y_3 + ptd_delta) # liste des 4 points sommet du rectangle : angles sans raccord circulaire angle_pts = ((x_0, y_0), (x_0, y_n), (x_n, y_n), (x_n, y_0)) # liste des 4 segments quadratique : raccord d'angle = radius roundeds = [[(x_2, y_0), (x_1, y_0, 'c'), (x_0, y_1, 'c'), (x_0, y_2),], [(x_0, y_3), (x_0, y_4, 'c'), (x_1, y_n, 'c'), (x_2, y_n),], [(x_3, y_n), (x_4, y_n, 'c'), (x_n, y_4, 'c'), (x_n, y_3),], [(x_n, y_2), (x_n, y_1, 'c'), (x_4, y_0, 'c'), (x_3, y_0),]] pts = [] previous = None for i in xrange(0, 4): #BUGS ?? if (corners[i]): if (previous is not None) : # on teste si non duplication de point (nx, ny) = roundeds[i][0] if (previous[0] == nx and previous[1] == ny) : pts.pop() pts.extend(roundeds[i]) previous = roundeds[i][3] else : pts.append(angle_pts[i]) return pts def ellipse_coords(coords, **options): """ #--------------------------------------------------------------------------- # Graphics::ellipse_coords # calcul des coords d'une ellipse #--------------------------------------------------------------------------- # paramètres : # coords : coordonnées bbox du rectangle exinscrit # options : # -corners : liste des raccords de sommets # [0 (aucun raccord)|1] par défaut [1,1,1,1] #--------------------------------------------------------------------------- """ (x_0, y_0, x_n, y_n) = (coords[0][0], coords[0][1], coords[1][0], coords[1][1]) if options.has_key('corners') : corners = options.has_key('corners') else: corners = [1, 1, 1, 1] # attention aux formes 'négatives' if (x_n < x_0) : xs = x_0 (x_0, x_n) = (x_n, xs) if (y_n < y_0) : ys = y_0 (y_0, y_n) = (y_n, ys) # points remarquables delta_x = (x_n - x_0)/2 * const_ptd_factor delta_y = (y_n - y_0)/2 * const_ptd_factor (x_2, y_2) = ((x_0+x_n)/2, (y_0+y_n)/2) (x_1, x_3) = (x_2 - delta_x, x_2 + delta_x) (y_1, y_3) = (y_2 - delta_y, y_2 + delta_y) # liste des 4 points sommet de l'ellipse : angles sans raccord circulaire angle_pts = ((x_0, y_0), (x_0, y_n), (x_n, y_n), (x_n, y_0)) # liste des 4 segments quadratique : raccord d'angle = arc d'ellipse roundeds = (((x_2, y_0), (x_1, y_0, 'c'), (x_0, y_1, 'c'), (x_0, y_2), ), ((x_0, y_2), (x_0, y_3, 'c'), (x_1, y_n, 'c'), (x_2, y_n), ), ((x_2, y_n), (x_3, y_n, 'c'), (x_n, y_3, 'c'), (x_n, y_2), ), ((x_n, y_2), (x_n, y_1, 'c'), (x_3, y_0, 'c'), (x_2, y_0), )) pts = [] previous = None for i in xrange(0, 4): if (corners[i]) : if (previous) : # on teste si non duplication de point (nx, ny) = roundeds[i][0] if (previous[0] == nx and previous[1] == ny) : pts.pop() pts.extend(roundeds[i]) previous = roundeds[i][3] else : pts.append(angle_pts[i]) return pts def hippodrome_coords(coords, **options) : """ #--------------------------------------------------------------------------- # Graphics::hippodrome_coords # calcul des coords d'un hippodrome #--------------------------------------------------------------------------- # paramètres : # coords : coordonnées bbox du rectangle exinscrit # options : # -orientation : orientation forcée de l'hippodrome [horizontal|vertical] # -corners : liste des raccords de sommets [0|1] par défaut [1,1,1,1] # -trunc : troncatures [left|right|top|bottom|both] #--------------------------------------------------------------------------- """ (x_0, y_0, x_n, y_n) = (coords[0][0], coords[0][1], coords[1][0], coords[1][1]) if (options.has_key('orientation')) : orientation = options['orientation'] else: orientation = 'none' # orientation forcée de l'hippodrome # (sinon hippodrome sur le plus petit coté) if (orientation == 'horizontal') : height = abs(y_n - y_0) elif (orientation == 'vertical') : height = abs(x_n - x_0) else: height = min(abs(x_n - x_0), abs(y_n - y_0)) radius = height/2 corners = (1, 1, 1, 1) if (options.has_key('corners')) : corners = options['corners'] elif (options.has_key('trunc')) : trunc = options['trunc'] if (trunc == 'both') : return ((x_0, y_0), (x_0, y_n), (x_n, y_n), (x_n, y_0)) else : if (trunc == 'left'): corners = (0, 0, 1, 1) elif (trunc == 'right'): corners = (1, 1, 0, 0) elif (trunc == 'top'): corners = (0, 1, 1, 0) elif (trunc == 'bottom') : corners = (1, 0, 0, 1) else : corners = (1, 1, 1, 1) # l'hippodrome est un cas particulier de roundedRectangle # on retourne en passant la 'configuration' à la fonction # générique rounded_rectangle_coords return rounded_rectangle_coords(coords, radius = radius, corners = corners) def polygon_coords(coords, **options): """ #--------------------------------------------------------------------------- # Graphics::polygon_coords # calcul des coords d'un polygone régulier #--------------------------------------------------------------------------- # paramètres : # coords : point centre du polygone # options : # -numsides : nombre de cotés # -radius : rayon de définition du polygone # (distance centre-sommets) # -inner_radius : rayon interne (polygone type étoile) # -corners : liste des raccords de sommets [0|1] # par défaut [1,1,1,1] # -corner_radius : rayon de raccord des cotés # -startangle : angle de départ en degré du polygone #--------------------------------------------------------------------------- """ if options.has_key('numsides'): numsides = options['numsides'] else : numsides = 0 if options.has_key('radius'): radius = options['radius'] else: radius = None if (numsides < 3 or not radius) : raise ValueError("Vous devez au moins spécifier " +"un nombre de cotés >= 3 et un rayon...\n") if (coords is None): coords = (0, 0) if (options.has_key('startangle')) : startangle = options['startangle'] else: startangle = 0 anglestep = 360/numsides if options.has_key('inner_radius'): inner_radius = options['inner_radius'] else: inner_radius = None pts = [] # points du polygone for i in xrange(0, numsides): (xp, yp) = rad_point(coords, radius, startangle + (anglestep*i)) pts.append((xp, yp)) # polygones 'étoiles' if (inner_radius) : (xp, yp) = rad_point(coords, inner_radius, startangle + (anglestep*(i+ 0.5))) pts.append((xp, yp)) pts.reverse() if (options.has_key('corner_radius')) : if options.has_key('corners'): corners = options['corners'] else: corners = None return roundedcurve_coords(pts, radius = options['corner_radius'], corners = corners) else : return pts def rounded_angle(widget, parentgroup, coords, radius) : """ #--------------------------------------------------------------------------- # Graphics::rounded_angle # THIS FUNCTION IS NO MORE USED, NEITHER EXPORTED # curve d'angle avec raccord circulaire #--------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget Zinc # parentgroup : identifiant de l'item group parent # coords : les 3 points de l'angle # radius : rayon de raccord #--------------------------------------------------------------------------- """ (pt0, pt1, pt2) = coords (corner_pts, center_pts) = rounded_angle_coords(coords, radius) (cx_0, cy_0) = center_pts if (parentgroup is None) : parentgroup = 1 pts = [pt0] pts.extend(corner_pts) pts.append(pt2) widget.add('curve', parentgroup, lpts2coords(pts), closed = 0, linewidth = 1, priority = 20, ) def rounded_angle_coords (coords, radius) : """ #--------------------------------------------------------------------------- # Graphics::rounded_angle_coords # calcul des coords d'un raccord d'angle circulaire #--------------------------------------------------------------------------- # le raccord circulaire de 2 droites sécantes est traditionnellement # réalisé par un # arc (conique) du cercle inscrit de rayon radius tangent à ces 2 droites # # Quadratique : # une approche de cette courbe peut être réalisée simplement par le calcul # de 4 points # spécifiques qui définiront - quelle que soit la valeur de l'angle formé # par les 2 # droites - le segment de raccord : # - les 2 points de tangence au cercle inscrit seront les points de début # et de fin # du segment de raccord # - les 2 points de controle seront situés chacun sur le vecteur # reliant le point de # tangence au sommet de l'angle (point secant des 2 droites) # leur position sur ce vecteur peut être simplifiée comme suit : # - à un facteur de 0.5523 de la distance au sommet pour # un angle >= 90° et <= 270° # - à une 'réduction' de ce point vers le point de tangence # pour les angles limites # de 90° vers 0° et de 270° vers 360° # ce facteur sera légérement modulé pour recouvrir plus précisement # l'arc correspondant #--------------------------------------------------------------------------- # coords : les 3 points de l'angle # radius : rayon de raccord #--------------------------------------------------------------------------- """ (pt0, pt1, pt2) = coords # valeur d'angle et angle formé par la bisectrice (angle, bisecangle) = vertex_angle(pt0, pt1, pt2) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(radius / asin) else: delta = radius # point centre du cercle inscrit de rayon $radius if (angle < 180) : refangle = bisecangle + 90 else : refangle = bisecangle - 90 (cx_0, cy_0) = rad_point(pt1, delta, refangle) # points de tangeance : pts perpendiculaires du centre aux 2 droites (px_1, py_1) = perpendicular_point((cx_0, cy_0), (pt0, pt1)) (px_2, py_2) = perpendicular_point((cx_0, cy_0), (pt1, pt2)) # point de controle de la quadratique # facteur de positionnement sur le vecteur pt.tangence, sommet ptd_factor = const_ptd_factor if (angle < 90 or angle > 270) : if (angle < 90) : diffangle = angle else: diffangle = 360 - angle if (diffangle > 15) : ptd_factor -= (((90 - diffangle)/90) * (ptd_factor/4)) ptd_factor = (diffangle/90) * (ptd_factor + ((1 - ptd_factor) * (90 - diffangle)/90)) else : diffangle = abs(180 - angle) if (diffangle > 15) : ptd_factor += (((90 - diffangle)/90) * (ptd_factor/3)) # delta xy aux pts de tangence (d1x, d1y) = ((pt1[0] - px_1) * ptd_factor, (pt1[1] - py_1) * ptd_factor) (d2x, d2y) = ((pt1[0] - px_2) * ptd_factor, (pt1[1] - py_2) * ptd_factor) # les 4 points de l'arc 'quadratique' corner_pts = [(px_1, py_1), (px_1+d1x, py_1+d1y, 'c'), (px_2+d2x, py_2+d2y, 'c'), (px_2, py_2)] # retourne le segment de quadratique et le centre du cercle inscrit return (corner_pts, (cx_0, cy_0)) def roundedcurve_coords(coords, **options): """ #--------------------------------------------------------------------------- # Graphics::roundedcurve_coords # retourne les coordonnées d'une curve à coins arrondis #--------------------------------------------------------------------------- # paramètres : # coords : liste de coordonnées des points de la curve # options : # -radius : rayon de raccord d'angle # -corners : liste des raccords de sommets [0|1] # par défaut [1,1,1,1] #--------------------------------------------------------------------------- """ numfaces = len(coords) curve_pts = [] if (options.has_key('radius')) : radius = options['radius'] else: radius = 0 corners = None if options.has_key('corners') : corners = options['corners'] for index in xrange(numfaces): if (corners is not None) : if (index+1 > len(corners)) or not corners[index] : curve_pts.append(coords[index]) continue if (index) : prev = index - 1 else : prev = numfaces - 1 if (index > numfaces - 2) : next = 0 else : next = index + 1 anglecoords = (coords[prev], coords[index], coords[next]) quad_pts = rounded_angle_coords(anglecoords, radius)[0] curve_pts.extend(quad_pts) return curve_pts def polyline_coords(coords, **options): """ #--------------------------------------------------------------------------- # Graphics::polyline_coords # retourne les coordonnées d'une polyline #--------------------------------------------------------------------------- # paramètres : # coords : liste de coordonnées des sommets de la polyline # options : # -radius : rayon global de raccord d'angle # -corners : liste des raccords de sommets [0|1] # par défaut [1,1,1,1], # -corners_radius : liste des rayons de raccords de sommets #--------------------------------------------------------------------------- """ numfaces = len(coords) curve_pts = [] if (options.has_key('radius')) : radius = options['radius'] else: radius = 0 if options.has_key('corners_radius'): corners_radius = options['corners_radius'] corners = corners_radius else: corners_radius = None if options.has_key('corners'): corners = options['corners'] else: corners = None for index in xrange(0, numfaces): if (corners is not None and (len(corners) - 1 < index or not corners[index])): curve_pts.append(coords[index]) else : if (index) : prev = index - 1 else: prev = numfaces - 1 if (index > numfaces - 2) : next = 0 else: next = index + 1 anglecoords = (coords[prev], coords[index], coords[next]) if (corners_radius) : rad = corners_radius[index] else: rad = radius quad_pts = rounded_angle_coords(anglecoords, rad)[0] curve_pts.extend(quad_pts) return curve_pts def pathline_coords (coords, **options): """ #--------------------------------------------------------------------------- # Graphics::pathline_coords # retourne les coordonnées d'une pathline #--------------------------------------------------------------------------- # paramètres : # coords : liste de coordonnées des points du path # options : # -closed : ligne fermée # -shifting : sens de décalage du path (par défaut center) # -linewidth : epaisseur de la ligne #--------------------------------------------------------------------------- """ numfaces = len(coords) pts = [] if options.has_key('closed'): closed = options['closed'] else: closed = None if (options.has_key('linewidth')) : linewidth = options['linewidth'] else: linewidth = 2 if (options.has_key('shifting')) : shifting = options['shifting'] else: shifting = 'center' if ( not numfaces or linewidth < 2): raise ValueError("Invalid PathLine_coords") if (closed) : previous = coords[numfaces - 1] else: previous = None next = coords[1] if (shifting == 'center'): linewidth /= 2 for i in xrange(0, numfaces): pt = coords[i] if (previous is None) : # extrémité de curve sans raccord -> angle plat previous = (pt[0] + (pt[0] - next[0]), pt[1] + (pt[1] - next[1])) (angle, bisecangle) = vertex_angle(previous, pt, next) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(linewidth / asin) else: delta = linewidth if (shifting == 'out' or shifting == 'in') : if (shifting == 'out') : adding = -90 else: adding = 90 pts.append(rad_point(pt, delta, bisecangle + adding)) pts.append(pt) else : pts.append(rad_point(pt, delta, bisecangle-90)) pts.append(rad_point(pt, delta, bisecangle+90)) if (i == numfaces - 2) : if (closed) : next = coords[0] else: next = (coords[i+1][0] + (coords[i+1][0] - pt[0]), coords[i+1][1] + (coords[i+1][1] - pt[1])) elif (i == numfaces - 1): next = None else : next = coords[i+2] previous = coords[i] if (closed) : pts.extend((pts[0], pts[1], pts[2], pts[3])) return pts def curveline_coords(coords, **options) : """ #--------------------------------------------------------------------------- # Graphics::curveline_coords # retourne les coordonnées d'une curveLine #--------------------------------------------------------------------------- # paramètres : # coords : liste de coordonnées des points de la ligne # options : # -closed : ligne fermée # -shifting : sens de décalage du contour # (par défaut center) # -linewidth : epaisseur de la ligne #--------------------------------------------------------------------------- """ numfaces = len(coords) gopts = [] backpts = [] pts = [] if options.has_key('closed'): closed = options['closed'] else: closed = None if (options.has_key('linewidth')) : linewidth = options['linewidth'] else: linewidth = 2 if (options.has_key('shifting')) : shifting = options['shifting'] else: shifting = 'center' if( not numfaces or linewidth < 2): raise ValueError("Bad coords %s or linewidth %s"%(numfaces, linewidth)) if (closed) : previous = coords[numfaces - 1] else: previous = None next = coords[1] if (shifting == 'center'): linewidth /= 2 for i in xrange(0, numfaces): pt = coords[i] if ( previous is None ) : # extrémité de curve sans raccord -> angle plat previous = (pt[0] + (pt[0] - next[0]), pt[1] + (pt[1] - next[1])) (angle, bisecangle) = vertex_angle(previous, pt, next) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(linewidth / asin) else: delta = linewidth if (shifting == 'out' or shifting == 'in') : if (shifting == 'out') : adding = -90 else: adding = 90 pts.append(rad_point(pt, delta, bisecangle + adding)) pts.append(pt) else : pts = rad_point(pt, delta, bisecangle+90) gopts.append(pts) pts = rad_point(pt, delta, bisecangle-90) backpts.insert(0, pts) if (i == numfaces - 2) : if (closed) : next = coords[0] else: next = (coords[i+1][0] + (coords[i+1][0] - pt[0]), coords[i+1][1] + (coords[i+1][1] - pt[1])) else : next = coords[i+2] previous = coords[i] gopts.extend(backpts) if (closed) : gopts.extend ((gopts[0], gopts[1])) return gopts def shiftpath_coords(coords, **options) : """ #--------------------------------------------------------------------------- # Graphics::shiftpath_coords # retourne les coordonnées d'un décalage de path #--------------------------------------------------------------------------- # paramètres : # coords : liste de coordonnées des points du path # options : # -closed : ligne fermée # -shifting : <'out'|'in'> sens de décalage du path (par défaut out) # -width : largeur de décalage (par défaut 1) #--------------------------------------------------------------------------- """ numfaces = len(coords) if options.has_key('closed'): closed = options['closed'] else: closed = None if (options.has_key('width')) : width = options['width'] else: width = 1 if (options.has_key('shifting')) : shifting = options['shifting'] else: shifting = 'out' if (not numfaces or not width): return coords pts = [] if (closed) : previous = coords[numfaces - 1] else: previous = None next = coords[1] for i in xrange(0, numfaces): pt = coords[i] if ( previous is None ) : # extrémité de curve sans raccord -> angle plat previous = (pt[0] + (pt[0] - next[0]), pt[1] + (pt[1] - next[1])) (angle, bisecangle) = vertex_angle(previous, pt, next) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(width / asin) else: delta = width if (shifting == 'out') : adding = -90 else: adding = 90 (x, y) = rad_point(pt, delta, bisecangle + adding) pts.append((x, y)) if (i > numfaces - 3) : if (closed) : next = coords[0] else: next = (pt[0] + (pt[0] - previous[0]), pt[1] + (pt[1] - previous[1])) else : next = coords[i+2] previous = coords[i] return pts def curveitem2polyline_coords(widget, item, **options): """ #--------------------------------------------------------------------------- # Graphics::curveitem2polyline_coords # Conversion des coordonnées Znitem curve (multicontours) # en coordonnées polyline(s) #--------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget zinc # item : identifiant de l'item source # options : # -tunits : nombre pas de division des segments bezier # (par défaut 20) # -adjust : ajustement de la courbe de bezier (par défaut 1) #--------------------------------------------------------------------------- """ if (not widget.type(item)): raise ValueError("Item Not Found") coords = [] numcontours = widget.contour(item) #parentgroup = widget.group(item) for contour in xrange(0, numcontours): points = widget.coords(item, contour) contourcoords = curve2polyline_coords(points, **options) coords.append(contourcoords) return coords def curve2polyline_coords(points, **options): """ #--------------------------------------------------------------------------- # Graphics::curve2polyline_coords # Conversion curve -> polygone #--------------------------------------------------------------------------- # paramètres : # points : liste des coordonnées curve à transformer # options : # -tunits : nombre pas de division des segments bezier # (par défaut 20) # -adjust : ajustement de la courbe de bezier (par défaut 1) #--------------------------------------------------------------------------- """ if (options.has_key('tunits')) : tunits = options['tunits'] else: tunits = 20 if (options.has_key('adjust')) : adjust = options['adjust'] else: adjust = 1 poly = [] previous = None bseg = [] numseg = 0 #prevtype = None for point in points: if len(point) == 3: (x, y, c) = point elif len(point) == 2: (x, y, c) = (point, None) else: ValueError("Bad point") if (c == 'c') : if not len(bseg) and previous: bseg.append(previous) bseg.append(point) else : if (len (bseg)) : bseg.append(point) if (adjust) : pts = bezier_compute(bseg, skipend = 1) del pts[0] del pts[0] poly.extend(pts) else : pts = bezier_segment(bseg, tunits = tunits, skipend = 1) del pts[0] del pts[0] poly.extend(pts) bseg = [] numseg += 1 #prevtype = 'bseg' else : poly.append((x, y)) #prevtype = 'line' previous = point return poly def build_tabboxitem(widget, parentgroup, **options): """ #------------------------------------------------------------------------------- # Graphics::build_tabboxitem # construit les items de représentations Zinc d'une boite à onglets #------------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget zinc # parentgroup : identifiant de l'item group parent # # options : # -coords : coordonnées haut-gauche et bas-droite # du rectangle # englobant du Tabbox # -params : arguments spécifiques des items curve # à passer au widget # -texture : ajout d'une texture aux items curve # -tabtitles : table de hash de définition des titres onglets # -pageitems : table de hash de définition des pages internes # -relief : table de hash de définition du relief de forme # # (options de construction géometrique passées à tabbox_coords) # -numpages : nombre de pages (onglets) de la boite # -anchor : <'n'|'e'|'s'|'w'> ancrage (positionnement) de la ligne # d'onglets # -alignment : <'left'|'center'|'right'> alignement des onglets sur le coté # d'ancrage # -tabwidth : <'auto'>|| : largeur des onglets # 'auto' largeur répartie, les largeurs sont auto-ajustée # si besoin. # -tabheight : <'auto'>| : hauteur des onglets # -tabshift : <'auto'>| offset de 'biseau' entre base et haut de # l'onglet (défaut auto) # -radius : rayon des arrondis d'angle # -overlap : <'auto'>| offset de recouvrement/séparation entre # onglets # -corners : liste 'spécifique' des raccords de sommets [0|1] #--------------------------------------------------------------------------- """ if options.has_key('coords') : coords = options['coords'] else: raise ValueError("Coords needed") if options.has_key('params'): params = options['params'] else: params = {} if params.has_key('tags'): tags = params['tags'] else : tags = [] texture = None if (options.has_key('texture')) : texture = get_texture(widget, options['texture']) if options.has_key('tabtitles'): titlestyle = options['tabtitles'] else : titlestyle = None if (titlestyle) : titles = titlestyle['text'] else: titles = None tabs = [] (shapes, tcoords, invert) = tabbox_coords(**options) if (invert) : k = len(shapes) else: k = -1 shapes.reverse() for shape in shapes : if (invert) : k -= 1 else : k += +1 group = widget.add('group', parentgroup) params['tags'] = tags params['tags'] += (k, 'intercalaire') form = widget.add('curve', group, lpts2coords(shape), **params) if texture : widget.itemconfigure(form, tile = texture) if (options.has_key('relief')) : graphicitem_relief(widget, form, **options['relief']) if (options.has_key('page')) : build_zinc_item(widget, group, **options['page']) if (titles) : if (invert) : tindex = k else: tindex = len(shapes) - k titlestyle['itemtype'] = 'text' titlestyle['coords'] = tcoords[tindex] titlestyle['params']['text'] = titles[tindex] ltags = list(tags) ltags.append(tindex) ltags.append('titre') titlestyle['params']['tags'] = tuple(ltags) build_zinc_item(widget, group, **titlestyle) return tabs def tabbox_coords(coords, **options): """ #--------------------------------------------------------------------------- # tabbox_coords # Calcul des shapes de boites à onglets #--------------------------------------------------------------------------- # paramètres : # coords : coordonnées haut-gauche bas-droite du rectangle # englobant de la tabbox # options # -numpages : nombre de pages (onglets) de la boite # -anchor : <'n'|'e'|'s'|'w'> ancrage (positionnement) de la # ligne d'onglets # -alignment : <'left'|'center'|'right'> alignement des onglets # sur le coté d'ancrage # -tabwidth : <'auto'>|| : largeur des onglets # 'auto' largeur répartie, les largeurs sont auto-ajustée # si besoin. # -tabheight : <'auto'>| : hauteur des onglets # -tabshift : <'auto'>| offset de 'biseau' entre base et haut # de l'onglet (défaut auto) # -radius : rayon des arrondis d'angle # -overlap : <'auto'>| offset de recouvrement/séparation # entre onglets # -corners : liste 'spécifique' des raccords # de sommets [0|1] #--------------------------------------------------------------------------- """ (x_0, y_0) = coords[0] (x_n, y_n) = coords[1] shapes, titles_coords = [], [] inverse = None #loptions = options.keys() if options.has_key('numpages'): numpages = options['numpages'] else: numpages = 0 if (not x_0 or not y_0 or not x_n or not y_n or not numpages) : raise ValueError("Vous devez au minimum spécifier\ le rectangle englobant et le nombre de pages") if (options.has_key('anchor')) : anchor = options['anchor'] else: anchor = 'n' if (options.has_key('alignment')) : alignment = options['alignment'] else: alignment ='left' if (options.has_key('tabwidth')) : nlen = options['tabwidth'] else: nlen ='auto' if (options.has_key('tabheight')) : thick = options['tabheight'] else: thick ='auto' if (options.has_key('tabshift')) : biso = options['tabshift'] else: biso = 'auto' if (options.has_key('radius')) : radius = options['radius'] else: radius = 0 if (options.has_key('overlap')) : overlap = options['overlap'] else: overlap = 0 if (options.has_key('corners')): corners = options['corners'] else: corners = None if (anchor in ( 'n', 's')) : orientation = 'horizontal' else: orientation = 'vertical' if (orientation == 'horizontal') : maxwidth = (x_n - x_0) else: maxwidth = (y_n - y_0) tabswidth = 0 align = 1 if (nlen == 'auto') : tabswidth = maxwidth nlen = float(tabswidth + (overlap * (numpages - 1)))/numpages else : if (type(nlen) in (types.TupleType, types.ListType )) : for w in nlen : tabswidth += (w - overlap) tabswidth += overlap else : tabswidth = (nlen * numpages) - (overlap * (numpages - 1)) if (tabswidth > maxwidth) : tabswidth = maxwidth nlen = float(tabswidth + (overlap * (numpages - 1)))/numpages if (alignment == 'center' and ((maxwidth - tabswidth) > radius)): align = 0 if (thick == 'auto') : if (orientation == 'horizontal') : thick = int((y_n - y_0)/10) else: thick = int((x_n - y_0)/10) thick = max(10, thick) thick = min(40, thick) if (biso == 'auto') : biso = int(thick/2) if ((alignment == 'right' and anchor != 'w') or (anchor == 'w' and alignment != 'right')) : if (type(nlen) in (types.TupleType, types.ListType)) : for p in xrange(0, numpages): nlen[p] *= -1 else : nlen *= -1 biso *= -1 overlap *= -1 if (alignment == 'center') : (biso1, biso2) = (biso/2, biso/2) else: (biso1, biso2) = (0, biso) cadre, tabdxy = [], [] xref, yref = 0, 0 if (orientation == 'vertical') : if (anchor == 'w'): thick *= -1 if (anchor == 'w') : (startx, endx) = (x_0, x_n) else: (startx, endx) = (x_n, x_0) if ((anchor == 'w' and alignment != 'right') or (anchor == 'e' and alignment == 'right')) : (starty, endy) = (y_n, y_0) else: (starty, endy) = (y_0, y_n) xref = startx - thick yref = starty if (alignment == 'center') : if (anchor == 'w') : ratio = -2 else: ratio = 2 yref += (float(maxwidth - tabswidth)/ratio) cadre = ((xref, endy), (endx, endy), (endx, starty), (xref, starty)) # flag de retournement de la liste des pts de curve si nécessaire # -> sens anti-horaire inverse = (alignment == 'right') else : if (anchor == 's'): thick *= -1 (starty, endy) = (y_n, y_0) else : (starty, endy) = (y_0, y_n) if (alignment == 'right') : (startx, endx) = (x_n, x_0) else: (startx, endx) = (x_0, x_n) yref = starty + thick if (alignment == 'center') : xref = x_0 + (float(maxwidth - tabswidth)/2) else : xref = startx cadre = ((endx, yref), (endx, endy), (startx, endy), (startx, yref)) # flag de retournement de la liste des pts de curve si nécessaire # -> sens anti-horaire inverse = ((anchor == 'n' and alignment != 'right') or (anchor == 's' and alignment == 'right')) for i in xrange(0, numpages): pts = [] # décrochage onglet #push (pts, ([xref, yref])) if i > 0 # cadre pts.extend(cadre) # points onglets if (i > 0 or not align) : pts.append((xref, yref)) if (type(nlen) in (types.TupleType, types.ListType)) : tw = nlen[i] else: tw = nlen if (type(nlen) in (types.TupleType, types.ListType)) : slen = len(nlen) else: slen = nlen if (orientation == 'vertical') : tabdxy = ((thick, biso1), (thick, tw - biso2), (0, tw)) else: tabdxy = ((biso1, -thick), (tw - biso2, -thick), (tw, 0)) for delta_xy in tabdxy : pts.append((xref + delta_xy[0], yref + delta_xy[1])) if (radius) : if (not options.has_key('corners')) : if (i > 0 or not align) : corners = (0, 1, 1, 0, 0, 1, 1, 0) else : corners = (0, 1, 1, 0, 1, 1, 0, 0, 0) curvepts = roundedcurve_coords(pts, radius = radius, corners = corners) lcurvepts = list(curvepts) if (inverse): lcurvepts.reverse() shapes.append(lcurvepts) else : if (inverse): pts.reverse() shapes.append(pts) if (orientation == 'horizontal') : titles_coords.append((float(xref) + (tw - (biso2 - biso1))/2, float(yref) - (thick/2))) xref += (tw - overlap) else : titles_coords.append( (float(xref) + (thick/2), yref + (slen - ((biso2 - biso1)/2))/2)) yref += (slen - overlap) return (shapes, titles_coords, inverse) def graphicitem_relief(widget, item, **options): """ #--------------------------------------------------------------------------- # Graphics::graphicitem_relief # construit un relief à l'item Zinc en utilisant des items Triangles #--------------------------------------------------------------------------- # paramètres : # widget : identifiant du widget zinc # item : identifiant de l'item zinc # options : table d'options # -closed : le relief assure la fermeture de forme (défaut 1) # -profil : <'rounded'|'flat'> type de profil (defaut 'rounded') # -relief : <'raised'|'sunken'> (défaut 'raised') # -side : <'inside'|'outside'> relief interne ou externe à la forme # (défaut 'inside') # -color : couleur du relief (défaut couleur de la forme) # -smoothed : facettes relief lissées ou non (défaut 1) # -lightangle : angle d'éclairage (défaut valeur générale widget) # -width : 'épaisseur' du relief en pixel # -fine : mode précision courbe de bezier # (défaut 0 : auto-ajustée) #------------------------------------------------------------------------------- """ items = [] # relief d'une liste d'items -> appel récursif if (type(item) in (types.TupleType, types.ListType)) : for part in item : items.extend(graphicitem_relief(widget, part, **options)) else : itemtype = widget.type(item) if not itemtype: raise ValueError("Bad Item") parentgroup = widget.group(item) if (options.has_key('priority')) : priority = options['priority'] else : priority = widget.itemcget(item, 'priority')+1 # coords transformés (polyline) de l'item adjust = not options['fine'] for coords in zincitem_2_curvecoords(widget, item, linear = 1, realcoords = 1, adjust = adjust) : (pts, colors) = polyline_relief_params(widget, item, coords, **options) items.append(widget.add('triangles', parentgroup, pts, priority = priority, colors = colors)) # renforcement du contour if (widget.itemcget(item, 'linewidth')) : items.append(widget.clone(item, filled = 0, priority = priority+1)) return items def polyline_relief_params(widget, item, coords, **options): """ #--------------------------------------------------------------------------- # Graphics::polyline_relief_params # retourne la liste des points et des couleurs nécessaires à la construction # de l'item Triangles du relief #--------------------------------------------------------------------------- # paramètres : # widget : identifiant widget Zinc # item : identifiant item Zinc # options : table d'options # -closed : le relief assure la fermeture de forme (défaut 1) # -profil : <'rounded'|'flat'> type de profil (defaut 'rounded') # -relief : <'raised'|'sunken'> (défaut 'raised') # -side : <'inside'|'outside'> relief interne ou externe à la forme # (défaut 'inside') # -color : couleur du relief (défaut couleur de la forme) # -smoothed : facettes relief lissées ou non (défaut 1) # -lightangle : angle d'éclairage (défaut valeur générale widget) # -width : 'épaisseur' du relief en pixel #--------------------------------------------------------------------------- """ if (options.has_key('closed')) : closed = options['closed'] else: closed = 1 if (options.has_key('profil')) : profil = options['profil'] else: profil = 'rounded' if (options.has_key('relief')) : relief = options['relief'] else : relief = 'raised' if (options.has_key('side')) : side = options['side'] else: side = 'inside' if (options.has_key('color')) : basiccolor = options['color'] else: basiccolor = zincitem_predominantcolor(widget, item) if (options.has_key('smooth')) : smoothed = options['smooth'] else: smoothed = 1 if (options.has_key('lightangle')) : lightangle = options['lightangle'] else: lightangle = widget.cget('lightangle') if options.has_key('width'): width = options['width'] else: raise ValueError('Options must have width field') if ( width < 1) : (x_0, y_0, x_1, y_1) = widget.bbox(item) width = min(x_1 -x_0, y_1 - y_0)/10 if (width < 2) : width = 2 numfaces = len(coords) if (closed): previous = coords[numfaces - 1] else: previous = None next = coords[1] pts = [] colors = [] alpha = 100 m = re.compile("^(?P#[0-9a-fA-F]{6});(?P\d{1,2})$") res = m.match(basiccolor) if (res is not None) : (basiccolor, alpha) = res.group('color'), res.group('alpha') if ( options.has_key('color')): color = options['color'] res = m.match(color) if ((res is None) and (profil == 'flat')): alpha /= 2 if (profil == 'rounded') : reliefalphas = [0, alpha] else: reliefalphas = [alpha, alpha] for i in xrange(0, numfaces): pt = coords[i] if (previous) : # extrémité de curve sans raccord -> angle plat previous = (pt[0] + (pt[0] - next[0]), pt[1] + (pt[1] - next[1])) (angle, bisecangle) = vertex_angle(previous, pt, next) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(width / asin) else: delta = width if (side == 'outside') : decal = -90 else: decal = 90 shift_pt = rad_point(pt, delta, bisecangle+decal) pts.append(shift_pt) pts.append(pt) if (smoothed and i) : pts.append(shift_pt) pts.append(pt) faceangle = 360 -(linenormal(previous, next)+90) light = abs(lightangle - faceangle) if (light > 180): light = 360 - light if light < 1: light = 1 if (relief == 'sunken') : lumratio = (180-light)/180 else: lumratio = light/180 if ( not smoothed and i) : #A VOIR #OBSCURE colors.extend((colors[-2], colors[-1])) if (basiccolor) : # création des couleurs dérivées shade = lightingcolor(basiccolor, lumratio) color0 = "%s;%s"% (shade, reliefalphas[0]) color1 = "%s;%s"% (shade, reliefalphas[1]) colors.extend((color0, color1)) else : c = (255*lumratio) color0 = hexargbcolor(c, c, c, reliefalphas[0]) color1 = hexargbcolor(c, c, c, reliefalphas[1]) colors.extend((color0, color1)) if (i == (numfaces - 2)) : if (closed) : next = coords[0] else: next = (coords[i+1][0] + (coords[i+1][0] - pt[0]), coords[i+1][1] + (coords[i+1][1] - pt[1])) else : next = coords[i+2] previous = coords[i] if (closed) : pts.extend((pts[0], pts[1], pts[2], pts[3])) colors.extend((colors[0], colors[1])) if (not smoothed) : pts.extend((pts[0], pts[1], pts[2], pts[3])) colors.extend((colors[0], colors[1])) return (pts, colors) def graphicitem_shadow(widget, item, **options): """ #--------------------------------------------------------------------------- # Graphics::graphicitem_shadow # Création d'une ombre portée à l'item #--------------------------------------------------------------------------- # paramètres : # widget : identifiant widget Zinc # item : identifiant item Zinc # options : table d'options # -opacity : opacité de l'ombre (défaut 50) # -filled : remplissage totale de l'ombre (hors bordure) (defaut 1) # -lightangle : angle d'éclairage (défaut valeur générale widget) # -distance : distance de projection de l'ombre en pixel # -enlarging : grossi de l'ombre portée en pixels (defaut 0) # -width : taille de diffusion/diffraction (défaut 4) # -color : couleur de l'ombre portée (défaut black) #--------------------------------------------------------------------------- """ items = [] # relief d'une liste d'items -> appel récursif if (type(item) in (types.TupleType, types.ListType)) : for part in item : items.append(graphicitem_shadow(widget, part, **options)) return items else : itemtype = widget.type(item) if not itemtype : raise ValueError("Not a valid Item Id %s"%item) # création d'un groupe à l'ombre portée if (options.has_key('parentgroup')) : parentgroup = options['parentgroup'] else: parentgroup = widget.group(item) if (options.has_key('priority')) : priority = options['priority'] else: priority = widget.itemcget(item, 'priority')-1 priority = max(0, priority) shadow = widget.add('group', parentgroup, priority = priority) if (itemtype == 'text') : if (options.has_key('opacity')) : opacity = options['opacity'] else: opacity = 50 if (options['color']) : color = options['color'] else: color = '#000000' clone = widget.clone(item, color = "%s;%s"% (color, opacity)) widget.chggroup(clone, shadow) else : # création des items (de dessin) de l'ombre if ( options.has_key('filled')) : filled = options['filled'] else: filled = 1 # coords transformés (polyline) de l'item for coords in zincitem_2_curvecoords(widget, item, linear = 1, realcoords = 1) : (t_pts, i_pts, colors) = polyline_shadow_params( coords, **options) # option filled : remplissage hors bordure # de l'ombre portée (item curve) if (filled) : if (len(items)) : widget.contour(items[0], 'add', 0, i_pts) else : items.append( widget.add('curve', shadow, i_pts, linewidth = 0, filled = 1, fillcolor = colors[0], )) # bordure de diffusion de l'ombre (item triangles) items.append( widget.add('triangles', shadow, t_pts, colors = colors)) # positionnement de l'ombre portée if (options.has_key('distance')) : distance = options['distance'] else: distance = 10 if (options.has_key('lightangle')) : lightangle = options['lightangle'] else: lightangle = widget.cget('lightangle') (delta_x, delta_y) = rad_point((0, 0), distance, lightangle+180) widget.translate(shadow, delta_x, -delta_y) return shadow def polyline_shadow_params(coords, **options): """ #--------------------------------------------------------------------------- # Graphics::polyline_shadow_params # retourne les listes des points et de couleurs nécessaires à la # construction des items triangles (bordure externe) et curve # (remplissage interne) de l'ombre portée #--------------------------------------------------------------------------- # paramètres : # coords : coordonnées # options : table d'options # -opacity : opacité de l'ombre (défaut 50) # -lightangle : angle d'éclairage (défaut valeur générale widget) # -distance : distance de projection de l'ombre en pixel # (défaut 10) # -enlarging : grossi de l'ombre portée en pixels (defaut 2) # -width : taille de diffusion/diffraction # (défaut distance -2) # -color : couleur de l'ombre portée (défaut black) #--------------------------------------------------------------------------- """ if (options.has_key('distance')) : distance = options['distance'] else: distance = 10 if (options.has_key('width')) : width = options['width'] else: width = distance-2 if (options.has_key('opacity')) : opacity = options['opacity'] else: opacity = 50 if (options.has_key('color')) : color = options['color'] else: color ='#000000' if (options.has_key('enlarging')) : enlarging = options['enlarging'] else : enlarging = 2 if (enlarging) : coords = shiftpath_coords(coords, width = enlarging, closed = 1, shifting = 'out') numfaces = len(coords) previous = coords[numfaces - 1] next = coords[1] t_pts = [] i_pts = [] colors = [] (color0, color1) = ("%s;%s"% (color, opacity), "%s;0"% color) for i in xrange(0, numfaces): pt = coords[i] #A VOIR #Je ne vois pas quand cela peut arriver if (not previous) : # extrémité de curve sans raccord -> angle plat previous = (pt[0] + (pt[0] - next[0]), pt[1] + (pt[1] - next[1])) (angle, bisecangle) = vertex_angle(previous, pt, next) # distance au centre du cercle inscrit : rayon/sinus demi-angle asin = sin(radians(angle/2)) if (asin) : delta = abs(width / asin) else : delta = width decal = 90 shift_pt = rad_point(pt, delta, bisecangle+decal) i_pts.append(shift_pt) t_pts.append(shift_pt) t_pts.append(pt) colors.append(color0) colors.append(color1) if (i == numfaces - 2) : next = coords[0] else : next = coords[i+2] previous = coords[i] # fermeture t_pts.extend((t_pts[0], t_pts[1], t_pts[2], t_pts[3])) i_pts.extend((t_pts[0], t_pts[1])) colors.extend((color0, color1, color0, color1)) return (t_pts, i_pts, colors) #Local Variables: #mode : python #tab-width: 4 #end: