Initial commit for the dynamic version
This commit is contained in:
commit
ef5be70e4c
|
@ -0,0 +1 @@
|
|||
__version__ = '1.2rc3.post152'
|
|
@ -0,0 +1,957 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Module calcio
|
||||
=============
|
||||
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
import ase
|
||||
import re
|
||||
|
||||
from msspec.misc import UREG, LOGGER
|
||||
|
||||
class PhagenIO(object):
|
||||
def __init__(self, phagen_parameters, malloc_parameters):
|
||||
self.parameters = phagen_parameters
|
||||
self.malloc_parameters = malloc_parameters
|
||||
|
||||
self.tl = None
|
||||
self.nat = None
|
||||
self.nateqm = None
|
||||
self.ne = None
|
||||
self.ipot = None
|
||||
self.lmax_mode = None
|
||||
self.nlmax = None
|
||||
self.energies = None
|
||||
|
||||
def write_input_file(self, filename='input.ms'):
|
||||
# in input folder
|
||||
atoms = self.parameters.atoms
|
||||
if not atoms.has('mt_radii'):
|
||||
for a in atoms:
|
||||
a.set('mt_radius', 0.)
|
||||
|
||||
if not atoms.has('mt_radii_scale'):
|
||||
for a in atoms:
|
||||
a.set('mt_radius_scale', 1.)
|
||||
|
||||
radii = atoms.get_array('mt_radii')
|
||||
radii_scale = atoms.get_array('mt_radii_scale')
|
||||
|
||||
if np.all(radii == 0) and np.all(radii_scale == 1):
|
||||
self.parameters.norman = 'stdcrm'
|
||||
elif np.all(radii == 0) and np.any(radii_scale != 1):
|
||||
self.parameters.norman = 'scaled'
|
||||
radii = radii_scale
|
||||
else:
|
||||
self.parameters.norman = 'extrad'
|
||||
radii *= radii_scale
|
||||
|
||||
parameters = []
|
||||
for parameter in self.parameters:
|
||||
name = parameter.name
|
||||
if name in ('ionicity', 'atoms'):
|
||||
# skip ionicity and atoms parameters as they are treated in
|
||||
# other sections
|
||||
continue
|
||||
value = parameter.value
|
||||
if isinstance(value, str) and not re.match('\..*\.', value):
|
||||
s = ' {}=\'{:s}\''.format(name, str(parameter))
|
||||
else:
|
||||
s = ' {}={:s}'.format(name, str(parameter))
|
||||
parameters.append(s)
|
||||
|
||||
header = " &job\n"
|
||||
header += ',\n'.join(parameters) + "\n &end\n"
|
||||
header += (' c Auto-generated file on {}\n Computes the T-matrix and '
|
||||
'radial matrix elements\n\n').format(
|
||||
datetime.ctime(datetime.now()))
|
||||
|
||||
# cluster section
|
||||
try:
|
||||
absorber = atoms.absorber
|
||||
except AttributeError as err:
|
||||
print(err)
|
||||
absorber = 0
|
||||
|
||||
cluster_section = ''
|
||||
absorber_line = ''
|
||||
line_format = '{:>10s}{:4d}{:11.6f}{:14.6f}{:14.6f}{:13.4f}\n'
|
||||
for iat, atom in enumerate(atoms):
|
||||
symbol = atom.symbol
|
||||
if symbol == 'X':
|
||||
symbol = 'ES'
|
||||
number = atom.number
|
||||
x, y, z = atom.position
|
||||
r = radii[iat]
|
||||
cluster_line = line_format.format(symbol, number, x, y, z, r)
|
||||
# store absober line
|
||||
if atom.index == absorber:
|
||||
absorber_line = cluster_line
|
||||
else:
|
||||
cluster_section += cluster_line
|
||||
|
||||
cluster_section = absorber_line + cluster_section
|
||||
cluster_section += '{:10d}{:4d}{:10.0f}{:9.0f}{:9.0f}{:8.0f}\n'.format(
|
||||
-1, -1, 0, 0, 0, 0)
|
||||
|
||||
# Ionicity section
|
||||
ionicity = self.parameters.ionicity
|
||||
ionicity_section = ''
|
||||
ionicity_format = '{:8d}{:9.2f}\n'
|
||||
symbols = set(atoms.get_chemical_symbols())
|
||||
for symbol in symbols:
|
||||
try:
|
||||
charge = ionicity[symbol]
|
||||
except KeyError:
|
||||
charge = 0.
|
||||
ionicity_section += ionicity_format.format(
|
||||
ase.data.atomic_numbers[symbol],
|
||||
charge)
|
||||
ionicity_section += '{:8d}\n'.format(-1)
|
||||
|
||||
content = header + cluster_section + ionicity_section
|
||||
|
||||
# Write the content to filename
|
||||
try:
|
||||
with open(filename, 'r') as fd:
|
||||
old_content = fd.read()
|
||||
except IOError:
|
||||
old_content = ''
|
||||
|
||||
pat = re.compile(r' c .*\n')
|
||||
|
||||
modified = False
|
||||
if pat.sub('', content) != pat.sub('', old_content):
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write(content)
|
||||
modified = True
|
||||
|
||||
return modified
|
||||
|
||||
def write_include_file(self, filename='msxas3.inc'):
|
||||
# read the whole include file content
|
||||
with open(filename, 'r') as fd:
|
||||
content = fd.read()
|
||||
|
||||
# backup the content in memory
|
||||
old_content = content
|
||||
|
||||
# replace the content
|
||||
for p in self.malloc_parameters:
|
||||
content = re.sub(r'({:s}\s*=\s*)\d+'.format(p.name),
|
||||
r'\g<1>{:d}'.format(p.value), content)
|
||||
|
||||
# actually write to the file only if different from the previous file
|
||||
modified = False
|
||||
if content != old_content:
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write(content)
|
||||
modified = True
|
||||
return modified
|
||||
|
||||
def load_tl_file(self, filename='tmatrix.tl'):
|
||||
atom_data = []
|
||||
|
||||
# load all the file in the string 'content'
|
||||
with open(filename, 'r') as fd:
|
||||
content = fd.read()
|
||||
|
||||
# convert the file to a (nat x ne) array
|
||||
#
|
||||
# first, split the content in a list for each atom
|
||||
pattern = re.compile(r'-+\s*ATOM.*-+')
|
||||
lines = pattern.split(content)
|
||||
|
||||
# get the number of atoms (nat) and the number of energy points (ne)
|
||||
nat, ne, _, ipot, lmax_mode = list(map(int, content.split('\n')[0].split()))
|
||||
self.nat = nat
|
||||
self.ne = ne
|
||||
self.ipot = ipot
|
||||
self.lmax_mode = lmax_mode
|
||||
|
||||
# extract atom data
|
||||
for line in lines:
|
||||
numbers_str = ''.join(line.strip().split('\n')).split()
|
||||
numbers = []
|
||||
for n in numbers_str:
|
||||
if not re.match(r'^\d+$', n):
|
||||
numbers.append(float(n))
|
||||
if len(numbers) > 0:
|
||||
array = np.array(numbers).reshape((-1, 4)) # pylint: disable=no-member
|
||||
atom_data.append(array)
|
||||
|
||||
# construct the data array
|
||||
data = []
|
||||
for i in range(nat):
|
||||
data.append([])
|
||||
for j in range(ne):
|
||||
data[i].append(atom_data[j * nat + i])
|
||||
|
||||
self.tl = data
|
||||
return data
|
||||
|
||||
def write_tl_file(self, filename='tmatrix.tl'):
|
||||
|
||||
def get_lmaxs(ie):
|
||||
lmaxs = np.zeros(int(4 * np.ceil(self.nat / 4.)), dtype=int)
|
||||
for a in range(self.nat):
|
||||
lmaxs[a] = len(self.tl[a][ie]) - 1
|
||||
lmaxs = lmaxs.reshape((-1, 4))
|
||||
return lmaxs
|
||||
|
||||
def get_energies(unit='eV'):
|
||||
emin = self.parameters.emin
|
||||
emax = self.parameters.emax
|
||||
delta = self.parameters.delta
|
||||
energies = np.arange(emin, emax, delta)
|
||||
if len(energies) == 0:
|
||||
energies = np.array([emin])
|
||||
if energies[-1] + delta / 2 < emax:
|
||||
energies = np.append(energies, energies[-1] + delta)
|
||||
|
||||
# conversion in eV
|
||||
if unit == 'eV':
|
||||
energies = (energies * UREG.Ry).to('eV')
|
||||
|
||||
return energies
|
||||
|
||||
def custom_strfloat(f):
|
||||
mantissa, exponent = '{:.10E}'.format(f).split('E')
|
||||
m = format(float(mantissa) / 10, '.6f').replace('-0', '-')
|
||||
e = format(int(exponent) + 1, '+03d')
|
||||
return ' {}E{}'.format(m, e)
|
||||
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write('{:>9}{:>9}{:>9}{:>9}{:>9}\n'.format(self.nat, self.ne, 1,
|
||||
self.ipot,
|
||||
self.lmax_mode))
|
||||
|
||||
nlmax = 0
|
||||
for ie in range(self.ne):
|
||||
# write all lmaxs for each energy set
|
||||
lmaxs = get_lmaxs(ie)
|
||||
nlmax = int(max(nlmax, np.max(lmaxs)))
|
||||
fmt1 = '{:>9}' * 4 + '\n'
|
||||
fmt2 = '{:12.4f}{:10.4f}'
|
||||
for _ in lmaxs:
|
||||
fd.write(fmt1.format(*_))
|
||||
|
||||
for ia in range(self.nat):
|
||||
# write the atom header line
|
||||
fd.write('{}ATOM{:>4}{}\n'.format('-' * 26 + ' ', ia + 1,
|
||||
' ' + '-' * 23))
|
||||
for _a, _b, _c, _d in self.tl[ia][ie]:
|
||||
fd.write(fmt2.format(_a, _b))
|
||||
fd.write(custom_strfloat(_c))
|
||||
fd.write(custom_strfloat(_d))
|
||||
fd.write('\n')
|
||||
self.nlmax = nlmax
|
||||
self.energies = get_energies()
|
||||
|
||||
def load_cluster_file(self, filename='cluster.clu'):
|
||||
data = np.loadtxt(filename, skiprows=1, usecols=(0, 2, 3, 4, 5, 6))
|
||||
atoms = self.parameters.atoms
|
||||
|
||||
absorber_position = atoms[atoms.absorber].position
|
||||
positions = data[:, 2:5] + absorber_position
|
||||
|
||||
proto_indices = []
|
||||
for atom in atoms:
|
||||
i = np.argmin(np.linalg.norm(positions - atom.position, axis=1))
|
||||
proto_index = int(data[i, -1])
|
||||
proto_indices.append(proto_index)
|
||||
atoms.set_array('proto_indices', np.array(proto_indices))
|
||||
self.nateqm = int(np.max([len(np.where(data[:,-1]==i)[0]) for i in range(
|
||||
1, self.nat + 1)]))
|
||||
|
||||
def load_potential_file(self, filename='plot_vc.dat'):
|
||||
a_index = 0
|
||||
pot_data = []
|
||||
with open(filename, 'r') as fd:
|
||||
data = fd.readlines()
|
||||
|
||||
for d in data:
|
||||
if d[1] == 'a':
|
||||
a_index += 1
|
||||
d = d.split()
|
||||
a = {'Symbol': d[1], 'distance': float(d[4]),
|
||||
'coord': np.array([float(d[7]), float(d[8]), float(d[9])]),
|
||||
'index': int(a_index), 'values': []}
|
||||
pot_data.append(a)
|
||||
else:
|
||||
pot_data[a_index - 1]['values'].append(tuple(float(_) for _ in d.split()))
|
||||
|
||||
# convert the values list to a numpy array
|
||||
for _pot_data in pot_data:
|
||||
v = _pot_data['values']
|
||||
_pot_data['values'] = np.asarray(v)
|
||||
|
||||
return pot_data
|
||||
|
||||
class SpecIO(object):
|
||||
def __init__(self, parameters, malloc_parameters, phagenio):
|
||||
self.parameters = parameters
|
||||
self.malloc_parameters = malloc_parameters
|
||||
self.phagenio = phagenio
|
||||
|
||||
def write_input_file(self, filename='spec.dat'):
|
||||
def title(t, shift=4, width=79, center=True):
|
||||
if center:
|
||||
s = ('{}*{:^%ds}*\n' % (width - shift - 2)).format(' ' * shift, t)
|
||||
else:
|
||||
s = ('{}*{:%ds}*\n' % (width - shift - 2)).format(' ' * shift, t)
|
||||
return s
|
||||
|
||||
def rule(tabs=(5, 10, 10, 10, 10), symbol='=', shift=4, width=79):
|
||||
s = ' ' * shift + '*' + symbol * (width - shift - 2) + '*\n'
|
||||
t = np.cumsum(tabs) + shift
|
||||
l = list(s)
|
||||
for i in t:
|
||||
l[i] = '+'
|
||||
return ''.join(l)
|
||||
|
||||
def fillstr(a, b, index, justify='left'):
|
||||
alist = list(a)
|
||||
|
||||
if justify == 'left':
|
||||
offset = -len(b) + 1
|
||||
elif justify == 'center':
|
||||
offset = (-len(b) + 1) / 2
|
||||
elif justify == 'decimal':
|
||||
try:
|
||||
offset = -(b.index('.') - 1)
|
||||
except ValueError:
|
||||
offset = 0
|
||||
else:
|
||||
offset = 0
|
||||
|
||||
for i, _ in enumerate(b):
|
||||
alist[int(index + offset + i)] = _
|
||||
return ''.join(alist)
|
||||
|
||||
def create_line(legend='', index=49, dots=False):
|
||||
s = ' ' * 79 + '\n'
|
||||
if dots:
|
||||
s = fillstr(s, "..", 6, justify='right')
|
||||
s = fillstr(s, "*", 4)
|
||||
s = fillstr(s, "*", 78)
|
||||
s = fillstr(s, legend, index, justify='right')
|
||||
return s
|
||||
|
||||
p = self.parameters
|
||||
|
||||
content = rule(tabs=(), symbol='*')
|
||||
content += title('spec input file')
|
||||
content += rule(tabs=(), symbol='*')
|
||||
content += rule(tabs=(), symbol='=')
|
||||
|
||||
content += title('CRYSTAL STRUCTURE :')
|
||||
content += rule()
|
||||
line = create_line("CRIST,CENTR,IBAS,NAT")
|
||||
line = fillstr(line, "CUB", 9, 'left')
|
||||
line = fillstr(line, "P", 19, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('extra_nat')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("A,BSURA,CSURA,UNIT")
|
||||
line = fillstr(line, format(1., '.4f'), 9, 'decimal')
|
||||
line = fillstr(line, format(1., '.3f'), 19, 'decimal')
|
||||
line = fillstr(line, format(1., '.3f'), 29, 'decimal')
|
||||
line = fillstr(line, "ATU", 39, 'left')
|
||||
content += line
|
||||
line = create_line("ALPHAD,BETAD,GAMMAD")
|
||||
line = fillstr(line, format(90., '.2f'), 9, 'decimal')
|
||||
line = fillstr(line, format(90., '.2f'), 19, 'decimal')
|
||||
line = fillstr(line, format(90., '.2f'), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("H,K,I,L")
|
||||
line = fillstr(line, format(0, 'd'), 9, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 19, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 29, 'left')
|
||||
line = fillstr(line, format(1, 'd'), 39, 'left')
|
||||
content += line
|
||||
line = create_line("NIV,COUPUR,ITEST,IESURF")
|
||||
line = fillstr(line, format(8, 'd'), 9, 'left')
|
||||
line = fillstr(line, format(1.4, '.2f'), 19, 'decimal')
|
||||
line = fillstr(line, format(0, 'd'), 29, 'left')
|
||||
line = fillstr(line, format(1, 'd'), 39, 'left')
|
||||
content += line
|
||||
line = create_line("ATBAS,CHEM(NAT),NZAT(NAT)")
|
||||
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
|
||||
line = fillstr(line, format("Mg", '>2s'), 39, 'right')
|
||||
line = fillstr(line, format(12, '>2d'), 43, 'right')
|
||||
content += line
|
||||
line = create_line("ATBAS,CHEM(NAT),NZAT(NAT)")
|
||||
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
|
||||
line = fillstr(line, format(0.5, '.6f'), 19, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
|
||||
line = fillstr(line, format("O", '>2s'), 39, 'right')
|
||||
line = fillstr(line, format(8, '>2d'), 43, 'right')
|
||||
content += line
|
||||
line = create_line("VECBAS")
|
||||
line = fillstr(line, format(1., '.6f'), 9, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("VECBAS")
|
||||
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
|
||||
line = fillstr(line, format(1., '.6f'), 19, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("VECBAS")
|
||||
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
|
||||
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
|
||||
line = fillstr(line, format(1., '.6f'), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("IREL,NREL,PCREL(NREL)")
|
||||
line = fillstr(line, format(0, 'd'), 9, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 19, 'left')
|
||||
line = fillstr(line, format(0., '.1f'), 29, 'decimal')
|
||||
line = fillstr(line, format(0., '.1f'), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("OMEGA1,OMEGA2,IADS")
|
||||
line = fillstr(line, format(28., '.2f'), 9, 'decimal')
|
||||
line = fillstr(line, format(0., '.2f'), 19, 'decimal')
|
||||
line = fillstr(line, format(1, 'd'), 29, 'left')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title('TYPE OF CALCULATION :')
|
||||
content += rule()
|
||||
line = create_line("SPECTRO,ISPIN,IDICHR,IPOL")
|
||||
line = fillstr(line, str(p.calctype_spectro), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calctype_ispin')), 19, 'left')
|
||||
line = fillstr(line, str(p.calctype_idichr), 29, 'left')
|
||||
line = fillstr(line, str(p.calctype_ipol), 39, 'left')
|
||||
content += line
|
||||
line = create_line("I_AMP")
|
||||
line = fillstr(line, str(p.calctype_iamp), 9, 'left')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title('PhD EXPERIMENTAL PARAMETERS :')
|
||||
content += rule()
|
||||
line = create_line("LI,S-O,INITL,I_SO")
|
||||
line = fillstr(line, str(p.ped_li), 9, 'left')
|
||||
line = fillstr(line, str(p.ped_so), 19, 'center')
|
||||
line = fillstr(line, str(p.ped_initl), 29, 'left')
|
||||
line = fillstr(line, str(p.ped_iso), 39, 'left')
|
||||
content += line
|
||||
line = create_line("IPHI,ITHETA,IE,IFTHET")
|
||||
line = fillstr(line, str(p.ped_iphi), 9, 'left')
|
||||
line = fillstr(line, str(p.ped_itheta), 19, 'left')
|
||||
line = fillstr(line, str(p.ped_ie), 29, 'left')
|
||||
line = fillstr(line, str(p.ped_ifthet), 39, 'left')
|
||||
content += line
|
||||
line = create_line("NPHI,NTHETA,NE,NFTHET")
|
||||
line = fillstr(line, str(p.ped_nphi), 9, 'left')
|
||||
line = fillstr(line, str(p.ped_ntheta), 19, 'left')
|
||||
line = fillstr(line, str(p.ped_ne), 29, 'left')
|
||||
line = fillstr(line, str(p.ped_nfthet), 39, 'left')
|
||||
content += line
|
||||
line = create_line("PHI0,THETA0,E0,R0")
|
||||
line = fillstr(line, str(p.get_parameter('ped_phi0')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_theta0')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_e0')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_r0')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("PHI1,THETA1,E1,R1")
|
||||
line = fillstr(line, str(p.get_parameter('ped_phi1')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_theta1')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_e1')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_r1')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("THLUM,PHILUM,ELUM")
|
||||
line = fillstr(line, str(p.get_parameter('ped_thlum')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_philum')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_elum')), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
|
||||
line = fillstr(line, str(p.get_parameter('ped_imod')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_imoy')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_accept')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('ped_ichkdir')), 39, 'decimal')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 22 + 'LEED EXPERIMENTAL PARAMETERS :', center=False)
|
||||
content += rule()
|
||||
line = create_line("IPHI,ITHETA,IE,IFTHET")
|
||||
line = fillstr(line, str(p.get_parameter('leed_iphi')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_itheta')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_ie')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_ifthet')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("NPHI,NTHETA,NE,NFTHET")
|
||||
line = fillstr(line, str(p.get_parameter('leed_nphi')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_ntheta')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_ne')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('leed_nfthet')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("PHI0,THETA0,E0,R0")
|
||||
line = fillstr(line, str(p.get_parameter('leed_phi0')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_theta0')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_e0')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_r0')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("PHI1,THETA1,E1,R1")
|
||||
line = fillstr(line, str(p.get_parameter('leed_phi1')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_theta1')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_e1')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_r1')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("TH_INI,PHI_INI")
|
||||
line = fillstr(line, str(p.get_parameter('leed_thini')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_phiini')), 19, 'decimal')
|
||||
content += line
|
||||
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
|
||||
line = fillstr(line, str(p.get_parameter('leed_imod')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_imoy')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_accept')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('leed_ichkdir')), 39,
|
||||
'decimal')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 21 + 'EXAFS EXPERIMENTAL PARAMETERS :', center=False)
|
||||
content += rule()
|
||||
line = create_line("EDGE,INITL,THLUM,PHILUM")
|
||||
line = fillstr(line, str(p.get_parameter('exafs_edge')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_initl')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_thlum')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_philum')), 39,
|
||||
'decimal')
|
||||
content += line
|
||||
line = create_line("NE,EK_INI,EK_FIN,EPH_INI")
|
||||
line = fillstr(line, str(p.get_parameter('exafs_ne')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_ekini')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_ekfin')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('exafs_ephini')), 39,
|
||||
'decimal')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 22 + 'AED EXPERIMENTAL PARAMETERS :', center=False)
|
||||
content += rule()
|
||||
line = create_line("EDGE_C,EDGE_I,EDGE_A")
|
||||
line = fillstr(line, str(p.get_parameter('aed_edgec')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_edgei')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_edgea')), 29, 'left')
|
||||
content += line
|
||||
line = create_line("I_MULT,MULT")
|
||||
line = fillstr(line, str(p.get_parameter('aed_imult')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_mult')), 19, 'center')
|
||||
content += line
|
||||
line = create_line("IPHI,ITHETA,IFTHET,I_INT")
|
||||
line = fillstr(line, str(p.get_parameter('aed_iphi')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_itheta')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_ifthet')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_iint')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("NPHI,NTHETA,NFTHET")
|
||||
line = fillstr(line, str(p.get_parameter('aed_nphi')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_ntheta')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_nfthet')), 29, 'left')
|
||||
content += line
|
||||
line = create_line("PHI0,THETA0,R0")
|
||||
line = fillstr(line, str(p.get_parameter('aed_phi0')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('aed_theta0')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('aed_r0')), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("PHI1,THETA1,R1")
|
||||
line = fillstr(line, str(p.get_parameter('aed_phi1')), 9, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('aed_theta1')), 19, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('aed_r1')), 29, 'decimal')
|
||||
content += line
|
||||
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
|
||||
line = fillstr(line, str(p.get_parameter('aed_imod')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_imoy')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('aed_accept')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('aed_ichkdir')), 39, 'left')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 19 + 'EIGENVALUE CALCULATION PARAMETERS :',
|
||||
center=False)
|
||||
content += rule()
|
||||
line = create_line("NE,EK_INI,EK_FIN,I_DAMP")
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ne')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ekini')), 19,
|
||||
'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ekfin')), 29,
|
||||
'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_idamp')), 39, 'left')
|
||||
content += line
|
||||
|
||||
if p.get_parameter('calctype_spectro') == "EIG":
|
||||
nlines = int(np.ceil(p.eigval_ne / 4.))
|
||||
else:
|
||||
nlines = 1
|
||||
table = np.chararray((nlines, 4), unicode=True)
|
||||
table[:] = str(p.get_parameter('eigval_ispectrum_ne').default)
|
||||
for i in range(nlines):
|
||||
line = create_line("I_SPECTRUM(NE)")
|
||||
for j, o in enumerate((9, 19, 29, 39)):
|
||||
line = fillstr(line, table[i, j], o, 'left')
|
||||
content += line
|
||||
|
||||
line = create_line("I_PWM,METHOD,ACC,EXPO")
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ipwm')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_method')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_acc')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_expo')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("N_MAX,N_ITER,N_TABLE,SHIFT")
|
||||
line = fillstr(line, str(p.get_parameter('eigval_nmax')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_niter')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ntable')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_shift')), 39,
|
||||
'decimal')
|
||||
content += line
|
||||
line = create_line("I_XN,I_VA,I_GN,I_WN")
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ixn')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_iva')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_ign')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_iwn')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("L,ALPHA,BETA")
|
||||
line = fillstr(line, str(p.get_parameter('eigval_l')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_alpha')), 19,
|
||||
'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('eigval_beta')), 29,
|
||||
'decimal')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 24 + 'CALCULATION PARAMETERS :', center=False)
|
||||
content += rule()
|
||||
line = create_line("NO,NDIF,ISPHER,I_GR")
|
||||
line = fillstr(line, str(p.get_parameter('calc_no')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ndif')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ispher')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_igr')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("ISFLIP,IR_DIA,ITRTL,I_TEST")
|
||||
line = fillstr(line, str(p.get_parameter('calc_isflip')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_irdia')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_itrtl')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_itest')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("NEMET,IEMET(NEMET)")
|
||||
line = fillstr(line, format(1, 'd'), 9, 'left')
|
||||
line = fillstr(line, format(1, 'd'), 19, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 29, 'left')
|
||||
line = fillstr(line, format(0, 'd'), 39, 'left')
|
||||
content += line
|
||||
line = create_line("ISOM,NONVOL,NPATH,VINT")
|
||||
line = fillstr(line, str(p.get_parameter('calc_isom')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_nonvol')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_npath')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_vint')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("IFWD,NTHOUT,I_NO,I_RA")
|
||||
line = fillstr(line, str(p.get_parameter('calc_ifwd')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_nthout')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ino')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ira')), 39, 'left')
|
||||
content += line
|
||||
|
||||
nat = p.extra_nat
|
||||
nra_arr = np.ones((nat), dtype=np.int)
|
||||
thfwd_arr = np.ones((nat))
|
||||
path_filtering = p.extra_parameters['calculation'].get_parameter(
|
||||
'path_filtering').value
|
||||
if path_filtering != None and 'backward_scattering' in path_filtering:
|
||||
ibwd_arr = np.ones((nat), dtype=np.int)
|
||||
else:
|
||||
ibwd_arr = np.zeros((nat), dtype=np.int)
|
||||
thbwd_arr = np.ones((nat))
|
||||
for at in p.extra_atoms:
|
||||
i = at.get('proto_index') - 1
|
||||
thfwd_arr[i] = at.get('forward_angle')
|
||||
thbwd_arr[i] = at.get('backward_angle')
|
||||
nra_arr[i] = at.get('RA_cut_off')
|
||||
for i in range(p.extra_nat):
|
||||
line = create_line("N_RA,THFWD,IBWD,THBWD(NAT)", dots=True)
|
||||
line = fillstr(line, format(nra_arr[i], 'd'), 9, 'left')
|
||||
line = fillstr(line, format(thfwd_arr[i], '.2f'), 19, 'decimal')
|
||||
line = fillstr(line, format(ibwd_arr[i], 'd'), 29, 'left')
|
||||
line = fillstr(line, format(thbwd_arr[i], '.2f'), 39, 'decimal')
|
||||
content += line
|
||||
|
||||
line = create_line("IPW,NCUT,PCTINT,IPP")
|
||||
line = fillstr(line, str(p.get_parameter('calc_ipw')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ncut')), 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_pctint')), 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('calc_ipp')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("ILENGTH,RLENGTH,UNLENGTH")
|
||||
line = fillstr(line, str(p.get_parameter('calc_ilength')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_rlength')), 19,
|
||||
'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('calc_unlength')), 29, 'left')
|
||||
content += line
|
||||
line = create_line("IDWSPH,ISPEED,IATT,IPRINT")
|
||||
# Here, if 'vibrational_damping' is None, use the 'debye_waller'
|
||||
# approach and the debye model for the mean square displacements
|
||||
# and set the temeprature to 0K and the Debye temeparture to 500K.
|
||||
calc_param = p.extra_parameters['calculation']
|
||||
if calc_param.vibrational_damping is None:
|
||||
idwsph = format(0, 'd')
|
||||
idcm = format(2, 'd')
|
||||
temp = format(0., '.2f')
|
||||
td = format(500., '.2f')
|
||||
LOGGER.warning('Vibrational damping is disabled for this calculation.')
|
||||
else:
|
||||
idwsph = str(p.get_parameter('calc_idwsph'))
|
||||
idcm = str(p.get_parameter('calc_idcm'))
|
||||
temp = str(p.get_parameter('calc_t'))
|
||||
td = str(p.get_parameter('calc_td'))
|
||||
ispeed = str(p.get_parameter('calc_ispeed'))
|
||||
line = fillstr(line, idwsph, 9, 'left')
|
||||
line = fillstr(line, ispeed, 19, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_iatt')), 29, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_iprint')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("IDCM,TD,T,RSJ")
|
||||
line = fillstr(line, idcm, 9, 'left')
|
||||
line = fillstr(line, td, 19, 'decimal')
|
||||
line = fillstr(line, temp, 29, 'decimal')
|
||||
line = fillstr(line, str(p.get_parameter('calc_rsj')), 39, 'decimal')
|
||||
content += line
|
||||
line = create_line("ILPM,XLPM0")
|
||||
line = fillstr(line, str(p.get_parameter('calc_ilpm')), 9, 'left')
|
||||
line = fillstr(line, str(p.get_parameter('calc_xlpm0')), 19, 'decimal')
|
||||
content += line
|
||||
|
||||
nat = p.extra_nat
|
||||
nlines = int(np.ceil(nat / 4.))
|
||||
uj2_array = np.zeros((4 * nlines))
|
||||
# Now, for each atom in the cluster, get the mean_square_vibration and
|
||||
# store it in the index corresponding to the prototypical index
|
||||
for at in p.extra_atoms:
|
||||
i = at.get('proto_index') - 1
|
||||
msq_vib = at.get('mean_square_vibration')
|
||||
uj2_array[i] = msq_vib
|
||||
uj2_array = uj2_array.reshape((nlines, 4))
|
||||
for i in range(nlines):
|
||||
line = create_line("UJ2(NAT)", dots=True)
|
||||
for j, o in enumerate((9, 19, 29, 39)):
|
||||
line = fillstr(line, format(uj2_array[i, j], '.5f'), o,
|
||||
'decimal')
|
||||
content += line
|
||||
content += rule()
|
||||
|
||||
content += title(' ' * 17 + 'INPUT FILES (PHD, EXAFS, LEED, AED, '
|
||||
'APECS) :', center=False)
|
||||
content += rule(tabs=(), symbol='-')
|
||||
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
|
||||
center=False)
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
line = create_line("DATA FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_data')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit00')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("PHASE SHIFTS/TL FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_tl')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit01')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("RADIAL MATRIX ELTS FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_rad')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit02')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("CLUSTER FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_cluster')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit03')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("ADSORBATE FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_adsorbate')), 9,
|
||||
'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit04')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("K DIRECTIONS FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input_kdirs')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input_unit05')), 39, 'left')
|
||||
content += line
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
|
||||
content += title(' ' * 21 + 'ADDITIONAL INPUT FILES (APECS) :',
|
||||
center=False)
|
||||
content += title(' ' * 28 + '(AUGER ELECTRON)', center=False)
|
||||
content += rule(tabs=(), symbol='-')
|
||||
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
|
||||
center=False)
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
line = create_line("PHASE SHIFTS/TL FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input2_tl')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input2_unit06')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("RADIAL MATRIX ELTS FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input2_rad')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input2_unit07')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("K DIRECTIONS FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('input2_kdirs')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('input2_unit08')), 39, 'left')
|
||||
content += line
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
|
||||
content += title(' ' * 29 + 'OUTPUT FILES :', center=False)
|
||||
content += rule(tabs=(), symbol='-')
|
||||
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
|
||||
center=False)
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
line = create_line("CONTROL FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('output_log')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('output_unit09')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("RESULT FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('output_res')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('output_unit10')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("SCATTERING FACTOR FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('output_sf')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('output_unit11')), 39, 'left')
|
||||
content += line
|
||||
line = create_line("AUGMENTED CLUSTER FILE,UNIT")
|
||||
line = fillstr(line, str(p.get_parameter('output_augclus')), 9, 'right')
|
||||
line = fillstr(line, str(p.get_parameter('output_unit12')), 39, 'left')
|
||||
content += line
|
||||
content += rule(tabs=(5, 23, 7, 10))
|
||||
|
||||
content += title(' ' * 26 + 'END OF THE DATA FILE', center=False)
|
||||
content += rule(tabs=())
|
||||
content += rule(tabs=(), symbol='*')
|
||||
|
||||
try:
|
||||
with open(filename, 'r') as fd:
|
||||
old_content = fd.read()
|
||||
except IOError:
|
||||
old_content = ''
|
||||
|
||||
modified = False
|
||||
if content != old_content:
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write(content)
|
||||
modified = True
|
||||
|
||||
return modified
|
||||
|
||||
def write_include_file(self, filename='spec.inc'):
|
||||
def get_li(level):
|
||||
orbitals = 'spdfghi'
|
||||
m = re.match(r'\d(?P<l>[%s])(\d/2)?' % orbitals, level)
|
||||
return orbitals.index(m.group('l'))
|
||||
|
||||
requirements = {
|
||||
'NATP_M': self.phagenio.nat,
|
||||
'NATCLU_M': len(self.parameters.extra_atoms),
|
||||
'NAT_EQ_M': self.phagenio.nateqm,
|
||||
'N_CL_L_M': 0,
|
||||
'NE_M': self.phagenio.ne,
|
||||
'NL_M': self.phagenio.nlmax + 1,
|
||||
'LI_M': get_li(self.parameters.extra_level),
|
||||
'NEMET_M': 1,
|
||||
'NO_ST_M': self.parameters.calc_no,
|
||||
}
|
||||
|
||||
# read the include file
|
||||
with open(filename, 'r') as fd:
|
||||
content = fd.read()
|
||||
|
||||
# backup the content in memory
|
||||
old_content = content
|
||||
|
||||
"""
|
||||
for key in ('NATP_M', 'NATCLU_M', 'NE_M', 'NEMET_M', 'LI_M', 'NL_M',
|
||||
'NO_ST_M'):
|
||||
required = requirements[key]
|
||||
limit = self.malloc_parameters.get_parameter(key).value
|
||||
value = required if required > limit else limit
|
||||
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
|
||||
r'\g<1>{:d}'.format(value), content)
|
||||
"""
|
||||
|
||||
for key in ('NAT_EQ_M', 'N_CL_N_M', 'NDIF_M', 'NSO_M', 'NTEMP_M',
|
||||
'NODES_EX_M', 'NSPIN_M', 'NTH_M', 'NPH_M', 'NDIM_M',
|
||||
'N_TILT_M', 'N_ORD_M', 'NPATH_M', 'NGR_M'):
|
||||
value = self.malloc_parameters.get_parameter(key).value
|
||||
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
|
||||
r'\g<1>{:d}'.format(value), content)
|
||||
|
||||
for key, value in list(requirements.items()):
|
||||
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
|
||||
r'\g<1>{:d}'.format(value), content)
|
||||
|
||||
|
||||
modified = False
|
||||
if content != old_content:
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write(content)
|
||||
modified = True
|
||||
return modified
|
||||
|
||||
def write_kdirs_file(self, filename='kdirs.dat'):
|
||||
fwhm = 1.
|
||||
all_theta = self.parameters.extra_parameters['scan'].theta
|
||||
all_phi = self.parameters.extra_parameters['scan'].phi
|
||||
f = '{:7}{:4}{:6}\n'
|
||||
|
||||
old_content = None
|
||||
try:
|
||||
with open(filename, 'r') as fd:
|
||||
old_content = fd.read()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
content = ''
|
||||
content += f.format(2, 1, len(all_theta) * len(all_phi))
|
||||
content += f.format(1, len(all_phi), len(all_theta))
|
||||
for iphi, phi in enumerate(all_phi):
|
||||
for itheta, theta in enumerate(all_theta):
|
||||
s = '{:5}{:5}{:13.3f}{:11.3f}{:15e}\n'
|
||||
s = s.format(iphi + 1, itheta + 1, theta, phi, fwhm)
|
||||
content += s
|
||||
|
||||
modified = False
|
||||
if content != old_content:
|
||||
with open(filename, 'w') as fd:
|
||||
fd.write(content)
|
||||
modified = True
|
||||
return modified
|
||||
|
||||
def load_results(self, filename='results.dat'):
|
||||
rows2skip = {
|
||||
'PED': 27,
|
||||
'AED': 27,
|
||||
'EXAFS': 27,
|
||||
'LEED': 26,
|
||||
'EIG': 0
|
||||
}
|
||||
spectro = self.parameters.extra_parameters['global'].spectroscopy
|
||||
skip = rows2skip[spectro]
|
||||
|
||||
data = np.loadtxt(filename, skiprows=skip, unpack=True)
|
||||
if len(data.shape) <= 1:
|
||||
data = data.reshape((1, data.shape[0]))
|
||||
return data
|
||||
|
||||
def load_facdif(self, filename='facdif1.dat'):
|
||||
data = np.loadtxt(filename, skiprows=1)
|
||||
return data
|
||||
|
||||
def load_log(self, filename='spec.log'):
|
||||
pat = re.compile(r'ORDER\s+(\d+)\s+TOTAL NUMBER OF PATHS\s+:\s+(\d+)')
|
||||
with open(filename, 'r') as fd:
|
||||
content = fd.read()
|
||||
#return pat.findall(content.replace('\n', '__cr__'))
|
||||
return pat.findall(content)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,905 @@
|
|||
# coding: utf-8
|
||||
# vim: set et sw=4 ts=4 sts=4 nu ai cc=+0 fdm=indent mouse=a:
|
||||
"""
|
||||
Module calculator
|
||||
=================
|
||||
|
||||
This module contains different classes used to define a new calculator for
|
||||
specific spectroscopies understood by MsSpec.
|
||||
|
||||
These spectroscopies are listed :ref:`here <globalparameters-spectroscopy>`.
|
||||
|
||||
There is one *calculator* class for each spectroscopy. The class name is based
|
||||
on the spectroscopy name. For instance, the class for PhotoElectron Diffraction
|
||||
is called :py:class:`_PED`.
|
||||
|
||||
The helper function :py:func:`calculator.MSSPEC` is used to create objects from
|
||||
these classes by passing the kind of spectroscopy as a keyword argument.
|
||||
|
||||
For more information on MsSpec, follow this
|
||||
`link <https://ipr.univ-rennes1.fr/msspec>`__
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import inspect
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from shutil import copyfile, rmtree
|
||||
from datetime import datetime
|
||||
import time
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
|
||||
from ase.calculators.calculator import Calculator
|
||||
import ase.data
|
||||
import ase.atom
|
||||
import ase.atoms
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
from msspec import iodata
|
||||
from msspec.data import electron_be
|
||||
from msspec.config import Config
|
||||
from msspec.misc import (UREG, LOGGER, get_call_info, get_level_from_electron_configuration,
|
||||
XRaySource, set_log_output, log_process_output)
|
||||
from msspec.utils import get_atom_index
|
||||
|
||||
from msspec.parameters import (PhagenParameters, PhagenMallocParameters,
|
||||
SpecParameters, SpecMallocParameters,
|
||||
GlobalParameters, MuffintinParameters,
|
||||
TMatrixParameters, SourceParameters,
|
||||
DetectorParameters, ScanParameters,
|
||||
CalculationParameters,
|
||||
PEDParameters, EIGParameters)
|
||||
from msspec.calcio import PhagenIO, SpecIO
|
||||
|
||||
from msspec.phagen.libphagen import main as do_phagen
|
||||
from msspec.spec.libspec import run as do_spec
|
||||
|
||||
|
||||
from terminaltables.ascii_table import AsciiTable
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
MSSPEC_ROOT = os.environ['MSSPEC_ROOT']
|
||||
except KeyError:
|
||||
cfg = Config()
|
||||
MSSPEC_ROOT = cfg.get('path')
|
||||
|
||||
if MSSPEC_ROOT == str(None):
|
||||
raise NameError('No path to the MsSpec distribution found !!')
|
||||
|
||||
|
||||
|
||||
|
||||
def init_msspec():
|
||||
LOGGER.debug('Initialization of the msspec module')
|
||||
ase.atom.names['mt_radius'] = ('mt_radii', 0.)
|
||||
ase.atom.names['mt_radius_scale'] = ('mt_radii_scale', 1.)
|
||||
ase.atom.names['proto_index'] = ('proto_indices', 1)
|
||||
ase.atom.names['mean_square_vibration'] = ('mean_square_vibrations', 0.)
|
||||
ase.atom.names['forward_angle'] = ('forward_angles', 20.)
|
||||
ase.atom.names['backward_angle'] = ('backward_angles', 20.)
|
||||
ase.atom.names['RA_cut_off'] = ('RA_cuts_off', 1)
|
||||
ase.atoms.Atoms.absorber = None
|
||||
init_msspec()
|
||||
|
||||
|
||||
|
||||
|
||||
class _MSCALCULATOR(Calculator):
|
||||
"""
|
||||
This class defines an ASE calculator for doing Multiple scattering
|
||||
calculations.
|
||||
"""
|
||||
implemented_properties = ['', ]
|
||||
__data = {}
|
||||
|
||||
def __init__(self, spectroscopy='PED', algorithm='expansion',
|
||||
polarization=None, dichroism=None, spinpol=False,
|
||||
folder='./calc', txt='-', **kwargs):
|
||||
stdout = sys.stdout
|
||||
if isinstance(txt, str) and txt != '-':
|
||||
stdout = open(txt, 'w')
|
||||
#elif isinstance(txt, buffer):
|
||||
# stdout = txt
|
||||
elif txt == None:
|
||||
stdout = open('/dev/null', 'a')
|
||||
#set_log_output(stdout)
|
||||
########################################################################
|
||||
LOGGER.debug('Initialization of %s', self.__class__.__name__)
|
||||
LOGGER.debug(get_call_info(inspect.currentframe()))
|
||||
########################################################################
|
||||
# Init the upper class
|
||||
Calculator.__init__(self, **kwargs)
|
||||
|
||||
########################################################################
|
||||
LOGGER.debug(' create low level parameters')
|
||||
########################################################################
|
||||
self.phagen_parameters = PhagenParameters()
|
||||
self.phagen_malloc_parameters = PhagenMallocParameters()
|
||||
self.spec_parameters = SpecParameters()
|
||||
self.spec_malloc_parameters = SpecMallocParameters()
|
||||
|
||||
########################################################################
|
||||
LOGGER.debug(' create higher level parameters')
|
||||
########################################################################
|
||||
self.tmatrix_parameters = TMatrixParameters(self.phagen_parameters)
|
||||
self.muffintin_parameters = MuffintinParameters(self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
|
||||
|
||||
self.global_parameters = GlobalParameters(self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
|
||||
if spectroscopy == 'PED':
|
||||
self.spectroscopy_parameters = PEDParameters(self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
elif spectroscopy == 'EIG':
|
||||
self.spectroscopy_parameters = EIGParameters(self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
#pass
|
||||
else:
|
||||
raise NameError('No such spectrosopy')
|
||||
|
||||
self.source_parameters = SourceParameters(self.global_parameters,
|
||||
self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
|
||||
self.detector_parameters = DetectorParameters(self.global_parameters,
|
||||
self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
|
||||
self.scan_parameters = ScanParameters(self.global_parameters,
|
||||
self.phagen_parameters,
|
||||
self.spec_parameters)
|
||||
|
||||
self.calculation_parameters = CalculationParameters(
|
||||
self.global_parameters, self.phagen_parameters, self.spec_parameters)
|
||||
|
||||
# updated global parameters with provided keywords
|
||||
self.global_parameters.spectroscopy = spectroscopy
|
||||
self.global_parameters.algorithm = algorithm
|
||||
self.global_parameters.polarization = polarization
|
||||
self.global_parameters.dichroism = dichroism
|
||||
self.global_parameters.spinpol = spinpol
|
||||
self.global_parameters.folder = folder
|
||||
|
||||
|
||||
|
||||
self.phagenio = PhagenIO(self.phagen_parameters,
|
||||
self.phagen_malloc_parameters)
|
||||
self.specio = SpecIO(self.spec_parameters,
|
||||
self.spec_malloc_parameters,
|
||||
self.phagenio)
|
||||
|
||||
########################################################################
|
||||
LOGGER.debug(' create a space dedicated to the calculation')
|
||||
########################################################################
|
||||
self.init_folder = os.getcwd()
|
||||
self.msspec_folder = os.path.join(MSSPEC_ROOT)
|
||||
self.tmp_folder = os.path.abspath(folder)
|
||||
LOGGER.debug(' folder: \'%s\'', self.tmp_folder)
|
||||
if not os.path.exists(self.tmp_folder):
|
||||
os.makedirs(self.tmp_folder)
|
||||
os.makedirs(os.path.join(self.tmp_folder, 'input'))
|
||||
os.makedirs(os.path.join(self.tmp_folder, 'output'))
|
||||
#copyfile(os.path.join(self.msspec_folder, 'ase', 'Makefile'),
|
||||
# os.path.join(self.tmp_folder, 'Makefile'))
|
||||
|
||||
os.chdir(self.tmp_folder)
|
||||
|
||||
inv = cor = 'NO'
|
||||
if algorithm == 'expansion':
|
||||
pass
|
||||
elif algorithm == 'inversion':
|
||||
inv = 'YES'
|
||||
elif algorithm == 'correlation':
|
||||
cor = 'YES'
|
||||
|
||||
# spin orbit resolved (not yet)
|
||||
sorb = 'NO'
|
||||
|
||||
# spin resolved
|
||||
dichro_spinpol = False
|
||||
if dichroism in ('sum_over_spin', 'spin_resolved'):
|
||||
dichro_spinpol = True
|
||||
|
||||
spin = 'NO'
|
||||
if spinpol or dichro_spinpol:
|
||||
spin = 'YES'
|
||||
|
||||
if spin == 'YES':
|
||||
LOGGER.error('Option not implemented!')
|
||||
raise NotImplementedError(
|
||||
'Spin polarization is not implemeted yet!')
|
||||
|
||||
|
||||
calctype_spectro = self.spec_parameters.get_parameter('calctype_spectro')
|
||||
calctype_spectro = calctype_spectro.value
|
||||
self._make_opts = (MSSPEC_ROOT, calctype_spectro, inv, cor,
|
||||
spin, sorb, self.tmp_folder)
|
||||
|
||||
# Initialize the working environment
|
||||
#self._make('init')
|
||||
|
||||
self.modified = False
|
||||
|
||||
self.resources = {}
|
||||
########################################################################
|
||||
LOGGER.debug(' initialization done.\n')
|
||||
########################################################################
|
||||
|
||||
def _make(self, target):
|
||||
LOGGER.debug(get_call_info(inspect.currentframe()))
|
||||
os.chdir(self.tmp_folder)
|
||||
cmd = ("make__SPACE__ROOT_FOLDER=\"{}\"__SPACE__SPEC=\"{}\"__SPACE__INV=\"{}\"__SPACE__COR=\"{"
|
||||
"}\"__SPACE__"
|
||||
"SPIN=\"{}\"__SPACE__SO=\"{}\"__SPACE__CALC_FOLDER=\"{}\"__SPACE__{}").format(*(self._make_opts + (
|
||||
target,))).split('__SPACE__')
|
||||
#cmd = cmd.replace(' ', '\ ')
|
||||
#cmd = cmd.split('__SPACE__')
|
||||
|
||||
LOGGER.debug(' the full command is: %s', cmd)
|
||||
|
||||
child = Popen(cmd,stdout=PIPE, stderr=PIPE)
|
||||
logger_name = LOGGER.name
|
||||
if target == 'tmatrix':
|
||||
logger_name = 'Phagen'
|
||||
elif target == 'compute':
|
||||
logger_name = 'Spec'
|
||||
|
||||
log_process_output(child, logger=logger_name)
|
||||
os.chdir(self.init_folder)
|
||||
|
||||
if child.returncode != 0:
|
||||
LOGGER.error("Unable to successfully run the target: {}".format(target))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _guess_ke(self, level):
|
||||
""" Try to guess the kinetic energy based on the level and
|
||||
the source energy. If the kinetic energy cannot be infered
|
||||
because the level is not reported in the database, the returned
|
||||
value is None.
|
||||
"""
|
||||
try:
|
||||
state = get_level_from_electron_configuration(level)
|
||||
absorber_atomic_number = self.atoms[self.atoms.absorber].number
|
||||
lines = electron_be[absorber_atomic_number]
|
||||
binding_energy = lines[state]
|
||||
except KeyError:
|
||||
# unable to find a binding energy in the database
|
||||
return None
|
||||
|
||||
# let's assume work function energy (in eV)
|
||||
wf = 4.5
|
||||
source_energy = self.source_parameters.get_parameter('energy').value
|
||||
ke = source_energy - binding_energy - wf
|
||||
#return np.array(ke, dtype=np.float).flatten()
|
||||
return ke
|
||||
|
||||
|
||||
def run_phagen(self):
|
||||
#include_fname = os.path.join(self.tmp_folder, 'src/msxas3.inc')
|
||||
input_fname = os.path.join(self.tmp_folder, 'input/input.ms')
|
||||
|
||||
#mod0 = self.phagenio.write_include_file(filename=include_fname)
|
||||
mod1 = self.phagenio.write_input_file(filename=input_fname)
|
||||
|
||||
self.modified = self.modified or mod1 #or mod0 or mod1
|
||||
|
||||
if self.modified:
|
||||
# run phagen
|
||||
#self._make('tmatrix')
|
||||
os.chdir(os.path.join(self.tmp_folder, 'output'))
|
||||
do_phagen()
|
||||
# rename some output files to be more explicit
|
||||
os.rename('fort.10', 'cluster.clu')
|
||||
os.rename('fort.35', 'tmatrix.tl')
|
||||
os.rename('fort.55', 'tmatrix.rad')
|
||||
|
||||
|
||||
def run_spec(self):
|
||||
def get_li(level):
|
||||
orbitals = 'spdfghi'
|
||||
m = re.match(r'\d(?P<l>[%s])(\d/2)?' % orbitals, level)
|
||||
return orbitals.index(m.group('l'))
|
||||
|
||||
#include_fname = os.path.join(self.tmp_folder, 'src/spec.inc')
|
||||
input_fname = os.path.join(self.tmp_folder, 'input/spec.dat')
|
||||
kdirs_fname = os.path.join(self.tmp_folder, 'input/kdirs.dat')
|
||||
|
||||
mod0 = self.specio.write_input_file(filename=input_fname)
|
||||
#mod1 = self.specio.write_include_file(filename=include_fname)
|
||||
mod2 = self.specio.write_kdirs_file(filename=kdirs_fname)
|
||||
|
||||
#self.modified = self.modified or mod0 or mod1 or mod2
|
||||
self.modified = self.modified or mod0 or mod2
|
||||
|
||||
#self._make('tmatrix')
|
||||
#self._make('bin/spec')
|
||||
#t0 = time.time()
|
||||
#self._make('compute')
|
||||
#t1 = time.time()
|
||||
#self.resources['spec_time'] = t1 - t0
|
||||
if self.modified:
|
||||
#self.get_tmatrix()
|
||||
t0 = time.time()
|
||||
os.chdir(os.path.join(self.tmp_folder, 'output'))
|
||||
# set/get the dimension values
|
||||
requirements = OrderedDict({
|
||||
'NATP_M' : self.phagenio.nat,
|
||||
'NATCLU_M' : len(self.atoms),
|
||||
'NAT_EQ_M' : self.phagenio.nateqm,
|
||||
'N_CL_L_M' : 1,
|
||||
'NE_M' : self.phagenio.ne,
|
||||
'NL_M' : self.phagenio.nlmax + 1,
|
||||
'LI_M' : get_li(self.spec_parameters.extra_level),
|
||||
'NEMET_M' : 1,
|
||||
'NO_ST_M' : self.spec_parameters.calc_no,
|
||||
'NDIF_M' : 10,
|
||||
'NSO_M' : 2,
|
||||
'NTEMP_M' : 1,
|
||||
'NODES_EX_M' : 3,
|
||||
'NSPIN_M' : 1, # to change for spin dependent
|
||||
'NTH_M' : 2000,
|
||||
'NPH_M' : 2000,
|
||||
'NDIM_M' : 100000,
|
||||
'N_TILT_M' : 11, # to change see extdir.f
|
||||
'N_ORD_M' : 200,
|
||||
'NPATH_M' : 500,
|
||||
'NGR_M' : 10,})
|
||||
|
||||
for key, value in requirements.items():
|
||||
setattr(self.spec_malloc_parameters, key, value)
|
||||
|
||||
do_spec(*requirements.values())
|
||||
|
||||
t1 = time.time()
|
||||
self.resources['spec_time'] = t1 - t0
|
||||
|
||||
def get_tmatrix(self):
|
||||
LOGGER.info("Getting the TMatrix...")
|
||||
LOGGER.debug(get_call_info(inspect.currentframe()))
|
||||
|
||||
self.run_phagen()
|
||||
|
||||
filename = os.path.join(self.tmp_folder, 'output/tmatrix.tl')
|
||||
tl = self.phagenio.load_tl_file(filename)
|
||||
|
||||
filename = os.path.join(self.tmp_folder, 'output/cluster.clu')
|
||||
self.phagenio.load_cluster_file(filename)
|
||||
|
||||
|
||||
tl_threshold = self.tmatrix_parameters.get_parameter('tl_threshold')
|
||||
if tl_threshold.value != None:
|
||||
LOGGER.debug(" applying tl_threshold to %s...",
|
||||
tl_threshold.value)
|
||||
go_on = True
|
||||
while go_on:
|
||||
go_on = False
|
||||
for ia in range(self.phagenio.nat):
|
||||
for ie in range(self.phagenio.ne):
|
||||
last_tl = tl[ia][ie][-1, -2:]
|
||||
# convert to complex
|
||||
last_tl = last_tl[0] + last_tl[1] * 1j
|
||||
if np.abs(last_tl) < tl_threshold.value:
|
||||
# remove last line of tl
|
||||
tl[ia][ie] = tl[ia][ie][:-1, :]
|
||||
go_on = True
|
||||
|
||||
max_tl = self.tmatrix_parameters.get_parameter('max_tl').value
|
||||
cluster = self.phagen_parameters.get_parameter('atoms').value
|
||||
proto_indices = cluster.get_array('proto_indices')
|
||||
|
||||
if max_tl != None:
|
||||
LOGGER.debug(" applying max_tl: %s", max_tl)
|
||||
for ia in range(self.phagenio.nat):
|
||||
for ie in range(self.phagenio.ne):
|
||||
try:
|
||||
# for each set of tl:
|
||||
# 1. get the symbol of the prototipical atom
|
||||
j = np.where(proto_indices == ia+1)
|
||||
symbol = cluster[j][0].symbol
|
||||
# 2. get the number of max tl allowed
|
||||
ntl = max_tl[symbol]
|
||||
# 3. reshape the tl set accordingly
|
||||
tl[ia][ie] = tl[ia][ie][:ntl, :]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.phagenio.write_tl_file(
|
||||
os.path.join(self.tmp_folder, 'output/tmatrix.tl'))
|
||||
|
||||
# update spec extra parameters here
|
||||
self.spec_parameters.set_parameter('extra_nat', self.phagenio.nat)
|
||||
self.spec_parameters.set_parameter('extra_nlmax', self.phagenio.nlmax)
|
||||
|
||||
|
||||
|
||||
def set_atoms(self, atoms):
|
||||
"""Defines the cluster on which the calculator will work.
|
||||
|
||||
:param atoms: The cluster to attach the calculator to.
|
||||
:type atoms: :py:class:`ase.Atoms`
|
||||
"""
|
||||
if atoms.absorber == None:
|
||||
LOGGER.error("You must define the absorber before setting the atoms to the"
|
||||
"calculator.")
|
||||
self.atoms = atoms
|
||||
self.phagen_parameters.set_parameter('atoms', atoms)
|
||||
self.spec_parameters.set_parameter('extra_atoms', atoms)
|
||||
|
||||
|
||||
def get_parameters(self):
|
||||
"""Get all the defined parameters in the calculator.
|
||||
|
||||
:return: A list of all parameters objects.
|
||||
:rtype: List of :py:class:`parameters.Parameter`
|
||||
|
||||
"""
|
||||
_ = []
|
||||
for section in ('global', 'muffintin', 'tmatrix', 'spectroscopy',
|
||||
'source', 'detector', 'scan', 'calculation'):
|
||||
parameters = getattr(self, section + '_parameters')
|
||||
for p in parameters:
|
||||
_.append(p)
|
||||
return _
|
||||
|
||||
def shutdown(self):
|
||||
"""Removes the temporary folder and all its content.
|
||||
|
||||
The user may whish to keep the calculation folder (see :ref:`globalparameters-folder`) so it is not removed
|
||||
at the end of the calculation. The calculation folder contains raw results from *Phagen* and *Spec* programs as
|
||||
well as their input files and configuration. It allows the program to save some time by not repeating some
|
||||
tasks (such as the Fortran code generation, building the binaries, computing things that did not changed
|
||||
between runs...).
|
||||
Calling this function at the end of the calculation will erase this calculation folder.
|
||||
|
||||
.. warning::
|
||||
|
||||
Calling this function will erase the produced data without prompting you for confirmation,
|
||||
so take care of explicitly saving your results in your script, by using the
|
||||
:py:func:`iodata.Data.save` method for example.
|
||||
|
||||
"""
|
||||
LOGGER.info('Deleting temporary files...')
|
||||
rmtree(self.tmp_folder)
|
||||
|
||||
class _PED(_MSCALCULATOR):
|
||||
"""This class creates a calculator object for PhotoElectron DIffraction
|
||||
spectroscopy.
|
||||
|
||||
:param algorithm: The algorithm to use for the computation. See
|
||||
:ref:`globalparameters-algorithm` for more details about the allowed
|
||||
values and the type.
|
||||
|
||||
:param polarization: The incoming light polarization (see
|
||||
:ref:`globalparameters-polarization`)
|
||||
|
||||
:param dichroism: Wether to enable or not the dichroism (see
|
||||
:ref:`globalparameters-dichroism`)
|
||||
|
||||
:param spinpol: Enable or disable the spin polarization in the calculation
|
||||
(see :ref:`globalparameters-spinpol`)
|
||||
|
||||
:param folder: The path to the temporary folder for the calculations. See
|
||||
:ref:`globalparameters-folder`
|
||||
|
||||
:param txt: The name of a file where to redirect standard output. The string
|
||||
'-' will redirect the standard output to the screen (default).
|
||||
:type txt: str
|
||||
|
||||
.. note::
|
||||
|
||||
This class constructor is not meant to be called directly by the user.
|
||||
Use the :py:func:`MSSPEC` to instanciate any calculator.
|
||||
|
||||
|
||||
"""
|
||||
def __init__(self, algorithm='expansion', polarization=None, dichroism=None,
|
||||
spinpol=False, folder='./calc', txt='-'):
|
||||
_MSCALCULATOR.__init__(self, spectroscopy='PED', algorithm=algorithm,
|
||||
polarization=polarization, dichroism=dichroism,
|
||||
spinpol=spinpol, folder=folder, txt=txt)
|
||||
|
||||
self.iodata = iodata.Data('PED Simulation')
|
||||
|
||||
def _get_scan(self, scan_type='theta', phi=0,
|
||||
theta=np.linspace(-70, 70, 141), level=None,
|
||||
kinetic_energy=None, data=None):
|
||||
LOGGER.info("Computting the %s scan...", scan_type)
|
||||
if data:
|
||||
self.iodata = data
|
||||
|
||||
if kinetic_energy is None:
|
||||
# try to guess the kinetic energy
|
||||
kinetic_energy = self._guess_ke(level)
|
||||
|
||||
# if still None...
|
||||
if kinetic_energy is None:
|
||||
LOGGER.error('Unable to guess the kinetic energy!')
|
||||
raise ValueError('You must define a kinetic_energy value.')
|
||||
|
||||
# update the parameters
|
||||
self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy)
|
||||
all_ke = self.scan_parameters.get_parameter('ke_array')
|
||||
if np.any(all_ke.value < 0):
|
||||
LOGGER.error('Source energy is not high enough or level too deep!')
|
||||
raise ValueError('Kinetic energy is < 0! ({})'.format(
|
||||
kinetic_energy))
|
||||
self.scan_parameters.set_parameter('type', scan_type)
|
||||
|
||||
# make sure there is only one energy point in scatf scan
|
||||
if scan_type == 'scatf':
|
||||
assert len(all_ke) == 1, ('kinetic_energy should not be an array '
|
||||
'in scatf scan')
|
||||
|
||||
|
||||
if scan_type != 'scatf':
|
||||
self.scan_parameters.set_parameter('phi', phi)
|
||||
self.scan_parameters.set_parameter('theta', theta)
|
||||
|
||||
self.spectroscopy_parameters.set_parameter('level', level)
|
||||
|
||||
self.get_tmatrix()
|
||||
self.run_spec()
|
||||
|
||||
# Now load the data
|
||||
ndset = len(self.iodata)
|
||||
dset = self.iodata.add_dset('{} scan [{:d}]'.format(scan_type, ndset))
|
||||
for p in self.get_parameters():
|
||||
bundle = {'group': str(p.group),
|
||||
'name': str(p.name),
|
||||
'value': str(p.value),
|
||||
'unit': '' if p.unit is None else str(p.unit)}
|
||||
dset.add_parameter(**bundle)
|
||||
if scan_type in ('theta', 'phi', 'energy'):
|
||||
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
|
||||
data = self.specio.load_results(results_fname)
|
||||
for _plane, _theta, _phi, _energy, _dirsig, _cs in data.T:
|
||||
if _plane == -1:
|
||||
dset.add_row(theta=_theta, phi=_phi, energy=_energy, cross_section=_cs, direct_signal=_dirsig)
|
||||
elif scan_type in ('scatf',):
|
||||
results_fname = os.path.join(self.tmp_folder, 'output/facdif1.dat')
|
||||
data = self.specio.load_facdif(results_fname)
|
||||
data = data[:, [1, 4, 5, 6, 8]].T
|
||||
_proto, _sf_real, _sf_imag, _theta, _energy = data
|
||||
_sf = _sf_real + _sf_imag * 1j
|
||||
dset.add_columns(proto_index=_proto, sf_real=np.real(_sf),
|
||||
sf_imag=np.imag(_sf), sf_module=np.abs(_sf),
|
||||
theta=_theta, energy=_energy)
|
||||
elif scan_type in ('theta_phi',):
|
||||
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
|
||||
data = self.specio.load_results(results_fname)
|
||||
#theta_c, phi_c = data[[2, 3], :]
|
||||
#xsec_c = data[-1, :]
|
||||
#dirsig_c = data[-2, :]
|
||||
|
||||
#dset.add_columns(theta=theta_c)
|
||||
#dset.add_columns(phi=phi_c)
|
||||
#dset.add_columns(cross_section=xsec_c)
|
||||
#dset.add_columns(direct_signal=dirsig_c)
|
||||
for _plane, _theta, _phi, _energy, _dirsig, _cs in data.T:
|
||||
if _plane == -1:
|
||||
dset.add_row(theta=_theta, phi=_phi, energy=_energy, cross_section=_cs,
|
||||
direct_signal=_dirsig)
|
||||
|
||||
# create a view
|
||||
title = ''
|
||||
for ke in all_ke.value:
|
||||
if scan_type == 'theta':
|
||||
absorber_symbol = self.atoms[self.atoms.absorber].symbol
|
||||
title = 'Polar scan of {}({}) at {:.2f} eV'.format(
|
||||
absorber_symbol, level, ke)
|
||||
xlabel = r'Angle $\theta$($\degree$)'
|
||||
ylabel = r'Signal (a. u.)'
|
||||
|
||||
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
|
||||
xlabel=xlabel, ylabel=ylabel)
|
||||
for angle_phi in self.scan_parameters.get_parameter(
|
||||
'phi').value:
|
||||
where = ("energy=={:.2f} and phi=={:.2f}"
|
||||
"").format(ke, angle_phi)
|
||||
legend = r'$\phi$ = {:.1f} $\degree$'.format(angle_phi)
|
||||
view.select('theta', 'cross_section', where=where,
|
||||
legend=legend)
|
||||
if scan_type == 'phi':
|
||||
absorber_symbol = self.atoms[self.atoms.absorber].symbol
|
||||
title = 'Azimuthal scan of {}({}) at {:.2f} eV'.format(
|
||||
absorber_symbol, level, ke)
|
||||
xlabel = r'Angle $\phi$($\degree$)'
|
||||
ylabel = r'Signal (a. u.)'
|
||||
|
||||
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
|
||||
xlabel=xlabel, ylabel=ylabel)
|
||||
for angle_theta in self.scan_parameters.get_parameter(
|
||||
'theta').value:
|
||||
where = ("energy=={:.2f} and theta=={:.2f}"
|
||||
"").format(ke, angle_theta)
|
||||
legend = r'$\theta$ = {:.1f} $\degree$'.format(angle_theta)
|
||||
view.select('phi', 'cross_section', where=where,
|
||||
legend=legend)
|
||||
|
||||
if scan_type == 'theta_phi':
|
||||
absorber_symbol = self.atoms[self.atoms.absorber].symbol
|
||||
title = ('Stereographic projection of {}({}) at {:.2f} eV'
|
||||
'').format(absorber_symbol, level, ke)
|
||||
xlabel = r'Angle $\phi$($\degree$)'
|
||||
ylabel = r'Signal (a. u.)'
|
||||
|
||||
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
|
||||
xlabel=xlabel, ylabel=ylabel,
|
||||
projection='stereo', colorbar=True, autoscale=True)
|
||||
view.select('theta', 'phi', 'cross_section')
|
||||
|
||||
|
||||
if scan_type == 'scatf':
|
||||
for i in range(self.phagenio.nat):
|
||||
proto_index = i+1
|
||||
title = 'Scattering factor at {:.3f} eV'.format(kinetic_energy)
|
||||
|
||||
view = dset.add_view("Proto. atom #{:d}".format(proto_index),
|
||||
title=title, projection='polar')
|
||||
where = "proto_index=={:d}".format(proto_index)
|
||||
view.select('theta', 'sf_module', where=where,
|
||||
legend=r'$|f(\theta)|$')
|
||||
view.select('theta', 'sf_real', where=where,
|
||||
legend=r'$\Im(f(\theta))$')
|
||||
view.select('theta', 'sf_imag', where=where,
|
||||
legend=r'$\Re(f(\theta))$')
|
||||
# save the cluster
|
||||
clusbuf = StringIO()
|
||||
self.atoms.info['absorber'] = self.atoms.absorber
|
||||
self.atoms.write(clusbuf, format='xyz')
|
||||
dset.add_parameter(group='Cluster', name='cluster', value=clusbuf.getvalue(), hidden="True")
|
||||
|
||||
LOGGER.info('%s scan computing done!', scan_type)
|
||||
|
||||
return self.iodata
|
||||
|
||||
def get_potential(self, atom_index=None, data=None, units={'energy': 'eV', 'space': 'angstrom'}):
|
||||
"""Computes the coulombic part of the atomic potential.
|
||||
|
||||
:param atom_index: The atom indices to get the potential of, either as a list or as a single integer
|
||||
:param data: The data object to store the results to
|
||||
:param units: The units to be used. A dictionary with the keys 'energy' and 'space'
|
||||
:return: A Data object
|
||||
"""
|
||||
LOGGER.info("Getting the Potential...")
|
||||
LOGGER.debug(get_call_info(inspect.currentframe()))
|
||||
|
||||
_units = {'energy': 'eV', 'space': 'angstrom'}
|
||||
_units.update(units)
|
||||
|
||||
if data:
|
||||
self.iodata = data
|
||||
|
||||
self.run_phagen()
|
||||
|
||||
filename = os.path.join(self.tmp_folder, 'output/tmatrix.tl')
|
||||
tl = self.phagenio.load_tl_file(filename)
|
||||
|
||||
filename = os.path.join(self.tmp_folder, 'output/cluster.clu')
|
||||
self.phagenio.load_cluster_file(filename)
|
||||
|
||||
filename = os.path.join(self.tmp_folder, 'bin/plot/plot_vc.dat')
|
||||
pot_data = self.phagenio.load_potential_file(filename)
|
||||
|
||||
cluster = self.phagen_parameters.get_parameter('atoms').value
|
||||
|
||||
dset = self.iodata.add_dset('Potential [{:d}]'.format(len(self.iodata)))
|
||||
r = []
|
||||
v = []
|
||||
index = np.empty((0,1), dtype=int)
|
||||
|
||||
absorber_position = cluster[cluster.absorber].position
|
||||
for _pot_data in pot_data:
|
||||
# find the proto index of these data
|
||||
at_position = (_pot_data['coord'] * UREG.bohr_radius).to('angstrom').magnitude + absorber_position
|
||||
at_index = get_atom_index(cluster, *at_position)
|
||||
at_proto_index = cluster[at_index].get('proto_index')
|
||||
#values = np.asarray(_pot_data['values'])
|
||||
values = _pot_data['values']
|
||||
index = np.append(index, np.ones(values.shape[0], dtype=int) * at_proto_index)
|
||||
r = np.append(r, (values[:, 0] * UREG.bohr_radius).to(_units['space']).magnitude)
|
||||
v = np.append(v, (values[:, 1] * UREG.rydberg).to(_units['energy']).magnitude)
|
||||
|
||||
dset.add_columns(distance=r, potential=v, index=index)
|
||||
view = dset.add_view('potential data', title='Potential energy of atoms',
|
||||
xlabel='distance from atomic center [{:s}]'.format(_units['space']),
|
||||
ylabel='energy [{:s}]'.format(_units['energy']), scale='linear',
|
||||
autoscale=True)
|
||||
|
||||
if atom_index == None:
|
||||
for i in range(pot_data[len(pot_data) - 1]['index']):
|
||||
view.select('distance', 'potential', where="index=={:d}".format(i),
|
||||
legend="Atom index #{:d}".format(i + 1))
|
||||
else:
|
||||
for i in atom_index:
|
||||
view.select('distance', 'potential', where="index=={:d}".format(cluster[i].get('proto_index') - 1),
|
||||
legend="Atom index #{:d}".format(i))
|
||||
|
||||
return self.iodata
|
||||
|
||||
def get_scattering_factors(self, level='1s', kinetic_energy=None,
|
||||
data=None):
|
||||
"""Computes the scattering factors of all prototypical atoms in the
|
||||
cluster.
|
||||
|
||||
This function computes the real and imaginery parts of the scattering
|
||||
factor as well as its modulus for each non symetrically equivalent atom
|
||||
in the cluster. The results are stored in the *data* object if provided
|
||||
as a parameter.
|
||||
|
||||
:param level: The electronic level. See :ref:`pedparameters-level`.
|
||||
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
|
||||
:param data: a :py:class:`iodata.Data` object to append the results to
|
||||
or None.
|
||||
|
||||
:returns: The modified :py:class:`iodata.Data` object passed as an
|
||||
argument or a new :py:class:`iodata.Data` object.
|
||||
|
||||
"""
|
||||
data = self._get_scan(scan_type='scatf', level=level, data=data,
|
||||
kinetic_energy=kinetic_energy)
|
||||
return data
|
||||
|
||||
def get_theta_scan(self, phi=0, theta=np.linspace(-70, 70, 141),
|
||||
level=None, kinetic_energy=None, data=None):
|
||||
"""Computes a polar scan of the emitted photoelectrons.
|
||||
|
||||
:param phi: The azimuthal angle in degrees. See
|
||||
:ref:`scanparameters-phi`.
|
||||
:param theta: All the values of the polar angle to be computed. See
|
||||
:ref:`scanparameters-theta`.
|
||||
:param level: The electronic level. See :ref:`pedparameters-level`.
|
||||
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
|
||||
:param data: a :py:class:`iodata.Data` object to append the results to
|
||||
or None.
|
||||
|
||||
:returns: The modified :py:class:`iodata.Data` object passed as an
|
||||
argument or a new :py:class:`iodata.Data` object.
|
||||
|
||||
"""
|
||||
data = self._get_scan(scan_type='theta', level=level, theta=theta,
|
||||
phi=phi, kinetic_energy=kinetic_energy, data=data)
|
||||
return data
|
||||
|
||||
def get_phi_scan(self, phi=np.linspace(0, 359, 359), theta=0,
|
||||
level=None, kinetic_energy=None, data=None):
|
||||
"""Computes an azimuthal scan of the emitted photoelectrons.
|
||||
|
||||
:param phi: All the values of the azimuthal angle to be computed. See
|
||||
:ref:`scanparameters-phi`.
|
||||
:param theta: The polar angle in degrees. See
|
||||
:ref:`scanparameters-theta`.
|
||||
:param level: The electronic level. See :ref:`pedparameters-level`.
|
||||
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
|
||||
:param data: a :py:class:`iodata.Data` object to append the results to
|
||||
or None.
|
||||
|
||||
:returns: The modified :py:class:`iodata.Data` object passed as an
|
||||
argument or a new :py:class:`iodata.Data` object.
|
||||
|
||||
"""
|
||||
data = self._get_scan(scan_type='phi', level=level, theta=theta,
|
||||
phi=phi, kinetic_energy=kinetic_energy, data=data)
|
||||
return data
|
||||
|
||||
def get_theta_phi_scan(self, phi=np.linspace(0, 360),
|
||||
theta=np.linspace(0, 90, 45), level=None,
|
||||
kinetic_energy=None, data=None):
|
||||
"""Computes a stereographic scan of the emitted photoelectrons.
|
||||
|
||||
The azimuth ranges from 0 to 360° and the polar angle ranges from 0 to
|
||||
90°.
|
||||
|
||||
:param level: The electronic level. See :ref:`pedparameters-level`.
|
||||
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
|
||||
:param data: a :py:class:`iodata.Data` object to append the results to
|
||||
or None.
|
||||
|
||||
:returns: The modified :py:class:`iodata.Data` object passed as an
|
||||
argument or a new :py:class:`iodata.Data` object.
|
||||
|
||||
"""
|
||||
self.spec_malloc_parameters.NPH_M = 8000
|
||||
data = self._get_scan(scan_type='theta_phi', level=level, theta=theta,
|
||||
phi=phi, kinetic_energy=kinetic_energy, data=data)
|
||||
return data
|
||||
|
||||
|
||||
class _EIG(_MSCALCULATOR):
|
||||
"""
|
||||
.. note::
|
||||
|
||||
This class constructor is not meant to be called directly by the user.
|
||||
Use the :py:func:`MSSPEC` to instanciate any calculator.
|
||||
|
||||
"""
|
||||
def __init__(self, algorithm='inversion', polarization=None, dichroism=None,
|
||||
spinpol=False, folder='./calc', txt='-'):
|
||||
_MSCALCULATOR.__init__(self, spectroscopy='EIG', algorithm=algorithm,
|
||||
polarization=polarization, dichroism=dichroism,
|
||||
spinpol=spinpol, folder=folder, txt=txt)
|
||||
if algorithm not in ('inversion', 'power'):
|
||||
LOGGER.error("Only the 'inversion' or the 'power' algorithms "
|
||||
"are supported in EIG spectroscopy mode")
|
||||
exit(1)
|
||||
self.iodata = iodata.Data('EIG Simulation')
|
||||
|
||||
|
||||
def get_eigen_values(self, level=None, kinetic_energy=None, data=None):
|
||||
LOGGER.info("Computting the eigen values...")
|
||||
if data:
|
||||
self.iodata = data
|
||||
|
||||
if kinetic_energy is None:
|
||||
# try to guess the kinetic energy
|
||||
kinetic_energy = self._guess_ke(level)
|
||||
|
||||
# if still None...
|
||||
if kinetic_energy is None:
|
||||
LOGGER.error('Unable to guess the kinetic energy!')
|
||||
raise ValueError('You must define a kinetic_energy value.')
|
||||
|
||||
# update the parameters
|
||||
self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy)
|
||||
all_ke = self.scan_parameters.get_parameter('ke_array')
|
||||
if np.any(all_ke < 0):
|
||||
LOGGER.error('Source energy is not high enough or level too deep!')
|
||||
raise ValueError('Kinetic energy is < 0! ({})'.format(
|
||||
kinetic_energy))
|
||||
|
||||
self.spectroscopy_parameters.set_parameter('level', level)
|
||||
|
||||
self.get_tmatrix()
|
||||
self.run_spec()
|
||||
|
||||
# Now load the data
|
||||
ndset = len(self.iodata)
|
||||
dset = self.iodata.add_dset('Eigen values calculation [{:d}]'.format(ndset))
|
||||
for p in self.get_parameters():
|
||||
bundle = {'group': str(p.group),
|
||||
'name': str(p.name),
|
||||
'value': str(p.value),
|
||||
'unit': '' if p.unit is None else str(p.unit)}
|
||||
dset.add_parameter(**bundle)
|
||||
|
||||
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
|
||||
data = self.specio.load_results(results_fname)
|
||||
|
||||
dset.add_columns(energy=data[:,0], eigen_value=data[:,1])
|
||||
|
||||
|
||||
return self.iodata
|
||||
|
||||
|
||||
def MSSPEC(spectroscopy='PED', **kwargs):
|
||||
""" The MsSpec calculator constructor.
|
||||
|
||||
Instanciates a calculator for the given spectroscopy.
|
||||
|
||||
:param spectroscopy: See :ref:`globalparameters-spectroscopy`.
|
||||
:param kwargs: Other keywords are passed to the spectroscopy-specific
|
||||
constructor.
|
||||
:returns: A :py:class:`calculator._MSCALCULATOR` object.
|
||||
"""
|
||||
module = sys.modules[__name__]
|
||||
cls = getattr(module, '_' + spectroscopy)
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
|
@ -0,0 +1,118 @@
|
|||
# coding: utf-8
|
||||
"""
|
||||
Module config
|
||||
=============
|
||||
|
||||
"""
|
||||
|
||||
from configparser import NoSectionError
|
||||
from configparser import SafeConfigParser as ConfigParser
|
||||
import os
|
||||
import sys
|
||||
import fnmatch
|
||||
from msspec.misc import LOGGER
|
||||
|
||||
|
||||
class NoConfigFile(Exception): pass
|
||||
|
||||
|
||||
class Config(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.fname = os.path.join(os.environ['HOME'], '.config/msspec/pymsspec.cfg')
|
||||
self.version = sys.modules['msspec'].__version__
|
||||
self.config = ConfigParser()
|
||||
self.defaults = {'path': 'None'}
|
||||
self.defaults.update(kwargs)
|
||||
|
||||
# try to load the config file, create one with defaults if none is found
|
||||
try:
|
||||
self.options = self.read()
|
||||
self.options.update(kwargs)
|
||||
self.set(**self.options)
|
||||
self.write()
|
||||
except NoConfigFile:
|
||||
# write a file with default options
|
||||
self.write(defaults=True)
|
||||
except NoSectionError:
|
||||
# the file exists but has no options for this version of pymsspec
|
||||
self.config.add_section(self.version)
|
||||
self.set(**self.defaults)
|
||||
self.write()
|
||||
|
||||
def read(self):
|
||||
fp = self.config.read(self.fname)
|
||||
if len(fp) == 0:
|
||||
raise NoConfigFile
|
||||
self.version = self.config.get('general', 'version')
|
||||
return dict(self.config.items(self.version))
|
||||
|
||||
def set(self, **kwargs):
|
||||
if not(self.config.has_section(self.version)):
|
||||
self.config.add_section(self.version)
|
||||
for k, v in list(kwargs.items()):
|
||||
self.config.set(self.version, k, v)
|
||||
|
||||
def get(self, key):
|
||||
return self.config.get(self.version, key)
|
||||
|
||||
def choose_msspec_folder(self, *folders):
|
||||
print("Several folders containing the appropriate version of MsSpec were found.")
|
||||
print("Please choose one tu use:")
|
||||
s = ""
|
||||
for i, f in enumerate(folders):
|
||||
s += '{:d}) {:s}\n'.format(i, f)
|
||||
print(s)
|
||||
return(folders[int(input('Your choice: '))])
|
||||
|
||||
def find_msspec_folders(self):
|
||||
version = sys.modules['msspec'].__version__
|
||||
folders = []
|
||||
i = 0
|
||||
prompt = 'Please wait while scanning the filesystem (%3d found) '
|
||||
sys.stdout.write(prompt % len(folders))
|
||||
sys.stdout.write('\033[s')
|
||||
|
||||
for root, dirnames, filenames in os.walk('/home/stricot'):
|
||||
sys.stdout.write('\033[u\033[k')
|
||||
sys.stdout.write('%d folders scanned' % i)
|
||||
i += 1
|
||||
for fn in fnmatch.filter(filenames, 'VERSION'):
|
||||
with open(os.path.join(root, fn), 'r') as fd:
|
||||
try:
|
||||
line = fd.readline()
|
||||
if line.strip() == version:
|
||||
folders.append(root)
|
||||
sys.stdout.write('\033[2000D\033[k')
|
||||
sys.stdout.write(prompt % len(folders))
|
||||
except:
|
||||
pass
|
||||
sys.stdout.write('\033[u\033[k\n')
|
||||
print('Done.')
|
||||
return(folders)
|
||||
|
||||
|
||||
def write(self, defaults=False):
|
||||
if defaults:
|
||||
self.set(**self.defaults)
|
||||
|
||||
with open(self.fname, 'w') as fd:
|
||||
self.config.write(fd)
|
||||
LOGGER.info("{} file written".format(self.fname))
|
||||
|
||||
def set_mode(self, mode="pymsspec"):
|
||||
if not(self.config.has_section("general")):
|
||||
self.config.add_section("general")
|
||||
self.config.set("general", "mode", str(mode))
|
||||
|
||||
def get_mode(self):
|
||||
return self.config.get("general", "mode")
|
||||
|
||||
def set_version(self, version=""):
|
||||
if not(self.config.has_section("general")):
|
||||
self.config.add_section("general")
|
||||
self.config.set("general", "version", version)
|
||||
|
||||
def remove_version(self, version):
|
||||
pass
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# vim: set fdm=indent ts=4 sw=4 sts=4 et ai tw=80 cc=+0 mouse=a nu : #
|
||||
|
||||
|
||||
from .electron_be import electron_be
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
# coding: utf-8
|
||||
|
||||
import unittest
|
||||
import numpy
|
||||
import delaunay.core as delc
|
||||
from ase import Atoms
|
||||
|
||||
def Define_Domain(set,ncover,ratio) :
|
||||
#defines the sphere englobing the molecule, such as we will have at least ncover empty spheres around the molecule,
|
||||
#and this empty sphere have a size around ratio x {global size of muffin tin atom size}
|
||||
|
||||
|
||||
def Distance(A,B)
|
||||
#calculate distance between A and B
|
||||
try :
|
||||
assert len(A) == len(B)
|
||||
d=0
|
||||
for l in range(0,len(A))
|
||||
d+=(B[l]-A[l])**2
|
||||
d=sqrt(d)
|
||||
except AssertionError :
|
||||
print("Error : distance calculated from two points in different spaces")
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# coding: utf-8
|
||||
|
||||
import unittest
|
||||
import numpy
|
||||
import delaunay.core as delc
|
||||
from ase import Atoms
|
||||
|
||||
|
||||
#=============================================
|
|
@ -0,0 +1,2 @@
|
|||
# coding: utf-8
|
||||
|
|
@ -0,0 +1,469 @@
|
|||
# coding: utf-8
|
||||
|
||||
============================================================
|
||||
| Empty_Spheres Modules |
|
||||
============================================================
|
||||
|
||||
Made by : Ulysse DUPONT
|
||||
Collaboration with : Didier SEBILLEAU, Sylvain TRICOT
|
||||
Helped by : Floris Bruynooghe (Delny) and the team from scipy.spatial (qhull/Voronoi)
|
||||
|
||||
Path : msspec/src/python/pymsspec/msspec/es
|
||||
Language : Python
|
||||
Important Global Variable : ES_AllRadius
|
||||
|
||||
============================================================
|
||||
| About ES_AllRadius |
|
||||
============================================================
|
||||
|
||||
For now : Empty spheres can be created from a cluster of Atoms.
|
||||
The missing part is the definition of the Empty-Sphere Python Class : they are considered as "X" Atoms.
|
||||
It means that the radius of empty spheres are not directly linked to them :
|
||||
The routine empty-spheres.Atom_Radius(Structure,n,list) will recognize empty spheres because their atom number is 0.
|
||||
So we created ES_AllRadius global variable, wich is updated after each EmptySphere generation with function
|
||||
"ES_AllRadius_Updater(NewES,Structure)". The new radius are calculated brutally by taking the max, without touching
|
||||
Structure molecules
|
||||
|
||||
To use this global variable, it will be necessary to delete no empty spheres (or be sure to delete the
|
||||
correspondant radius in ES_AllRadius).
|
||||
|
||||
An example of ES_AllRadius use is given afterward : in the example "Covering GeCl4 using ES_AllRadius"
|
||||
|
||||
In the future, the informations about each empty-spheres radius can be taken directly in construction routines, and linked
|
||||
to the empty-sphere python object.
|
||||
|
||||
|
||||
============================================================
|
||||
| Objectives |
|
||||
============================================================
|
||||
|
||||
The empty_spheres modules are routines made to add empty spheres in clusters for diffusion computations.
|
||||
The routines used are based on tetrahedralisation, convex-hull research and triangularisation to create adapted mesh,
|
||||
and uses tangent-spheres resolutions to compute adapted coordinates and radius of empty-spheres
|
||||
|
||||
|
||||
============================================================
|
||||
| How to use ? |
|
||||
============================================================
|
||||
|
||||
Before adding empty-spheres, you need a cluster, or list of positions.
|
||||
The argument name "Structure" designs the Atoms type in pymsspec. The coordinates of the set of points is given
|
||||
by the command : numpy.ndarray.tolist(Structure.positions)
|
||||
|
||||
For the moment, empty-sphere type isn't defined, only coordinates are computed.
|
||||
|
||||
To see how the routines work : We will show few examples : You just need to adjust the folder where the xyz files will be
|
||||
|
||||
|
||||
__________________Internal/External ConvexHull Cover in C60_____________________________________________________________
|
||||
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
Structure = read('cluster_examples/C60.xyz')
|
||||
ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1,tol=1)
|
||||
1
|
||||
Result = Atoms(positions = ExtCover)
|
||||
view(Structure + Result)
|
||||
IntCover1 = esph.Internal_Hull_Cover(Structure,radius_es=0.84,tol=0.6)
|
||||
l=len(IntCover1)
|
||||
wcolor = "B"+str(l)
|
||||
Result1 = Atoms(wcolor,positions = IntCover1)
|
||||
view(Structure + Result1)
|
||||
IntCover2 = esph.Internal_Hull_Cover(Structure + Result1)
|
||||
|
||||
Result2 = Atoms(positions = IntCover2)
|
||||
view(Result1 + Result2)
|
||||
view(Structure + Result1 + Result2)
|
||||
=================================Comments :
|
||||
|
||||
The view will allow you to see the added spheres. For the moment, no empty spheres objects have been created,
|
||||
so we defin special radii, from covalent radii table.
|
||||
|
||||
You can change IntCover1, for example taking radius_es = 0.71 and wcolor with "N" letter,
|
||||
or es_radius = 0.76 and C letter,etc... The letter allow view function to set correct radii.
|
||||
The tol parameter influences Fusion_Overlap. Spheres are fusionned if the distance beetween centers is less than
|
||||
the sum of the two spheres raddi, multiplied by tol parameter. So tol=1 is used to completely avoid spheres to touch
|
||||
themselves. Default value of tol has been subjectively settled at 0.6
|
||||
|
||||
ICover2 will ask for radius_es : it is not given in the function call.
|
||||
If you put radius = 1.3, for example, you will obtain the centroid of the hull after fusionning the spheres.
|
||||
Try very big or smaller radius to understand how fusion works.
|
||||
____________________________________________________
|
||||
|
||||
|
||||
|
||||
__________________Delaunay TetraHedral with a cube face-centered________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
|
||||
struct = struct = [[0,-1,0],[0,1,0],[0,0,1],[0,0,-1],[1,0,0],[-1,0,0],[-1,-1,-1],[1,-1,-1],[-1,1,-1],[1,1,-1],[-1,-1,1],[1,-1,1],[-1,1,1],[1,1,1]]
|
||||
Structure = Atoms("C14",positions=struct)
|
||||
set1 = esph.Delaunay_Tetrahedral_ES(Structure)
|
||||
Set1 = Atoms (positions = set1)
|
||||
view(Set1)
|
||||
view(Structure + Set1)
|
||||
|
||||
set2 = esph.Delaunay_Tetrahedral_ES(Structure+Set1)
|
||||
Set2 = Atoms (positions = set2)
|
||||
view(Set2)
|
||||
view(Structure + Set1 + Set2)
|
||||
=================================Comments :
|
||||
First iteration as you can see is given with np minsize and maxsize : This parameters are otionnaly set both at 0 and 999.
|
||||
The second iteration will show you an actual problem :
|
||||
The size of empty spheres must be saved to be used once more.
|
||||
If free space is to small, the routine will obtain some unsolvable problems. This issue is showed in your board with
|
||||
singular matrix
|
||||
____________________________________________________
|
||||
|
||||
|
||||
|
||||
|
||||
__________________Delaunay TetraHedral with a copper sample_____________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
Structure = read('cluster_examples/copper.xyz')
|
||||
set1 = esph.Delaunay_Tetrahedral_ES(Structure)
|
||||
Set1 = Atoms (positions = set1)
|
||||
view(Set1)
|
||||
view(Structure + Set1)
|
||||
|
||||
=================================Comments :
|
||||
First iteration as you can see is given with np minsize and maxsize : This parameters are otionnaly set both at 0 and 999.
|
||||
If you want to delete the empty-spheres added out of convexhull, you can add the parameter maxsize=2
|
||||
No second iteration is done : see the centered cube example for explanations.
|
||||
____________________________________________________
|
||||
|
||||
|
||||
|
||||
|
||||
__________________Experiment problem of missing spheres in cover________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
struct = struct = [[0,-1,0],[0,1,0],[0,0,1],[0,0,-1],[1,0,0],[-1,0,0],[-1,-1,-1],[1,-1,-1],[-1,1,-1],[1,1,-1],[-1,-1,1],[1,-1,1],[-1,1,1],[1,1,1]]
|
||||
Structure = Atoms("C14",positions=struct)
|
||||
|
||||
ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1,missing=True)
|
||||
1
|
||||
Result = Atoms(positions = ExtCover)
|
||||
view(Structure + Result)
|
||||
ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1)
|
||||
1
|
||||
Result = Atoms(positions = ExtCover)
|
||||
view(Structure + Result)
|
||||
=================================Comments :
|
||||
As you will see : The Second Result show only 6 empty spheres, and this spheres could maybe overlap the centers of facets.
|
||||
Classic ConveHull routine returns only boundary points of facets : if points are included in this facets, they miss...
|
||||
To solve this problem : put the optionnal parameter "missing" at "True". Do it when your cluster as some huge facets with
|
||||
some atoms included in (exactly like this cube facets, wich are centered).
|
||||
The first result shows the "good way" : it solves the problem of atoms forgotten by ConvexHull routine.
|
||||
____________________________________________________
|
||||
|
||||
|
||||
|
||||
__________________Covering Nanotube_____________________________________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
|
||||
Structure = read('cluster_examples/tubes.xyz')
|
||||
#set = np.ndarray.tolist(Structure.positions)
|
||||
#hull=ConvexHull(set)
|
||||
Cover = esph.Convex_Hull_Cover(Structure,radius_es=1,missing=True)
|
||||
|
||||
Result = Atoms(positions = Cover)
|
||||
view(Structure + Result)
|
||||
|
||||
=================================Comments :
|
||||
Computing time here is bigger, as you will see (Operation is done twice, if you need to go faster, you can do internal and
|
||||
external cover in one same routine, by not using Select_Int_Ext function.
|
||||
You can test routine with no missing parameter (missing=False by default) : You will feel good the problem of ConvexHull.
|
||||
|
||||
____________________________________________________
|
||||
|
||||
|
||||
__________________Covering little NH3___________________________________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
Structure = read('cluster_examples/NH3.xyz')
|
||||
|
||||
|
||||
FCover = esph.Flat_Covering(Structure)
|
||||
1
|
||||
3
|
||||
Result = Atoms(positions = FCover)
|
||||
view(Structure + Result)
|
||||
|
||||
Cover = esph.Convex_Hull_Cover(Structure)
|
||||
1
|
||||
1
|
||||
Result = Atoms(positions = Cover)
|
||||
view(Structure + Result)
|
||||
|
||||
TCover = esph.Delaunay_Tetrahedral_ES(Structure)
|
||||
Result = Atoms(positions = TCover)
|
||||
view(Structure + Result)
|
||||
=================================Comments :
|
||||
Using Flat Covening in caseof little molecule can let some problems : the "biggest plane" selected isn't H3, but an NH2
|
||||
face. The results you would prefer are obtained in Convex_Hull_Cover : with a small radius (1 for example), overlap will
|
||||
happen and routine computes only the empty sphere on the other side of H3 face. With bigger radius (try 2), you will
|
||||
obtain 3 empty spheres.
|
||||
TCover uses Tetrahedral resolution. But this problem set a radius solution of polygon with delta < 0, ie no solution is
|
||||
computed
|
||||
|
||||
|
||||
|
||||
__________________Covering GeCl4 using ES_AllRadius global variable_____________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
Structure = read('cluster_examples/GeCl4.xyz')
|
||||
Cover = esph.Convex_Hull_Cover(Structure,radius_es=2)
|
||||
1
|
||||
Result = Atoms(positions = Cover)
|
||||
view(Structure+Result)
|
||||
|
||||
ESAR1=esph.ES_AllRadius_Updater(Result,Structure,list=1)
|
||||
print("Actual ES radius list (ESAR1) :\n{}".format(ESAR1))
|
||||
Structure=Structure+Result
|
||||
|
||||
Cover = esph.Convex_Hull_Cover(Structure,radius_es=3.5,tol=0)
|
||||
1
|
||||
Result = Atoms(positions = Cover)
|
||||
view(Structure+Result)
|
||||
|
||||
ESAR2=esph.ES_AllRadius_Updater(Result,Structure,list=1)
|
||||
print("Actual ES radius list (ESAR2) :\n{}".format(ESAR2))
|
||||
comp1=ESAR2[-1]
|
||||
Cover = esph.Convex_Hull_Cover(Structure,radius_es=3.5)
|
||||
1
|
||||
Result = Atoms(positions = Cover)
|
||||
view(Structure+Result)
|
||||
|
||||
ESAR2bis=esph.ES_AllRadius_Updater(Result,Structure,list=1)
|
||||
comp2=ESAR2bis[-1]
|
||||
print("\n\nWithout fusioning 2nd Cover, empty spheres are sized {} , after fusion there radius is {}".format(comp1,comp2))
|
||||
|
||||
|
||||
=================================Comments :
|
||||
ESAR1 and ESAR2 are just copy in time of the global variable "ES_AllRadius". Indeed : this global variable is used in
|
||||
empty_spheres.py, not in main. So ES_AllRadius_Updater returns the global variable in the time it is called.
|
||||
|
||||
We also use this example to show tol use : in the second covering, we need a big radius to touch the 3 bounds of triangle
|
||||
vertices, so afterward the empty spheres are overlapping. So without tol parameter (tol=0.6 by default), the empty-spheres
|
||||
fusion, and the second cover is aligned with Cl atoms.
|
||||
With tol = 0 , no fusion is done, so we have more empty spheres in cover.
|
||||
Comparing the size of spheres with and without fusionning, you can see that fusion isn't always a good solution.
|
||||
|
||||
|
||||
|
||||
__________________Covering molecule Phtalocyanine_______________________________________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
from scipy.spatial import ConvexHull
|
||||
|
||||
Structure = read('cluster_examples/phtalocyanine.xyz')
|
||||
view(Structure)
|
||||
FCover = esph.Flat_Covering(Structure,R=2)
|
||||
|
||||
Result = Atoms(positions = FCover)
|
||||
view(Structure + Result)
|
||||
FCover = esph.Flat_Covering(Structure,Curved=True)
|
||||
|
||||
Result = Atoms(positions = FCover)
|
||||
view(Structure + Result)
|
||||
=================================Comments :
|
||||
The first FCover is using classic "FlatCovering" : the routine search for every plane, then triangulate and add ES.
|
||||
The problem here is that phtalocyanine is curved : so the polygons we see are not in same plane !
|
||||
As command : Enter 1 to tell there are a lot of different planes.
|
||||
To see the planes constructed, remove the R=2 parameter. Don't be afraid about time taken in this routine (5min possible)
|
||||
|
||||
To solve the problem : we add the "Curved=True" : as the cluster is close to z=0 plane (but curved), we project him as
|
||||
2D plane with z=0. So we can triangulate correctly. Be careful with this method : all spheres are taken in the mesh !
|
||||
As radius, you should select 1.5, and do again with 2 : The results are pretty different because of the overlap : it can
|
||||
be interesting to compare the results.
|
||||
|
||||
|
||||
|
||||
__________________Computin Voronoi Vertices of a Structure (face-centered cube)_________________________________________
|
||||
=================================Commands :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from es_mod import empty_spheres as esph
|
||||
Structure = read('cluster_examples/centeredcube.xyz')
|
||||
Vor=esph.Voronoi_Vertex(Structure)
|
||||
Result=Atoms(positions=Vor)
|
||||
view(Structure+Result)
|
||||
Structure = read('cluster_examples/GeCl4.xyz')
|
||||
Vor=esph.Voronoi_Vertex(Structure)
|
||||
Result=Atoms(positions=Vor)
|
||||
view(Structure+Result)
|
||||
Structure = read('cluster_examples/tubes.xyz')
|
||||
Vor=esph.Voronoi_Vertex(Structure)
|
||||
Result=Atoms(positions=Vor)
|
||||
view(Structure)
|
||||
view(Structure+Result)
|
||||
=================================Commands for complete Voronoi informations (ridges, regions/polyhedrons) :
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
import numpy as np
|
||||
from scipy.spatial import Voronoi
|
||||
|
||||
Structure = read('cluster_examples/centeredcube.xyz')
|
||||
Str=np.ndarray.tolist(Structure.positions)
|
||||
Vor = Voronoi(Str)
|
||||
|
||||
Vor. [Then click on Tab]
|
||||
=================================Comments :
|
||||
The Voronoi_Vertex routine uses scipy.spatial.Voronoi :
|
||||
https://docs.scipy.org/doc/scipy-0.19.1/reference/generated/scipy.spatial.Voronoi.html
|
||||
|
||||
You can use commands for complete Voronoi information to get voronoi ridges, regions, etc (see details after clicking Tab)
|
||||
|
||||
As you can see, the Voronoi research is very similar to the research of empty spheres. But the common errors solved
|
||||
before will happen here to (see the 2 last figures : example with nanotube)
|
||||
|
||||
Other Voronoi Tesselations can be found under C++ opensource Voro++ : http://math.lbl.gov/voro++/about.html
|
||||
|
||||
========================================================================================================================
|
||||
========================================================================================================================
|
||||
|
||||
|
||||
============================================================
|
||||
| Routines |
|
||||
============================================================
|
||||
|
||||
The Empty_Spheres routines are agenced in different folders :
|
||||
|
||||
=====================empty_spheres.py=========================
|
||||
|
||||
ES_AllRadius_Updater(NewES,Structure,[list]) : Update ES_AllRadius global variable with new radius of empty spheres
|
||||
given as NewES
|
||||
|
||||
Voronoi_Vertex(Structure) : Computes Voronoi Vertices of Structure
|
||||
|
||||
Delaunay_Tetrahedral_ES(Structure,[minsize],[maxsize],[tol]) : Creates a tetrehedral mesh from the structure,
|
||||
then returns for each center the perfect sphere going in.
|
||||
|
||||
Convex_Hull_Cover(Structure,[es_radius],[tol],[missing],[Int_Ext]) : Finds the exterior Hull from the set, create triangular
|
||||
mesh then returns cover coordinates. tol=0 => no fusion
|
||||
|
||||
Select_Int_Ext(Centroid,E1,E2,IE) : Clean the Cover, taking only internal or external
|
||||
|
||||
Internal_Hull_Cover(Structure,[es_radius],[tol],[missing]) : Finds the interior Hull from the set, create triangular
|
||||
mesh then returns cover coordinates
|
||||
|
||||
Internal_Hull_Centers(set) : Finds the interior Hull from the set, create triangular mesh then returns centers coordinates
|
||||
|
||||
ES_Fusion(set, structure, size) : Change the set by clustering spheres near from size to each other. No size given => take shortest
|
||||
Maintain distances with structure, compared with the ancient set.
|
||||
|
||||
Fusion_Overlap(Spheres_Data,tol) : Find Spheres touching each other, and fusions them. Don't return radius : only final coordinates
|
||||
|
||||
Flat_Covering(Structure,[R],[tol],[Curved]) : For flat (or almost) set : Searchs major plane, triangulates,
|
||||
and cover the 2 sides.
|
||||
|
||||
Plane_Triangulation(Plane3D,Plane_eq): Return triangulation of a 3D Plane (convert into 2D, uses Delny)
|
||||
|
||||
Atom_Radius(Structure,n,list) : Returns radius of n°th atom in Structure (Angstrom). Regroup different radius lists.
|
||||
|
||||
Convex_Hull_InterCover(set) : Return list of internal cover using ConvexHull : Different from Delaunay_Intersphere :
|
||||
made for empty clusters
|
||||
|
||||
==============================================================
|
||||
=====================es_clustering.py=========================
|
||||
Tangent_Fourth_Sphere(Spheres_data, r, inout=1) : From 3 tangent spheres, returns the fourth, tangent to others, with radius r.
|
||||
inout determines the side of the coordinate.
|
||||
Spheres_Data_Structure_Extractor (Structure,list) : From Structure, returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
|
||||
Spheres_Data_XYZ_Extractor (name,list) : From name, given as "_____.xyz", returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
|
||||
Tetrahedron_ES (Spheres_data) : From 4 spheres forming a tetrahedron,returns the sphere tangent to others, with radius r.
|
||||
|
||||
Triangle_ES (Spheres_data,R) : Returns the 2 solutions of tangent of 3 spheres with radius R.
|
||||
==============================================================
|
||||
========================es_tools.py===========================
|
||||
=================Vector tools==================
|
||||
vector_def(A,B) : returns simply the vector translating A to B
|
||||
vector_norm(V) : return euclidian norm of a vector
|
||||
throw_away (P,O,d) : returns P' so as O,P and P' are aligned, and OP'=d. So it translate P from O with distance d to direction OP
|
||||
angle_vector(u,v) : returns the value of the convex angle defined by vectors u and v, in radians.
|
||||
ColinearTest(u,v) : returns 1 if u and v colinear, and 0 if not.
|
||||
|
||||
===========Distance and Proximity tools========
|
||||
distance(a,b) : calculate distance between 2 points
|
||||
search_nearest(point,set,d) : search the point in set wich is the nearest from point. We must know that the min distance is d
|
||||
point_set_proximity(point, set) : returns the min distance between the point and the set of points
|
||||
set_set_proximity(S1,S2) : returns minimal distance between each points of each sets.
|
||||
longest_dist(set) : returns the longest distance between the points in the set
|
||||
shortest_dist(set) : returns the shortest distance between the points in the set
|
||||
dist_point_plan(Pt,Plan) : From Pt=[x,y,z] and Plan=[a,b,c,d], return distance beetween Pt and Plan
|
||||
|
||||
===============Construction tools===============
|
||||
Isobarycenter(set) : Calculate isobarycenter of a set of points. Returns his coordinates
|
||||
Invert_Coord(set,O,r) : Apply circular inversion to every point in the set, excepted for origin, remaining origin.
|
||||
Midpoint(P1,P2) : Computes the middle point of P1 and P2
|
||||
rot3D(P,A,u) : Returns P' image of P by rotation from angle A around unity vector u
|
||||
===================Data tools===================
|
||||
commonlist(L1,L2) : Returns a list of elements common to L1 and L2, ie output is L1 intercection with L2
|
||||
cleanlist(list) : Returns a list without repeated elements (each element in cleaned list appears only once)
|
||||
=====================es_sym_analys.py=========================
|
||||
sym_analyse(Cluster) : Convert set into xyz folder, then finds all symetries using. Uses compare_sym and read_sym_file
|
||||
|
||||
major_plane(set) : Search the major plane with max nb of points in set and returns all of his points, and his equation
|
||||
|
||||
Cluster_flatness_informations(set) : Returns set's hull's total volume and area
|
||||
|
||||
Cluster_search_hollow(set,tol) : Returns the hollow datas : hollow=[[list,center,volume]...] where list is hollow' vertice's
|
||||
|
||||
Cluster_emptyness_informations(structure) : Returns the % of volume of hull occupied by his spheres index,[center,volume] his hollow's center and volume. tol defines hollow's diagonale
|
||||
|
||||
Vertice_Sphere_Proportion(O,hull) : Returns the proportion of the sphere centered in Oth pt in the hull (proportion in ]O,1])
|
||||
|
||||
hull_search_neighbors(O,Simplices) : Returns list including O and neighbors (list contains only index, no coordinates)
|
||||
|
||||
convex_base(Neil,Simplices) : Returns all Neil[0] neighbors so as ConvBase is the base of pyramid from top Neil[0]
|
||||
|
||||
Neighbors_List(numAtom,Structure) : Returns index list in Structure of neighbors of Atom indexed numAtom. numatom not included
|
||||
|
||||
Hull_Tetracut(O,Structure,hull) : Returns index list in Structure of centers of spheres defining the terahedron cuting the vertice O
|
||||
|
||||
facet_cap(O,Structure,hull) : Return the proportion of sphere centered in O out from the hull(ie return the cap proportion of the sphere defined by cuting sphere with hull facets)
|
||||
==============================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
PARAMETER(NATCLU_M=700,NLINE_M=10000,NP_M=50)
|
||||
PARAMETER(NATP_M=700,NAT_EQ_M=64)
|
||||
C
|
||||
C NATCLU_M : MAXIMUM NUMBER OF ATOMS IN THE CLUSTER FILE
|
||||
C NLINE_M : MAXIMUM NUMBER OF LINES IN THE CLUSTER FILE
|
||||
C NP_M : MAXIMUM NUMBER OF PREAMBLE LINES IN THE
|
||||
C phagen_scf. f INPUT FILE (BEFORE THE CLUSTER
|
||||
C SECTION)
|
||||
C NATP_M : MAXIMUM NUMBER OF PROTOTYPICAL ATOMS
|
||||
C NAT_EQ_M : MAXIMUM NUMBER OF EQUIVALENT ATOMS
|
||||
C
|
|
@ -0,0 +1,73 @@
|
|||
#! /bin/bash -f
|
||||
clear
|
||||
echo " "
|
||||
echo " "
|
||||
echo "****************************************************"
|
||||
echo "* *"
|
||||
echo "* CLUSTER GEOMETRY ANALYSIS CODE *"
|
||||
echo "* *"
|
||||
echo "****************************************************"
|
||||
echo " "
|
||||
echo " "
|
||||
#
|
||||
time -p ./clus_geom <<Fin >& error.dat
|
||||
Test.xyz # Input cluster file
|
||||
1 # Tetrahedra detection
|
||||
1 # Octahedra detection
|
||||
1 # Cube detection
|
||||
1 # Hollow molecules detection
|
||||
1 # Nanotube detection
|
||||
1 # Regular polygons detection
|
||||
1 # Iregular polygons detection
|
||||
1 # Symmetries detection
|
||||
Fin
|
||||
#
|
||||
# Checking for errors in the execution
|
||||
#
|
||||
cat error.dat | sed -e '1,35d' \
|
||||
-e '/real/,/ /d' > error.txt
|
||||
#
|
||||
# Checking for a blend of dialog
|
||||
#
|
||||
DIAL=`which dialog | cut -d: -f2 | grep -c 'dialog'`
|
||||
XDIA=`which Xdialog | cut -d: -f2 | grep -c 'Xdialog'`
|
||||
KDIA=`which kdialog | cut -d: -f2 | grep -c 'kdialog'`
|
||||
ZENI=`which zenity | cut -d: -f2 | grep -c 'zenity'`
|
||||
#
|
||||
if [ "$ZENI" -ne "0" ]; then
|
||||
DIALOG=zenity
|
||||
else
|
||||
if [ "$XDIA" -ne "0" ]; then
|
||||
DIALOG=Xdialog
|
||||
else
|
||||
if [ "$KDIA" -ne "0" ]; then
|
||||
DIALOG=kdialog
|
||||
else
|
||||
if [ "$DIAL" -ne "0" ]; then
|
||||
DIALOG=dialog
|
||||
else
|
||||
DIALOG=none
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#
|
||||
FILE=`ls -at | grep .lis | awk '{print $1}'`
|
||||
tail --lines=10 $FILE | grep '<<<<' | sed 's/<</ /g' | sed 's/>>/ /g' >> run.txt
|
||||
cat run.txt >> error.txt
|
||||
ERR=`cat error.txt`
|
||||
NLINE=`cat error.txt | wc -l`
|
||||
#
|
||||
if [ $NLINE != 0 ]; then
|
||||
if [ "$DIALOG" = "zenity" ]; then
|
||||
zenity --width 400 --height 180 \
|
||||
--title "MsSpec-1.1 runtime error" \
|
||||
--info --text "The code has stopped with the message : \n \n \n $ERR" \
|
||||
--timeout 5
|
||||
fi
|
||||
fi
|
||||
#
|
||||
rm -f error.dat error.txt run.txt
|
||||
#
|
||||
exit
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
60
|
||||
|
||||
C -0.70401 0.00110 3.57345
|
||||
C 0.70083 0.00133 3.57327
|
||||
C 3.57574 -0.70246 0.00129
|
||||
C 3.57685 0.70239 0.00110
|
||||
C 0.00074 3.57570 0.70219
|
||||
C 0.00060 3.57460 -0.70265
|
||||
C 0.70410 -0.00010 -3.57531
|
||||
C -0.70080 -0.00043 -3.57472
|
||||
C -3.57649 0.70168 -0.00040
|
||||
C -3.57650 -0.70328 -0.00080
|
||||
C -0.00022 -3.57603 -0.70253
|
||||
C -0.00065 -3.57534 0.70260
|
||||
C 1.45527 1.22059 3.10904
|
||||
C 1.22007 3.11053 1.45642
|
||||
C 3.11158 1.45695 1.22069
|
||||
C -1.45773 1.22048 3.10909
|
||||
C -3.11002 1.45543 1.21878
|
||||
C -1.21916 3.10985 1.45535
|
||||
C -1.45798 -1.21865 3.10901
|
||||
C -3.11094 -1.45629 1.21895
|
||||
C -1.22020 -3.10925 1.45579
|
||||
C 1.45456 -1.21797 3.10872
|
||||
C 1.21832 -3.10945 1.45706
|
||||
C 3.10958 -1.45572 1.22084
|
||||
C -1.45534 -1.22011 -3.11047
|
||||
C -1.21944 -3.11066 -1.45668
|
||||
C -3.10954 -1.45689 -1.22018
|
||||
C 1.45725 -1.21982 -3.10992
|
||||
C 3.10891 -1.45572 -1.21802
|
||||
C 1.21907 -3.10943 -1.45581
|
||||
C 1.45804 1.21938 -3.10976
|
||||
C 3.11051 1.45522 -1.21855
|
||||
C 1.22021 3.10873 -1.45574
|
||||
C -1.45417 1.21854 -3.10850
|
||||
C -1.21859 3.10885 -1.45655
|
||||
C -3.10979 1.45514 -1.21977
|
||||
C 0.75317 2.35757 2.67585
|
||||
C 2.67579 0.75479 2.35684
|
||||
C 2.35709 2.67579 0.75442
|
||||
C -2.67816 0.75415 2.35674
|
||||
C -0.75451 2.35720 2.67539
|
||||
C -2.35486 2.67378 0.75265
|
||||
C -2.67807 -0.75360 2.35625
|
||||
C -0.75519 -2.35526 2.67525
|
||||
C -2.35673 -2.67557 0.75321
|
||||
C 0.75208 -2.35504 2.67576
|
||||
C 2.35468 -2.67447 0.75491
|
||||
C 2.67533 -0.75265 2.35696
|
||||
C -0.75325 -2.35679 -2.67619
|
||||
C -2.67475 -0.75438 -2.35653
|
||||
C -2.35590 -2.67596 -0.75411
|
||||
C 2.67648 -0.75411 -2.35574
|
||||
C 0.75400 -2.35630 -2.67588
|
||||
C 2.35436 -2.67411 -0.75219
|
||||
C 2.67749 0.75328 -2.35614
|
||||
C 0.75540 2.35626 -2.67613
|
||||
C 2.35610 2.67388 -0.75278
|
||||
C -0.75218 2.35599 -2.67618
|
||||
C -2.35517 2.67402 -0.75419
|
||||
C -2.67390 0.75268 -2.35549
|
|
@ -0,0 +1,7 @@
|
|||
5
|
||||
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
|
||||
Ge 0.0000 0.0000 0.0000 32
|
||||
Cl 1.2100 1.2100 1.2100 17
|
||||
Cl 1.2100 -1.2100 -1.2100 17
|
||||
Cl -1.2100 1.2100 -1.2100 17
|
||||
Cl -1.2100 -1.2100 1.2100 17
|
|
@ -0,0 +1,5 @@
|
|||
3
|
||||
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
|
||||
O 0.00000000 0.00000000 0.11926200 8
|
||||
H 0.00000000 0.76323900 -0.47704700 1
|
||||
H 0.00000000 -0.76323900 -0.47704700 1
|
|
@ -0,0 +1,6 @@
|
|||
4
|
||||
Ammonia
|
||||
N 0.257 -0.363 0.000
|
||||
H 0.257 0.727 0.000
|
||||
H 0.771 -0.727 0.890
|
||||
H 0.771 -0.727 -0.890
|
|
@ -0,0 +1,16 @@
|
|||
14
|
||||
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
|
||||
C 0.00000000 -1.00000000 0.00000000 6
|
||||
C 0.00000000 1.00000000 0.00000000 6
|
||||
C 0.00000000 0.00000000 1.00000000 6
|
||||
C 0.00000000 0.00000000 -1.00000000 6
|
||||
C 1.00000000 0.00000000 0.00000000 6
|
||||
C -1.00000000 0.00000000 0.00000000 6
|
||||
C -1.00000000 -1.00000000 -1.00000000 6
|
||||
C 1.00000000 -1.00000000 -1.00000000 6
|
||||
C -1.00000000 1.00000000 -1.00000000 6
|
||||
C 1.00000000 1.00000000 -1.00000000 6
|
||||
C -1.00000000 -1.00000000 1.00000000 6
|
||||
C 1.00000000 -1.00000000 1.00000000 6
|
||||
C -1.00000000 1.00000000 1.00000000 6
|
||||
C 1.00000000 1.00000000 1.00000000 6
|
|
@ -0,0 +1,85 @@
|
|||
83
|
||||
Lattice="36.0 0.0 0.0 0.0 36.0 0.0 0.0 0.0 36.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T"
|
||||
Cu -5.40000000 -3.60000000 -1.80000000 29
|
||||
Cu -5.40000000 -1.80000000 -3.60000000 29
|
||||
Cu -5.40000000 -1.80000000 0.00000000 29
|
||||
Cu -5.40000000 0.00000000 -1.80000000 29
|
||||
Cu -5.40000000 1.80000000 -3.60000000 29
|
||||
Cu -7.20000000 0.00000000 0.00000000 29
|
||||
Cu -5.40000000 1.80000000 0.00000000 29
|
||||
Cu -5.40000000 3.60000000 -1.80000000 29
|
||||
Cu -3.60000000 -5.40000000 -1.80000000 29
|
||||
Cu -1.80000000 -5.40000000 -3.60000000 29
|
||||
Cu -1.80000000 -5.40000000 0.00000000 29
|
||||
Cu -3.60000000 -1.80000000 -5.40000000 29
|
||||
Cu -1.80000000 -3.60000000 -5.40000000 29
|
||||
Cu -3.60000000 -3.60000000 -3.60000000 29
|
||||
Cu -3.60000000 -1.80000000 -1.80000000 29
|
||||
Cu -1.80000000 -3.60000000 -1.80000000 29
|
||||
Cu -1.80000000 -1.80000000 -3.60000000 29
|
||||
Cu -3.60000000 -3.60000000 0.00000000 29
|
||||
Cu -1.80000000 -1.80000000 0.00000000 29
|
||||
Cu -3.60000000 1.80000000 -5.40000000 29
|
||||
Cu -1.80000000 0.00000000 -5.40000000 29
|
||||
Cu -3.60000000 0.00000000 -3.60000000 29
|
||||
Cu -3.60000000 1.80000000 -1.80000000 29
|
||||
Cu -1.80000000 0.00000000 -1.80000000 29
|
||||
Cu -1.80000000 1.80000000 -3.60000000 29
|
||||
Cu -3.60000000 0.00000000 0.00000000 29
|
||||
Cu -1.80000000 1.80000000 0.00000000 29
|
||||
Cu -1.80000000 3.60000000 -5.40000000 29
|
||||
Cu -3.60000000 3.60000000 -3.60000000 29
|
||||
Cu -3.60000000 5.40000000 -1.80000000 29
|
||||
Cu -1.80000000 3.60000000 -1.80000000 29
|
||||
Cu -1.80000000 5.40000000 -3.60000000 29
|
||||
Cu -3.60000000 3.60000000 0.00000000 29
|
||||
Cu -1.80000000 5.40000000 0.00000000 29
|
||||
Cu 0.00000000 -5.40000000 -1.80000000 29
|
||||
Cu 1.80000000 -5.40000000 -3.60000000 29
|
||||
Cu 0.00000000 -7.20000000 0.00000000 29
|
||||
Cu 1.80000000 -5.40000000 0.00000000 29
|
||||
Cu 0.00000000 -1.80000000 -5.40000000 29
|
||||
Cu 1.80000000 -3.60000000 -5.40000000 29
|
||||
Cu 0.00000000 -3.60000000 -3.60000000 29
|
||||
Cu 0.00000000 -1.80000000 -1.80000000 29
|
||||
Cu 1.80000000 -3.60000000 -1.80000000 29
|
||||
Cu 1.80000000 -1.80000000 -3.60000000 29
|
||||
Cu 0.00000000 -3.60000000 0.00000000 29
|
||||
Cu 1.80000000 -1.80000000 0.00000000 29
|
||||
Cu 0.00000000 0.00000000 -7.20000000 29
|
||||
Cu 0.00000000 1.80000000 -5.40000000 29
|
||||
Cu 1.80000000 0.00000000 -5.40000000 29
|
||||
Cu 0.00000000 0.00000000 -3.60000000 29
|
||||
Cu 0.00000000 1.80000000 -1.80000000 29
|
||||
Cu 1.80000000 0.00000000 -1.80000000 29
|
||||
Cu 1.80000000 1.80000000 -3.60000000 29
|
||||
Cu 0.00000000 0.00000000 0.00000000 29
|
||||
Cu 1.80000000 1.80000000 0.00000000 29
|
||||
Cu 1.80000000 3.60000000 -5.40000000 29
|
||||
Cu 0.00000000 3.60000000 -3.60000000 29
|
||||
Cu 0.00000000 5.40000000 -1.80000000 29
|
||||
Cu 1.80000000 3.60000000 -1.80000000 29
|
||||
Cu 1.80000000 5.40000000 -3.60000000 29
|
||||
Cu 0.00000000 3.60000000 0.00000000 29
|
||||
Cu 1.80000000 5.40000000 0.00000000 29
|
||||
Cu 0.00000000 7.20000000 0.00000000 29
|
||||
Cu 3.60000000 -5.40000000 -1.80000000 29
|
||||
Cu 3.60000000 -1.80000000 -5.40000000 29
|
||||
Cu 3.60000000 -3.60000000 -3.60000000 29
|
||||
Cu 3.60000000 -1.80000000 -1.80000000 29
|
||||
Cu 5.40000000 -3.60000000 -1.80000000 29
|
||||
Cu 5.40000000 -1.80000000 -3.60000000 29
|
||||
Cu 3.60000000 -3.60000000 0.00000000 29
|
||||
Cu 5.40000000 -1.80000000 0.00000000 29
|
||||
Cu 3.60000000 1.80000000 -5.40000000 29
|
||||
Cu 3.60000000 0.00000000 -3.60000000 29
|
||||
Cu 3.60000000 1.80000000 -1.80000000 29
|
||||
Cu 5.40000000 0.00000000 -1.80000000 29
|
||||
Cu 5.40000000 1.80000000 -3.60000000 29
|
||||
Cu 3.60000000 0.00000000 0.00000000 29
|
||||
Cu 5.40000000 1.80000000 0.00000000 29
|
||||
Cu 3.60000000 3.60000000 -3.60000000 29
|
||||
Cu 3.60000000 5.40000000 -1.80000000 29
|
||||
Cu 5.40000000 3.60000000 -1.80000000 29
|
||||
Cu 3.60000000 3.60000000 0.00000000 29
|
||||
Cu 7.20000000 0.00000000 0.00000000 29
|
|
@ -0,0 +1,146 @@
|
|||
144
|
||||
Lattice="28.700000000000003 0.0 0.0 0.0 28.700000000000003 0.0 0.0 0.0 28.700000000000003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T"
|
||||
Fe -7.17500000 -4.30500000 -1.43500000 26
|
||||
Fe -7.17500000 -1.43500000 -4.30500000 26
|
||||
Fe -7.17500000 -1.43500000 -1.43500000 26
|
||||
Fe -7.17500000 1.43500000 -4.30500000 26
|
||||
Fe -7.17500000 1.43500000 -1.43500000 26
|
||||
Fe -8.61000000 0.00000000 0.00000000 26
|
||||
Fe -7.17500000 4.30500000 -1.43500000 26
|
||||
Fe -4.30500000 -7.17500000 -1.43500000 26
|
||||
Fe -4.30500000 -4.30500000 -4.30500000 26
|
||||
Fe -5.74000000 -5.74000000 -2.87000000 26
|
||||
Fe -4.30500000 -4.30500000 -1.43500000 26
|
||||
Fe -5.74000000 -5.74000000 0.00000000 26
|
||||
Fe -4.30500000 -1.43500000 -7.17500000 26
|
||||
Fe -5.74000000 -2.87000000 -5.74000000 26
|
||||
Fe -4.30500000 -1.43500000 -4.30500000 26
|
||||
Fe -5.74000000 -2.87000000 -2.87000000 26
|
||||
Fe -4.30500000 -1.43500000 -1.43500000 26
|
||||
Fe -5.74000000 -2.87000000 0.00000000 26
|
||||
Fe -4.30500000 1.43500000 -7.17500000 26
|
||||
Fe -5.74000000 0.00000000 -5.74000000 26
|
||||
Fe -4.30500000 1.43500000 -4.30500000 26
|
||||
Fe -5.74000000 0.00000000 -2.87000000 26
|
||||
Fe -4.30500000 1.43500000 -1.43500000 26
|
||||
Fe -5.74000000 0.00000000 0.00000000 26
|
||||
Fe -5.74000000 2.87000000 -5.74000000 26
|
||||
Fe -4.30500000 4.30500000 -4.30500000 26
|
||||
Fe -5.74000000 2.87000000 -2.87000000 26
|
||||
Fe -4.30500000 4.30500000 -1.43500000 26
|
||||
Fe -5.74000000 2.87000000 0.00000000 26
|
||||
Fe -5.74000000 5.74000000 -2.87000000 26
|
||||
Fe -4.30500000 7.17500000 -1.43500000 26
|
||||
Fe -5.74000000 5.74000000 0.00000000 26
|
||||
Fe -1.43500000 -7.17500000 -4.30500000 26
|
||||
Fe -1.43500000 -7.17500000 -1.43500000 26
|
||||
Fe -1.43500000 -4.30500000 -7.17500000 26
|
||||
Fe -2.87000000 -5.74000000 -5.74000000 26
|
||||
Fe -1.43500000 -4.30500000 -4.30500000 26
|
||||
Fe -2.87000000 -5.74000000 -2.87000000 26
|
||||
Fe -1.43500000 -4.30500000 -1.43500000 26
|
||||
Fe -2.87000000 -5.74000000 0.00000000 26
|
||||
Fe -1.43500000 -1.43500000 -7.17500000 26
|
||||
Fe -2.87000000 -2.87000000 -5.74000000 26
|
||||
Fe -1.43500000 -1.43500000 -4.30500000 26
|
||||
Fe -2.87000000 -2.87000000 -2.87000000 26
|
||||
Fe -1.43500000 -1.43500000 -1.43500000 26
|
||||
Fe -2.87000000 -2.87000000 0.00000000 26
|
||||
Fe -1.43500000 1.43500000 -7.17500000 26
|
||||
Fe -2.87000000 0.00000000 -5.74000000 26
|
||||
Fe -1.43500000 1.43500000 -4.30500000 26
|
||||
Fe -2.87000000 0.00000000 -2.87000000 26
|
||||
Fe -1.43500000 1.43500000 -1.43500000 26
|
||||
Fe -2.87000000 0.00000000 0.00000000 26
|
||||
Fe -1.43500000 4.30500000 -7.17500000 26
|
||||
Fe -2.87000000 2.87000000 -5.74000000 26
|
||||
Fe -1.43500000 4.30500000 -4.30500000 26
|
||||
Fe -2.87000000 2.87000000 -2.87000000 26
|
||||
Fe -1.43500000 4.30500000 -1.43500000 26
|
||||
Fe -2.87000000 2.87000000 0.00000000 26
|
||||
Fe -2.87000000 5.74000000 -5.74000000 26
|
||||
Fe -1.43500000 7.17500000 -4.30500000 26
|
||||
Fe -2.87000000 5.74000000 -2.87000000 26
|
||||
Fe -1.43500000 7.17500000 -1.43500000 26
|
||||
Fe -2.87000000 5.74000000 0.00000000 26
|
||||
Fe 1.43500000 -7.17500000 -4.30500000 26
|
||||
Fe 1.43500000 -7.17500000 -1.43500000 26
|
||||
Fe 0.00000000 -8.61000000 0.00000000 26
|
||||
Fe 1.43500000 -4.30500000 -7.17500000 26
|
||||
Fe 0.00000000 -5.74000000 -5.74000000 26
|
||||
Fe 1.43500000 -4.30500000 -4.30500000 26
|
||||
Fe 0.00000000 -5.74000000 -2.87000000 26
|
||||
Fe 1.43500000 -4.30500000 -1.43500000 26
|
||||
Fe 0.00000000 -5.74000000 0.00000000 26
|
||||
Fe 1.43500000 -1.43500000 -7.17500000 26
|
||||
Fe 0.00000000 -2.87000000 -5.74000000 26
|
||||
Fe 1.43500000 -1.43500000 -4.30500000 26
|
||||
Fe 0.00000000 -2.87000000 -2.87000000 26
|
||||
Fe 1.43500000 -1.43500000 -1.43500000 26
|
||||
Fe 0.00000000 -2.87000000 0.00000000 26
|
||||
Fe 0.00000000 0.00000000 -8.61000000 26
|
||||
Fe 1.43500000 1.43500000 -7.17500000 26
|
||||
Fe 0.00000000 0.00000000 -5.74000000 26
|
||||
Fe 1.43500000 1.43500000 -4.30500000 26
|
||||
Fe 0.00000000 0.00000000 -2.87000000 26
|
||||
Fe 1.43500000 1.43500000 -1.43500000 26
|
||||
Fe 0.00000000 0.00000000 0.00000000 26
|
||||
Fe 1.43500000 4.30500000 -7.17500000 26
|
||||
Fe 0.00000000 2.87000000 -5.74000000 26
|
||||
Fe 1.43500000 4.30500000 -4.30500000 26
|
||||
Fe 0.00000000 2.87000000 -2.87000000 26
|
||||
Fe 1.43500000 4.30500000 -1.43500000 26
|
||||
Fe 0.00000000 2.87000000 0.00000000 26
|
||||
Fe 0.00000000 5.74000000 -5.74000000 26
|
||||
Fe 1.43500000 7.17500000 -4.30500000 26
|
||||
Fe 0.00000000 5.74000000 -2.87000000 26
|
||||
Fe 1.43500000 7.17500000 -1.43500000 26
|
||||
Fe 0.00000000 5.74000000 0.00000000 26
|
||||
Fe 0.00000000 8.61000000 0.00000000 26
|
||||
Fe 4.30500000 -7.17500000 -1.43500000 26
|
||||
Fe 2.87000000 -5.74000000 -5.74000000 26
|
||||
Fe 4.30500000 -4.30500000 -4.30500000 26
|
||||
Fe 2.87000000 -5.74000000 -2.87000000 26
|
||||
Fe 4.30500000 -4.30500000 -1.43500000 26
|
||||
Fe 2.87000000 -5.74000000 0.00000000 26
|
||||
Fe 4.30500000 -1.43500000 -7.17500000 26
|
||||
Fe 2.87000000 -2.87000000 -5.74000000 26
|
||||
Fe 4.30500000 -1.43500000 -4.30500000 26
|
||||
Fe 2.87000000 -2.87000000 -2.87000000 26
|
||||
Fe 4.30500000 -1.43500000 -1.43500000 26
|
||||
Fe 2.87000000 -2.87000000 0.00000000 26
|
||||
Fe 4.30500000 1.43500000 -7.17500000 26
|
||||
Fe 2.87000000 0.00000000 -5.74000000 26
|
||||
Fe 4.30500000 1.43500000 -4.30500000 26
|
||||
Fe 2.87000000 0.00000000 -2.87000000 26
|
||||
Fe 4.30500000 1.43500000 -1.43500000 26
|
||||
Fe 2.87000000 0.00000000 0.00000000 26
|
||||
Fe 2.87000000 2.87000000 -5.74000000 26
|
||||
Fe 4.30500000 4.30500000 -4.30500000 26
|
||||
Fe 2.87000000 2.87000000 -2.87000000 26
|
||||
Fe 4.30500000 4.30500000 -1.43500000 26
|
||||
Fe 2.87000000 2.87000000 0.00000000 26
|
||||
Fe 2.87000000 5.74000000 -5.74000000 26
|
||||
Fe 2.87000000 5.74000000 -2.87000000 26
|
||||
Fe 4.30500000 7.17500000 -1.43500000 26
|
||||
Fe 2.87000000 5.74000000 0.00000000 26
|
||||
Fe 5.74000000 -5.74000000 -2.87000000 26
|
||||
Fe 7.17500000 -4.30500000 -1.43500000 26
|
||||
Fe 5.74000000 -5.74000000 0.00000000 26
|
||||
Fe 5.74000000 -2.87000000 -5.74000000 26
|
||||
Fe 7.17500000 -1.43500000 -4.30500000 26
|
||||
Fe 5.74000000 -2.87000000 -2.87000000 26
|
||||
Fe 7.17500000 -1.43500000 -1.43500000 26
|
||||
Fe 5.74000000 -2.87000000 0.00000000 26
|
||||
Fe 5.74000000 0.00000000 -5.74000000 26
|
||||
Fe 7.17500000 1.43500000 -4.30500000 26
|
||||
Fe 5.74000000 0.00000000 -2.87000000 26
|
||||
Fe 7.17500000 1.43500000 -1.43500000 26
|
||||
Fe 5.74000000 0.00000000 0.00000000 26
|
||||
Fe 5.74000000 2.87000000 -5.74000000 26
|
||||
Fe 5.74000000 2.87000000 -2.87000000 26
|
||||
Fe 7.17500000 4.30500000 -1.43500000 26
|
||||
Fe 5.74000000 2.87000000 0.00000000 26
|
||||
Fe 5.74000000 5.74000000 -2.87000000 26
|
||||
Fe 5.74000000 5.74000000 0.00000000 26
|
||||
Fe 8.61000000 0.00000000 0.00000000 26
|
|
@ -0,0 +1,786 @@
|
|||
784
|
||||
Lattice="4.21 0.0 0.0 0.0 4.21 0.0 0.0 0.0 4.21" Properties=species:S:1:pos:R:3:Z:I:1:tags:I:1 pbc="T T T"
|
||||
O -12.63000000 -4.21000000 -4.21000000 8 0
|
||||
Mg -12.63000000 -6.31500000 -4.21000000 12 1
|
||||
Mg -12.63000000 -4.21000000 -6.31500000 12 1
|
||||
O -12.63000000 -6.31500000 -2.10500000 8 0
|
||||
O -12.63000000 -4.21000000 0.00000000 8 0
|
||||
Mg -12.63000000 -6.31500000 0.00000000 12 1
|
||||
Mg -12.63000000 -4.21000000 -2.10500000 12 1
|
||||
O -12.63000000 -2.10500000 -6.31500000 8 0
|
||||
O -12.63000000 0.00000000 -4.21000000 8 0
|
||||
Mg -12.63000000 -2.10500000 -4.21000000 12 1
|
||||
Mg -12.63000000 0.00000000 -6.31500000 12 1
|
||||
O -12.63000000 -2.10500000 -2.10500000 8 0
|
||||
Mg -14.73500000 0.00000000 0.00000000 12 1
|
||||
O -12.63000000 0.00000000 0.00000000 8 0
|
||||
Mg -12.63000000 -2.10500000 0.00000000 12 1
|
||||
Mg -12.63000000 0.00000000 -2.10500000 12 1
|
||||
O -12.63000000 2.10500000 -6.31500000 8 0
|
||||
O -12.63000000 4.21000000 -4.21000000 8 0
|
||||
Mg -12.63000000 2.10500000 -4.21000000 12 1
|
||||
Mg -12.63000000 4.21000000 -6.31500000 12 1
|
||||
O -12.63000000 2.10500000 -2.10500000 8 0
|
||||
O -12.63000000 4.21000000 0.00000000 8 0
|
||||
Mg -12.63000000 2.10500000 0.00000000 12 1
|
||||
Mg -12.63000000 4.21000000 -2.10500000 12 1
|
||||
Mg -12.63000000 6.31500000 -4.21000000 12 1
|
||||
O -12.63000000 6.31500000 -2.10500000 8 0
|
||||
Mg -12.63000000 6.31500000 0.00000000 12 1
|
||||
O -8.42000000 -8.42000000 -8.42000000 8 0
|
||||
Mg -10.52500000 -8.42000000 -4.21000000 12 1
|
||||
O -8.42000000 -8.42000000 -4.21000000 8 0
|
||||
Mg -8.42000000 -10.52500000 -4.21000000 12 1
|
||||
Mg -8.42000000 -8.42000000 -6.31500000 12 1
|
||||
O -8.42000000 -10.52500000 -2.10500000 8 0
|
||||
Mg -10.52500000 -8.42000000 0.00000000 12 1
|
||||
O -8.42000000 -8.42000000 0.00000000 8 0
|
||||
Mg -8.42000000 -10.52500000 0.00000000 12 1
|
||||
Mg -8.42000000 -8.42000000 -2.10500000 12 1
|
||||
O -10.52500000 -8.42000000 -2.10500000 8 0
|
||||
Mg -10.52500000 -4.21000000 -8.42000000 12 1
|
||||
O -8.42000000 -4.21000000 -8.42000000 8 0
|
||||
Mg -8.42000000 -6.31500000 -8.42000000 12 1
|
||||
Mg -8.42000000 -4.21000000 -10.52500000 12 1
|
||||
Mg -10.52500000 -6.31500000 -6.31500000 12 1
|
||||
O -8.42000000 -6.31500000 -6.31500000 8 0
|
||||
Mg -10.52500000 -4.21000000 -4.21000000 12 1
|
||||
O -8.42000000 -4.21000000 -4.21000000 8 0
|
||||
Mg -8.42000000 -6.31500000 -4.21000000 12 1
|
||||
O -10.52500000 -6.31500000 -4.21000000 8 0
|
||||
Mg -8.42000000 -4.21000000 -6.31500000 12 1
|
||||
O -10.52500000 -4.21000000 -6.31500000 8 0
|
||||
Mg -10.52500000 -6.31500000 -2.10500000 12 1
|
||||
O -8.42000000 -6.31500000 -2.10500000 8 0
|
||||
Mg -10.52500000 -4.21000000 0.00000000 12 1
|
||||
O -8.42000000 -4.21000000 0.00000000 8 0
|
||||
Mg -8.42000000 -6.31500000 0.00000000 12 1
|
||||
O -10.52500000 -6.31500000 0.00000000 8 0
|
||||
Mg -8.42000000 -4.21000000 -2.10500000 12 1
|
||||
O -10.52500000 -4.21000000 -2.10500000 8 0
|
||||
O -8.42000000 -2.10500000 -10.52500000 8 0
|
||||
Mg -10.52500000 0.00000000 -8.42000000 12 1
|
||||
O -8.42000000 0.00000000 -8.42000000 8 0
|
||||
Mg -8.42000000 -2.10500000 -8.42000000 12 1
|
||||
O -10.52500000 -2.10500000 -8.42000000 8 0
|
||||
Mg -8.42000000 0.00000000 -10.52500000 12 1
|
||||
Mg -10.52500000 -2.10500000 -6.31500000 12 1
|
||||
O -8.42000000 -2.10500000 -6.31500000 8 0
|
||||
Mg -10.52500000 0.00000000 -4.21000000 12 1
|
||||
O -8.42000000 0.00000000 -4.21000000 8 0
|
||||
Mg -8.42000000 -2.10500000 -4.21000000 12 1
|
||||
O -10.52500000 -2.10500000 -4.21000000 8 0
|
||||
Mg -8.42000000 0.00000000 -6.31500000 12 1
|
||||
O -10.52500000 0.00000000 -6.31500000 8 0
|
||||
Mg -10.52500000 -2.10500000 -2.10500000 12 1
|
||||
O -8.42000000 -2.10500000 -2.10500000 8 0
|
||||
Mg -10.52500000 0.00000000 0.00000000 12 1
|
||||
O -8.42000000 0.00000000 0.00000000 8 0
|
||||
Mg -8.42000000 -2.10500000 0.00000000 12 1
|
||||
O -10.52500000 -2.10500000 0.00000000 8 0
|
||||
Mg -8.42000000 0.00000000 -2.10500000 12 1
|
||||
O -10.52500000 0.00000000 -2.10500000 8 0
|
||||
O -8.42000000 2.10500000 -10.52500000 8 0
|
||||
Mg -10.52500000 4.21000000 -8.42000000 12 1
|
||||
O -8.42000000 4.21000000 -8.42000000 8 0
|
||||
Mg -8.42000000 2.10500000 -8.42000000 12 1
|
||||
O -10.52500000 2.10500000 -8.42000000 8 0
|
||||
Mg -8.42000000 4.21000000 -10.52500000 12 1
|
||||
Mg -10.52500000 2.10500000 -6.31500000 12 1
|
||||
O -8.42000000 2.10500000 -6.31500000 8 0
|
||||
Mg -10.52500000 4.21000000 -4.21000000 12 1
|
||||
O -8.42000000 4.21000000 -4.21000000 8 0
|
||||
Mg -8.42000000 2.10500000 -4.21000000 12 1
|
||||
O -10.52500000 2.10500000 -4.21000000 8 0
|
||||
Mg -8.42000000 4.21000000 -6.31500000 12 1
|
||||
O -10.52500000 4.21000000 -6.31500000 8 0
|
||||
Mg -10.52500000 2.10500000 -2.10500000 12 1
|
||||
O -8.42000000 2.10500000 -2.10500000 8 0
|
||||
Mg -10.52500000 4.21000000 0.00000000 12 1
|
||||
O -8.42000000 4.21000000 0.00000000 8 0
|
||||
Mg -8.42000000 2.10500000 0.00000000 12 1
|
||||
O -10.52500000 2.10500000 0.00000000 8 0
|
||||
Mg -8.42000000 4.21000000 -2.10500000 12 1
|
||||
O -10.52500000 4.21000000 -2.10500000 8 0
|
||||
O -8.42000000 8.42000000 -8.42000000 8 0
|
||||
Mg -8.42000000 6.31500000 -8.42000000 12 1
|
||||
Mg -10.52500000 6.31500000 -6.31500000 12 1
|
||||
O -8.42000000 6.31500000 -6.31500000 8 0
|
||||
Mg -10.52500000 8.42000000 -4.21000000 12 1
|
||||
O -8.42000000 8.42000000 -4.21000000 8 0
|
||||
Mg -8.42000000 6.31500000 -4.21000000 12 1
|
||||
O -10.52500000 6.31500000 -4.21000000 8 0
|
||||
Mg -8.42000000 8.42000000 -6.31500000 12 1
|
||||
Mg -10.52500000 6.31500000 -2.10500000 12 1
|
||||
O -8.42000000 6.31500000 -2.10500000 8 0
|
||||
Mg -10.52500000 8.42000000 0.00000000 12 1
|
||||
O -8.42000000 8.42000000 0.00000000 8 0
|
||||
Mg -8.42000000 6.31500000 0.00000000 12 1
|
||||
O -10.52500000 6.31500000 0.00000000 8 0
|
||||
Mg -8.42000000 8.42000000 -2.10500000 12 1
|
||||
O -10.52500000 8.42000000 -2.10500000 8 0
|
||||
Mg -8.42000000 10.52500000 -4.21000000 12 1
|
||||
O -8.42000000 10.52500000 -2.10500000 8 0
|
||||
Mg -8.42000000 10.52500000 0.00000000 12 1
|
||||
Mg -6.31500000 -12.63000000 -4.21000000 12 1
|
||||
O -4.21000000 -12.63000000 -4.21000000 8 0
|
||||
Mg -4.21000000 -12.63000000 -6.31500000 12 1
|
||||
Mg -6.31500000 -12.63000000 0.00000000 12 1
|
||||
O -4.21000000 -12.63000000 0.00000000 8 0
|
||||
Mg -4.21000000 -12.63000000 -2.10500000 12 1
|
||||
O -6.31500000 -12.63000000 -2.10500000 8 0
|
||||
Mg -6.31500000 -8.42000000 -8.42000000 12 1
|
||||
O -4.21000000 -8.42000000 -8.42000000 8 0
|
||||
Mg -4.21000000 -10.52500000 -8.42000000 12 1
|
||||
Mg -4.21000000 -8.42000000 -10.52500000 12 1
|
||||
Mg -6.31500000 -10.52500000 -6.31500000 12 1
|
||||
O -4.21000000 -10.52500000 -6.31500000 8 0
|
||||
Mg -6.31500000 -8.42000000 -4.21000000 12 1
|
||||
O -4.21000000 -8.42000000 -4.21000000 8 0
|
||||
Mg -4.21000000 -10.52500000 -4.21000000 12 1
|
||||
O -6.31500000 -10.52500000 -4.21000000 8 0
|
||||
Mg -4.21000000 -8.42000000 -6.31500000 12 1
|
||||
O -6.31500000 -8.42000000 -6.31500000 8 0
|
||||
Mg -6.31500000 -10.52500000 -2.10500000 12 1
|
||||
O -4.21000000 -10.52500000 -2.10500000 8 0
|
||||
Mg -6.31500000 -8.42000000 0.00000000 12 1
|
||||
O -4.21000000 -8.42000000 0.00000000 8 0
|
||||
Mg -4.21000000 -10.52500000 0.00000000 12 1
|
||||
O -6.31500000 -10.52500000 0.00000000 8 0
|
||||
Mg -4.21000000 -8.42000000 -2.10500000 12 1
|
||||
O -6.31500000 -8.42000000 -2.10500000 8 0
|
||||
Mg -6.31500000 -4.21000000 -12.63000000 12 1
|
||||
O -4.21000000 -4.21000000 -12.63000000 8 0
|
||||
Mg -4.21000000 -6.31500000 -12.63000000 12 1
|
||||
Mg -6.31500000 -6.31500000 -10.52500000 12 1
|
||||
O -4.21000000 -6.31500000 -10.52500000 8 0
|
||||
Mg -6.31500000 -4.21000000 -8.42000000 12 1
|
||||
O -4.21000000 -4.21000000 -8.42000000 8 0
|
||||
Mg -4.21000000 -6.31500000 -8.42000000 12 1
|
||||
O -6.31500000 -6.31500000 -8.42000000 8 0
|
||||
Mg -4.21000000 -4.21000000 -10.52500000 12 1
|
||||
O -6.31500000 -4.21000000 -10.52500000 8 0
|
||||
Mg -6.31500000 -6.31500000 -6.31500000 12 1
|
||||
O -4.21000000 -6.31500000 -6.31500000 8 0
|
||||
Mg -6.31500000 -4.21000000 -4.21000000 12 1
|
||||
O -4.21000000 -4.21000000 -4.21000000 8 0
|
||||
Mg -4.21000000 -6.31500000 -4.21000000 12 1
|
||||
O -6.31500000 -6.31500000 -4.21000000 8 0
|
||||
Mg -4.21000000 -4.21000000 -6.31500000 12 1
|
||||
O -6.31500000 -4.21000000 -6.31500000 8 0
|
||||
Mg -6.31500000 -6.31500000 -2.10500000 12 1
|
||||
O -4.21000000 -6.31500000 -2.10500000 8 0
|
||||
Mg -6.31500000 -4.21000000 0.00000000 12 1
|
||||
O -4.21000000 -4.21000000 0.00000000 8 0
|
||||
Mg -4.21000000 -6.31500000 0.00000000 12 1
|
||||
O -6.31500000 -6.31500000 0.00000000 8 0
|
||||
Mg -4.21000000 -4.21000000 -2.10500000 12 1
|
||||
O -6.31500000 -4.21000000 -2.10500000 8 0
|
||||
Mg -6.31500000 0.00000000 -12.63000000 12 1
|
||||
O -4.21000000 0.00000000 -12.63000000 8 0
|
||||
Mg -4.21000000 -2.10500000 -12.63000000 12 1
|
||||
O -6.31500000 -2.10500000 -12.63000000 8 0
|
||||
Mg -6.31500000 -2.10500000 -10.52500000 12 1
|
||||
O -4.21000000 -2.10500000 -10.52500000 8 0
|
||||
Mg -6.31500000 0.00000000 -8.42000000 12 1
|
||||
O -4.21000000 0.00000000 -8.42000000 8 0
|
||||
Mg -4.21000000 -2.10500000 -8.42000000 12 1
|
||||
O -6.31500000 -2.10500000 -8.42000000 8 0
|
||||
Mg -4.21000000 0.00000000 -10.52500000 12 1
|
||||
O -6.31500000 0.00000000 -10.52500000 8 0
|
||||
Mg -6.31500000 -2.10500000 -6.31500000 12 1
|
||||
O -4.21000000 -2.10500000 -6.31500000 8 0
|
||||
Mg -6.31500000 0.00000000 -4.21000000 12 1
|
||||
O -4.21000000 0.00000000 -4.21000000 8 0
|
||||
Mg -4.21000000 -2.10500000 -4.21000000 12 1
|
||||
O -6.31500000 -2.10500000 -4.21000000 8 0
|
||||
Mg -4.21000000 0.00000000 -6.31500000 12 1
|
||||
O -6.31500000 0.00000000 -6.31500000 8 0
|
||||
Mg -6.31500000 -2.10500000 -2.10500000 12 1
|
||||
O -4.21000000 -2.10500000 -2.10500000 8 0
|
||||
Mg -6.31500000 0.00000000 0.00000000 12 1
|
||||
O -4.21000000 0.00000000 0.00000000 8 0
|
||||
Mg -4.21000000 -2.10500000 0.00000000 12 1
|
||||
O -6.31500000 -2.10500000 0.00000000 8 0
|
||||
Mg -4.21000000 0.00000000 -2.10500000 12 1
|
||||
O -6.31500000 0.00000000 -2.10500000 8 0
|
||||
Mg -6.31500000 4.21000000 -12.63000000 12 1
|
||||
O -4.21000000 4.21000000 -12.63000000 8 0
|
||||
Mg -4.21000000 2.10500000 -12.63000000 12 1
|
||||
O -6.31500000 2.10500000 -12.63000000 8 0
|
||||
Mg -6.31500000 2.10500000 -10.52500000 12 1
|
||||
O -4.21000000 2.10500000 -10.52500000 8 0
|
||||
Mg -6.31500000 4.21000000 -8.42000000 12 1
|
||||
O -4.21000000 4.21000000 -8.42000000 8 0
|
||||
Mg -4.21000000 2.10500000 -8.42000000 12 1
|
||||
O -6.31500000 2.10500000 -8.42000000 8 0
|
||||
Mg -4.21000000 4.21000000 -10.52500000 12 1
|
||||
O -6.31500000 4.21000000 -10.52500000 8 0
|
||||
Mg -6.31500000 2.10500000 -6.31500000 12 1
|
||||
O -4.21000000 2.10500000 -6.31500000 8 0
|
||||
Mg -6.31500000 4.21000000 -4.21000000 12 1
|
||||
O -4.21000000 4.21000000 -4.21000000 8 0
|
||||
Mg -4.21000000 2.10500000 -4.21000000 12 1
|
||||
O -6.31500000 2.10500000 -4.21000000 8 0
|
||||
Mg -4.21000000 4.21000000 -6.31500000 12 1
|
||||
O -6.31500000 4.21000000 -6.31500000 8 0
|
||||
Mg -6.31500000 2.10500000 -2.10500000 12 1
|
||||
O -4.21000000 2.10500000 -2.10500000 8 0
|
||||
Mg -6.31500000 4.21000000 0.00000000 12 1
|
||||
O -4.21000000 4.21000000 0.00000000 8 0
|
||||
Mg -4.21000000 2.10500000 0.00000000 12 1
|
||||
O -6.31500000 2.10500000 0.00000000 8 0
|
||||
Mg -4.21000000 4.21000000 -2.10500000 12 1
|
||||
O -6.31500000 4.21000000 -2.10500000 8 0
|
||||
Mg -4.21000000 6.31500000 -12.63000000 12 1
|
||||
Mg -6.31500000 6.31500000 -10.52500000 12 1
|
||||
O -4.21000000 6.31500000 -10.52500000 8 0
|
||||
Mg -6.31500000 8.42000000 -8.42000000 12 1
|
||||
O -4.21000000 8.42000000 -8.42000000 8 0
|
||||
Mg -4.21000000 6.31500000 -8.42000000 12 1
|
||||
O -6.31500000 6.31500000 -8.42000000 8 0
|
||||
Mg -4.21000000 8.42000000 -10.52500000 12 1
|
||||
Mg -6.31500000 6.31500000 -6.31500000 12 1
|
||||
O -4.21000000 6.31500000 -6.31500000 8 0
|
||||
Mg -6.31500000 8.42000000 -4.21000000 12 1
|
||||
O -4.21000000 8.42000000 -4.21000000 8 0
|
||||
Mg -4.21000000 6.31500000 -4.21000000 12 1
|
||||
O -6.31500000 6.31500000 -4.21000000 8 0
|
||||
Mg -4.21000000 8.42000000 -6.31500000 12 1
|
||||
O -6.31500000 8.42000000 -6.31500000 8 0
|
||||
Mg -6.31500000 6.31500000 -2.10500000 12 1
|
||||
O -4.21000000 6.31500000 -2.10500000 8 0
|
||||
Mg -6.31500000 8.42000000 0.00000000 12 1
|
||||
O -4.21000000 8.42000000 0.00000000 8 0
|
||||
Mg -4.21000000 6.31500000 0.00000000 12 1
|
||||
O -6.31500000 6.31500000 0.00000000 8 0
|
||||
Mg -4.21000000 8.42000000 -2.10500000 12 1
|
||||
O -6.31500000 8.42000000 -2.10500000 8 0
|
||||
Mg -4.21000000 10.52500000 -8.42000000 12 1
|
||||
Mg -6.31500000 10.52500000 -6.31500000 12 1
|
||||
O -4.21000000 10.52500000 -6.31500000 8 0
|
||||
Mg -6.31500000 12.63000000 -4.21000000 12 1
|
||||
O -4.21000000 12.63000000 -4.21000000 8 0
|
||||
Mg -4.21000000 10.52500000 -4.21000000 12 1
|
||||
O -6.31500000 10.52500000 -4.21000000 8 0
|
||||
Mg -4.21000000 12.63000000 -6.31500000 12 1
|
||||
Mg -6.31500000 10.52500000 -2.10500000 12 1
|
||||
O -4.21000000 10.52500000 -2.10500000 8 0
|
||||
Mg -6.31500000 12.63000000 0.00000000 12 1
|
||||
O -4.21000000 12.63000000 0.00000000 8 0
|
||||
Mg -4.21000000 10.52500000 0.00000000 12 1
|
||||
O -6.31500000 10.52500000 0.00000000 8 0
|
||||
Mg -4.21000000 12.63000000 -2.10500000 12 1
|
||||
O -6.31500000 12.63000000 -2.10500000 8 0
|
||||
Mg -2.10500000 -12.63000000 -4.21000000 12 1
|
||||
O 0.00000000 -12.63000000 -4.21000000 8 0
|
||||
Mg 0.00000000 -12.63000000 -6.31500000 12 1
|
||||
O -2.10500000 -12.63000000 -6.31500000 8 0
|
||||
Mg -2.10500000 -12.63000000 0.00000000 12 1
|
||||
O 0.00000000 -12.63000000 0.00000000 8 0
|
||||
Mg 0.00000000 -14.73500000 0.00000000 12 1
|
||||
Mg 0.00000000 -12.63000000 -2.10500000 12 1
|
||||
O -2.10500000 -12.63000000 -2.10500000 8 0
|
||||
Mg -2.10500000 -8.42000000 -8.42000000 12 1
|
||||
O 0.00000000 -8.42000000 -8.42000000 8 0
|
||||
Mg 0.00000000 -10.52500000 -8.42000000 12 1
|
||||
O -2.10500000 -10.52500000 -8.42000000 8 0
|
||||
Mg 0.00000000 -8.42000000 -10.52500000 12 1
|
||||
O -2.10500000 -8.42000000 -10.52500000 8 0
|
||||
Mg -2.10500000 -10.52500000 -6.31500000 12 1
|
||||
O 0.00000000 -10.52500000 -6.31500000 8 0
|
||||
Mg -2.10500000 -8.42000000 -4.21000000 12 1
|
||||
O 0.00000000 -8.42000000 -4.21000000 8 0
|
||||
Mg 0.00000000 -10.52500000 -4.21000000 12 1
|
||||
O -2.10500000 -10.52500000 -4.21000000 8 0
|
||||
Mg 0.00000000 -8.42000000 -6.31500000 12 1
|
||||
O -2.10500000 -8.42000000 -6.31500000 8 0
|
||||
Mg -2.10500000 -10.52500000 -2.10500000 12 1
|
||||
O 0.00000000 -10.52500000 -2.10500000 8 0
|
||||
Mg -2.10500000 -8.42000000 0.00000000 12 1
|
||||
O 0.00000000 -8.42000000 0.00000000 8 0
|
||||
Mg 0.00000000 -10.52500000 0.00000000 12 1
|
||||
O -2.10500000 -10.52500000 0.00000000 8 0
|
||||
Mg 0.00000000 -8.42000000 -2.10500000 12 1
|
||||
O -2.10500000 -8.42000000 -2.10500000 8 0
|
||||
Mg -2.10500000 -4.21000000 -12.63000000 12 1
|
||||
O 0.00000000 -4.21000000 -12.63000000 8 0
|
||||
Mg 0.00000000 -6.31500000 -12.63000000 12 1
|
||||
O -2.10500000 -6.31500000 -12.63000000 8 0
|
||||
Mg -2.10500000 -6.31500000 -10.52500000 12 1
|
||||
O 0.00000000 -6.31500000 -10.52500000 8 0
|
||||
Mg -2.10500000 -4.21000000 -8.42000000 12 1
|
||||
O 0.00000000 -4.21000000 -8.42000000 8 0
|
||||
Mg 0.00000000 -6.31500000 -8.42000000 12 1
|
||||
O -2.10500000 -6.31500000 -8.42000000 8 0
|
||||
Mg 0.00000000 -4.21000000 -10.52500000 12 1
|
||||
O -2.10500000 -4.21000000 -10.52500000 8 0
|
||||
Mg -2.10500000 -6.31500000 -6.31500000 12 1
|
||||
O 0.00000000 -6.31500000 -6.31500000 8 0
|
||||
Mg -2.10500000 -4.21000000 -4.21000000 12 1
|
||||
O 0.00000000 -4.21000000 -4.21000000 8 0
|
||||
Mg 0.00000000 -6.31500000 -4.21000000 12 1
|
||||
O -2.10500000 -6.31500000 -4.21000000 8 0
|
||||
Mg 0.00000000 -4.21000000 -6.31500000 12 1
|
||||
O -2.10500000 -4.21000000 -6.31500000 8 0
|
||||
Mg -2.10500000 -6.31500000 -2.10500000 12 1
|
||||
O 0.00000000 -6.31500000 -2.10500000 8 0
|
||||
Mg -2.10500000 -4.21000000 0.00000000 12 1
|
||||
O 0.00000000 -4.21000000 0.00000000 8 0
|
||||
Mg 0.00000000 -6.31500000 0.00000000 12 1
|
||||
O -2.10500000 -6.31500000 0.00000000 8 0
|
||||
Mg 0.00000000 -4.21000000 -2.10500000 12 1
|
||||
O -2.10500000 -4.21000000 -2.10500000 8 0
|
||||
Mg -2.10500000 0.00000000 -12.63000000 12 1
|
||||
O 0.00000000 0.00000000 -12.63000000 8 0
|
||||
Mg 0.00000000 -2.10500000 -12.63000000 12 1
|
||||
O -2.10500000 -2.10500000 -12.63000000 8 0
|
||||
Mg 0.00000000 0.00000000 -14.73500000 12 1
|
||||
Mg -2.10500000 -2.10500000 -10.52500000 12 1
|
||||
O 0.00000000 -2.10500000 -10.52500000 8 0
|
||||
Mg -2.10500000 0.00000000 -8.42000000 12 1
|
||||
O 0.00000000 0.00000000 -8.42000000 8 0
|
||||
Mg 0.00000000 -2.10500000 -8.42000000 12 1
|
||||
O -2.10500000 -2.10500000 -8.42000000 8 0
|
||||
Mg 0.00000000 0.00000000 -10.52500000 12 1
|
||||
O -2.10500000 0.00000000 -10.52500000 8 0
|
||||
Mg -2.10500000 -2.10500000 -6.31500000 12 1
|
||||
O 0.00000000 -2.10500000 -6.31500000 8 0
|
||||
Mg -2.10500000 0.00000000 -4.21000000 12 1
|
||||
O 0.00000000 0.00000000 -4.21000000 8 0
|
||||
Mg 0.00000000 -2.10500000 -4.21000000 12 1
|
||||
O -2.10500000 -2.10500000 -4.21000000 8 0
|
||||
Mg 0.00000000 0.00000000 -6.31500000 12 1
|
||||
O -2.10500000 0.00000000 -6.31500000 8 0
|
||||
Mg -2.10500000 -2.10500000 -2.10500000 12 1
|
||||
O 0.00000000 -2.10500000 -2.10500000 8 0
|
||||
Mg -2.10500000 0.00000000 0.00000000 12 1
|
||||
O 0.00000000 0.00000000 0.00000000 8 0
|
||||
Mg 0.00000000 -2.10500000 0.00000000 12 1
|
||||
O -2.10500000 -2.10500000 0.00000000 8 0
|
||||
Mg 0.00000000 0.00000000 -2.10500000 12 1
|
||||
O -2.10500000 0.00000000 -2.10500000 8 0
|
||||
Mg -2.10500000 4.21000000 -12.63000000 12 1
|
||||
O 0.00000000 4.21000000 -12.63000000 8 0
|
||||
Mg 0.00000000 2.10500000 -12.63000000 12 1
|
||||
O -2.10500000 2.10500000 -12.63000000 8 0
|
||||
Mg -2.10500000 2.10500000 -10.52500000 12 1
|
||||
O 0.00000000 2.10500000 -10.52500000 8 0
|
||||
Mg -2.10500000 4.21000000 -8.42000000 12 1
|
||||
O 0.00000000 4.21000000 -8.42000000 8 0
|
||||
Mg 0.00000000 2.10500000 -8.42000000 12 1
|
||||
O -2.10500000 2.10500000 -8.42000000 8 0
|
||||
Mg 0.00000000 4.21000000 -10.52500000 12 1
|
||||
O -2.10500000 4.21000000 -10.52500000 8 0
|
||||
Mg -2.10500000 2.10500000 -6.31500000 12 1
|
||||
O 0.00000000 2.10500000 -6.31500000 8 0
|
||||
Mg -2.10500000 4.21000000 -4.21000000 12 1
|
||||
O 0.00000000 4.21000000 -4.21000000 8 0
|
||||
Mg 0.00000000 2.10500000 -4.21000000 12 1
|
||||
O -2.10500000 2.10500000 -4.21000000 8 0
|
||||
Mg 0.00000000 4.21000000 -6.31500000 12 1
|
||||
O -2.10500000 4.21000000 -6.31500000 8 0
|
||||
Mg -2.10500000 2.10500000 -2.10500000 12 1
|
||||
O 0.00000000 2.10500000 -2.10500000 8 0
|
||||
Mg -2.10500000 4.21000000 0.00000000 12 1
|
||||
O 0.00000000 4.21000000 0.00000000 8 0
|
||||
Mg 0.00000000 2.10500000 0.00000000 12 1
|
||||
O -2.10500000 2.10500000 0.00000000 8 0
|
||||
Mg 0.00000000 4.21000000 -2.10500000 12 1
|
||||
O -2.10500000 4.21000000 -2.10500000 8 0
|
||||
Mg 0.00000000 6.31500000 -12.63000000 12 1
|
||||
O -2.10500000 6.31500000 -12.63000000 8 0
|
||||
Mg -2.10500000 6.31500000 -10.52500000 12 1
|
||||
O 0.00000000 6.31500000 -10.52500000 8 0
|
||||
Mg -2.10500000 8.42000000 -8.42000000 12 1
|
||||
O 0.00000000 8.42000000 -8.42000000 8 0
|
||||
Mg 0.00000000 6.31500000 -8.42000000 12 1
|
||||
O -2.10500000 6.31500000 -8.42000000 8 0
|
||||
Mg 0.00000000 8.42000000 -10.52500000 12 1
|
||||
O -2.10500000 8.42000000 -10.52500000 8 0
|
||||
Mg -2.10500000 6.31500000 -6.31500000 12 1
|
||||
O 0.00000000 6.31500000 -6.31500000 8 0
|
||||
Mg -2.10500000 8.42000000 -4.21000000 12 1
|
||||
O 0.00000000 8.42000000 -4.21000000 8 0
|
||||
Mg 0.00000000 6.31500000 -4.21000000 12 1
|
||||
O -2.10500000 6.31500000 -4.21000000 8 0
|
||||
Mg 0.00000000 8.42000000 -6.31500000 12 1
|
||||
O -2.10500000 8.42000000 -6.31500000 8 0
|
||||
Mg -2.10500000 6.31500000 -2.10500000 12 1
|
||||
O 0.00000000 6.31500000 -2.10500000 8 0
|
||||
Mg -2.10500000 8.42000000 0.00000000 12 1
|
||||
O 0.00000000 8.42000000 0.00000000 8 0
|
||||
Mg 0.00000000 6.31500000 0.00000000 12 1
|
||||
O -2.10500000 6.31500000 0.00000000 8 0
|
||||
Mg 0.00000000 8.42000000 -2.10500000 12 1
|
||||
O -2.10500000 8.42000000 -2.10500000 8 0
|
||||
Mg 0.00000000 10.52500000 -8.42000000 12 1
|
||||
O -2.10500000 10.52500000 -8.42000000 8 0
|
||||
Mg -2.10500000 10.52500000 -6.31500000 12 1
|
||||
O 0.00000000 10.52500000 -6.31500000 8 0
|
||||
Mg -2.10500000 12.63000000 -4.21000000 12 1
|
||||
O 0.00000000 12.63000000 -4.21000000 8 0
|
||||
Mg 0.00000000 10.52500000 -4.21000000 12 1
|
||||
O -2.10500000 10.52500000 -4.21000000 8 0
|
||||
Mg 0.00000000 12.63000000 -6.31500000 12 1
|
||||
O -2.10500000 12.63000000 -6.31500000 8 0
|
||||
Mg -2.10500000 10.52500000 -2.10500000 12 1
|
||||
O 0.00000000 10.52500000 -2.10500000 8 0
|
||||
Mg -2.10500000 12.63000000 0.00000000 12 1
|
||||
O 0.00000000 12.63000000 0.00000000 8 0
|
||||
Mg 0.00000000 10.52500000 0.00000000 12 1
|
||||
O -2.10500000 10.52500000 0.00000000 8 0
|
||||
Mg 0.00000000 12.63000000 -2.10500000 12 1
|
||||
O -2.10500000 12.63000000 -2.10500000 8 0
|
||||
Mg 0.00000000 14.73500000 0.00000000 12 1
|
||||
Mg 2.10500000 -12.63000000 -4.21000000 12 1
|
||||
O 4.21000000 -12.63000000 -4.21000000 8 0
|
||||
Mg 4.21000000 -12.63000000 -6.31500000 12 1
|
||||
O 2.10500000 -12.63000000 -6.31500000 8 0
|
||||
Mg 2.10500000 -12.63000000 0.00000000 12 1
|
||||
O 4.21000000 -12.63000000 0.00000000 8 0
|
||||
Mg 4.21000000 -12.63000000 -2.10500000 12 1
|
||||
O 2.10500000 -12.63000000 -2.10500000 8 0
|
||||
Mg 2.10500000 -8.42000000 -8.42000000 12 1
|
||||
O 4.21000000 -8.42000000 -8.42000000 8 0
|
||||
Mg 4.21000000 -10.52500000 -8.42000000 12 1
|
||||
O 2.10500000 -10.52500000 -8.42000000 8 0
|
||||
Mg 4.21000000 -8.42000000 -10.52500000 12 1
|
||||
O 2.10500000 -8.42000000 -10.52500000 8 0
|
||||
Mg 2.10500000 -10.52500000 -6.31500000 12 1
|
||||
O 4.21000000 -10.52500000 -6.31500000 8 0
|
||||
Mg 2.10500000 -8.42000000 -4.21000000 12 1
|
||||
O 4.21000000 -8.42000000 -4.21000000 8 0
|
||||
Mg 4.21000000 -10.52500000 -4.21000000 12 1
|
||||
O 2.10500000 -10.52500000 -4.21000000 8 0
|
||||
Mg 4.21000000 -8.42000000 -6.31500000 12 1
|
||||
O 2.10500000 -8.42000000 -6.31500000 8 0
|
||||
Mg 2.10500000 -10.52500000 -2.10500000 12 1
|
||||
O 4.21000000 -10.52500000 -2.10500000 8 0
|
||||
Mg 2.10500000 -8.42000000 0.00000000 12 1
|
||||
O 4.21000000 -8.42000000 0.00000000 8 0
|
||||
Mg 4.21000000 -10.52500000 0.00000000 12 1
|
||||
O 2.10500000 -10.52500000 0.00000000 8 0
|
||||
Mg 4.21000000 -8.42000000 -2.10500000 12 1
|
||||
O 2.10500000 -8.42000000 -2.10500000 8 0
|
||||
Mg 2.10500000 -4.21000000 -12.63000000 12 1
|
||||
O 4.21000000 -4.21000000 -12.63000000 8 0
|
||||
Mg 4.21000000 -6.31500000 -12.63000000 12 1
|
||||
O 2.10500000 -6.31500000 -12.63000000 8 0
|
||||
Mg 2.10500000 -6.31500000 -10.52500000 12 1
|
||||
O 4.21000000 -6.31500000 -10.52500000 8 0
|
||||
Mg 2.10500000 -4.21000000 -8.42000000 12 1
|
||||
O 4.21000000 -4.21000000 -8.42000000 8 0
|
||||
Mg 4.21000000 -6.31500000 -8.42000000 12 1
|
||||
O 2.10500000 -6.31500000 -8.42000000 8 0
|
||||
Mg 4.21000000 -4.21000000 -10.52500000 12 1
|
||||
O 2.10500000 -4.21000000 -10.52500000 8 0
|
||||
Mg 2.10500000 -6.31500000 -6.31500000 12 1
|
||||
O 4.21000000 -6.31500000 -6.31500000 8 0
|
||||
Mg 2.10500000 -4.21000000 -4.21000000 12 1
|
||||
O 4.21000000 -4.21000000 -4.21000000 8 0
|
||||
Mg 4.21000000 -6.31500000 -4.21000000 12 1
|
||||
O 2.10500000 -6.31500000 -4.21000000 8 0
|
||||
Mg 4.21000000 -4.21000000 -6.31500000 12 1
|
||||
O 2.10500000 -4.21000000 -6.31500000 8 0
|
||||
Mg 2.10500000 -6.31500000 -2.10500000 12 1
|
||||
O 4.21000000 -6.31500000 -2.10500000 8 0
|
||||
Mg 2.10500000 -4.21000000 0.00000000 12 1
|
||||
O 4.21000000 -4.21000000 0.00000000 8 0
|
||||
Mg 4.21000000 -6.31500000 0.00000000 12 1
|
||||
O 2.10500000 -6.31500000 0.00000000 8 0
|
||||
Mg 4.21000000 -4.21000000 -2.10500000 12 1
|
||||
O 2.10500000 -4.21000000 -2.10500000 8 0
|
||||
Mg 2.10500000 0.00000000 -12.63000000 12 1
|
||||
O 4.21000000 0.00000000 -12.63000000 8 0
|
||||
Mg 4.21000000 -2.10500000 -12.63000000 12 1
|
||||
O 2.10500000 -2.10500000 -12.63000000 8 0
|
||||
Mg 2.10500000 -2.10500000 -10.52500000 12 1
|
||||
O 4.21000000 -2.10500000 -10.52500000 8 0
|
||||
Mg 2.10500000 0.00000000 -8.42000000 12 1
|
||||
O 4.21000000 0.00000000 -8.42000000 8 0
|
||||
Mg 4.21000000 -2.10500000 -8.42000000 12 1
|
||||
O 2.10500000 -2.10500000 -8.42000000 8 0
|
||||
Mg 4.21000000 0.00000000 -10.52500000 12 1
|
||||
O 2.10500000 0.00000000 -10.52500000 8 0
|
||||
Mg 2.10500000 -2.10500000 -6.31500000 12 1
|
||||
O 4.21000000 -2.10500000 -6.31500000 8 0
|
||||
Mg 2.10500000 0.00000000 -4.21000000 12 1
|
||||
O 4.21000000 0.00000000 -4.21000000 8 0
|
||||
Mg 4.21000000 -2.10500000 -4.21000000 12 1
|
||||
O 2.10500000 -2.10500000 -4.21000000 8 0
|
||||
Mg 4.21000000 0.00000000 -6.31500000 12 1
|
||||
O 2.10500000 0.00000000 -6.31500000 8 0
|
||||
Mg 2.10500000 -2.10500000 -2.10500000 12 1
|
||||
O 4.21000000 -2.10500000 -2.10500000 8 0
|
||||
Mg 2.10500000 0.00000000 0.00000000 12 1
|
||||
O 4.21000000 0.00000000 0.00000000 8 0
|
||||
Mg 4.21000000 -2.10500000 0.00000000 12 1
|
||||
O 2.10500000 -2.10500000 0.00000000 8 0
|
||||
Mg 4.21000000 0.00000000 -2.10500000 12 1
|
||||
O 2.10500000 0.00000000 -2.10500000 8 0
|
||||
Mg 2.10500000 4.21000000 -12.63000000 12 1
|
||||
O 4.21000000 4.21000000 -12.63000000 8 0
|
||||
Mg 4.21000000 2.10500000 -12.63000000 12 1
|
||||
O 2.10500000 2.10500000 -12.63000000 8 0
|
||||
Mg 2.10500000 2.10500000 -10.52500000 12 1
|
||||
O 4.21000000 2.10500000 -10.52500000 8 0
|
||||
Mg 2.10500000 4.21000000 -8.42000000 12 1
|
||||
O 4.21000000 4.21000000 -8.42000000 8 0
|
||||
Mg 4.21000000 2.10500000 -8.42000000 12 1
|
||||
O 2.10500000 2.10500000 -8.42000000 8 0
|
||||
Mg 4.21000000 4.21000000 -10.52500000 12 1
|
||||
O 2.10500000 4.21000000 -10.52500000 8 0
|
||||
Mg 2.10500000 2.10500000 -6.31500000 12 1
|
||||
O 4.21000000 2.10500000 -6.31500000 8 0
|
||||
Mg 2.10500000 4.21000000 -4.21000000 12 1
|
||||
O 4.21000000 4.21000000 -4.21000000 8 0
|
||||
Mg 4.21000000 2.10500000 -4.21000000 12 1
|
||||
O 2.10500000 2.10500000 -4.21000000 8 0
|
||||
Mg 4.21000000 4.21000000 -6.31500000 12 1
|
||||
O 2.10500000 4.21000000 -6.31500000 8 0
|
||||
Mg 2.10500000 2.10500000 -2.10500000 12 1
|
||||
O 4.21000000 2.10500000 -2.10500000 8 0
|
||||
Mg 2.10500000 4.21000000 0.00000000 12 1
|
||||
O 4.21000000 4.21000000 0.00000000 8 0
|
||||
Mg 4.21000000 2.10500000 0.00000000 12 1
|
||||
O 2.10500000 2.10500000 0.00000000 8 0
|
||||
Mg 4.21000000 4.21000000 -2.10500000 12 1
|
||||
O 2.10500000 4.21000000 -2.10500000 8 0
|
||||
Mg 4.21000000 6.31500000 -12.63000000 12 1
|
||||
O 2.10500000 6.31500000 -12.63000000 8 0
|
||||
Mg 2.10500000 6.31500000 -10.52500000 12 1
|
||||
O 4.21000000 6.31500000 -10.52500000 8 0
|
||||
Mg 2.10500000 8.42000000 -8.42000000 12 1
|
||||
O 4.21000000 8.42000000 -8.42000000 8 0
|
||||
Mg 4.21000000 6.31500000 -8.42000000 12 1
|
||||
O 2.10500000 6.31500000 -8.42000000 8 0
|
||||
Mg 4.21000000 8.42000000 -10.52500000 12 1
|
||||
O 2.10500000 8.42000000 -10.52500000 8 0
|
||||
Mg 2.10500000 6.31500000 -6.31500000 12 1
|
||||
O 4.21000000 6.31500000 -6.31500000 8 0
|
||||
Mg 2.10500000 8.42000000 -4.21000000 12 1
|
||||
O 4.21000000 8.42000000 -4.21000000 8 0
|
||||
Mg 4.21000000 6.31500000 -4.21000000 12 1
|
||||
O 2.10500000 6.31500000 -4.21000000 8 0
|
||||
Mg 4.21000000 8.42000000 -6.31500000 12 1
|
||||
O 2.10500000 8.42000000 -6.31500000 8 0
|
||||
Mg 2.10500000 6.31500000 -2.10500000 12 1
|
||||
O 4.21000000 6.31500000 -2.10500000 8 0
|
||||
Mg 2.10500000 8.42000000 0.00000000 12 1
|
||||
O 4.21000000 8.42000000 0.00000000 8 0
|
||||
Mg 4.21000000 6.31500000 0.00000000 12 1
|
||||
O 2.10500000 6.31500000 0.00000000 8 0
|
||||
Mg 4.21000000 8.42000000 -2.10500000 12 1
|
||||
O 2.10500000 8.42000000 -2.10500000 8 0
|
||||
Mg 4.21000000 10.52500000 -8.42000000 12 1
|
||||
O 2.10500000 10.52500000 -8.42000000 8 0
|
||||
Mg 2.10500000 10.52500000 -6.31500000 12 1
|
||||
O 4.21000000 10.52500000 -6.31500000 8 0
|
||||
Mg 2.10500000 12.63000000 -4.21000000 12 1
|
||||
O 4.21000000 12.63000000 -4.21000000 8 0
|
||||
Mg 4.21000000 10.52500000 -4.21000000 12 1
|
||||
O 2.10500000 10.52500000 -4.21000000 8 0
|
||||
Mg 4.21000000 12.63000000 -6.31500000 12 1
|
||||
O 2.10500000 12.63000000 -6.31500000 8 0
|
||||
Mg 2.10500000 10.52500000 -2.10500000 12 1
|
||||
O 4.21000000 10.52500000 -2.10500000 8 0
|
||||
Mg 2.10500000 12.63000000 0.00000000 12 1
|
||||
O 4.21000000 12.63000000 0.00000000 8 0
|
||||
Mg 4.21000000 10.52500000 0.00000000 12 1
|
||||
O 2.10500000 10.52500000 0.00000000 8 0
|
||||
Mg 4.21000000 12.63000000 -2.10500000 12 1
|
||||
O 2.10500000 12.63000000 -2.10500000 8 0
|
||||
Mg 6.31500000 -12.63000000 -4.21000000 12 1
|
||||
Mg 6.31500000 -12.63000000 0.00000000 12 1
|
||||
O 6.31500000 -12.63000000 -2.10500000 8 0
|
||||
Mg 6.31500000 -8.42000000 -8.42000000 12 1
|
||||
O 8.42000000 -8.42000000 -8.42000000 8 0
|
||||
Mg 6.31500000 -10.52500000 -6.31500000 12 1
|
||||
Mg 6.31500000 -8.42000000 -4.21000000 12 1
|
||||
O 8.42000000 -8.42000000 -4.21000000 8 0
|
||||
Mg 8.42000000 -10.52500000 -4.21000000 12 1
|
||||
O 6.31500000 -10.52500000 -4.21000000 8 0
|
||||
Mg 8.42000000 -8.42000000 -6.31500000 12 1
|
||||
O 6.31500000 -8.42000000 -6.31500000 8 0
|
||||
Mg 6.31500000 -10.52500000 -2.10500000 12 1
|
||||
O 8.42000000 -10.52500000 -2.10500000 8 0
|
||||
Mg 6.31500000 -8.42000000 0.00000000 12 1
|
||||
O 8.42000000 -8.42000000 0.00000000 8 0
|
||||
Mg 8.42000000 -10.52500000 0.00000000 12 1
|
||||
O 6.31500000 -10.52500000 0.00000000 8 0
|
||||
Mg 8.42000000 -8.42000000 -2.10500000 12 1
|
||||
O 6.31500000 -8.42000000 -2.10500000 8 0
|
||||
Mg 6.31500000 -4.21000000 -12.63000000 12 1
|
||||
Mg 6.31500000 -6.31500000 -10.52500000 12 1
|
||||
Mg 6.31500000 -4.21000000 -8.42000000 12 1
|
||||
O 8.42000000 -4.21000000 -8.42000000 8 0
|
||||
Mg 8.42000000 -6.31500000 -8.42000000 12 1
|
||||
O 6.31500000 -6.31500000 -8.42000000 8 0
|
||||
Mg 8.42000000 -4.21000000 -10.52500000 12 1
|
||||
O 6.31500000 -4.21000000 -10.52500000 8 0
|
||||
Mg 6.31500000 -6.31500000 -6.31500000 12 1
|
||||
O 8.42000000 -6.31500000 -6.31500000 8 0
|
||||
Mg 6.31500000 -4.21000000 -4.21000000 12 1
|
||||
O 8.42000000 -4.21000000 -4.21000000 8 0
|
||||
Mg 8.42000000 -6.31500000 -4.21000000 12 1
|
||||
O 6.31500000 -6.31500000 -4.21000000 8 0
|
||||
Mg 8.42000000 -4.21000000 -6.31500000 12 1
|
||||
O 6.31500000 -4.21000000 -6.31500000 8 0
|
||||
Mg 6.31500000 -6.31500000 -2.10500000 12 1
|
||||
O 8.42000000 -6.31500000 -2.10500000 8 0
|
||||
Mg 6.31500000 -4.21000000 0.00000000 12 1
|
||||
O 8.42000000 -4.21000000 0.00000000 8 0
|
||||
Mg 8.42000000 -6.31500000 0.00000000 12 1
|
||||
O 6.31500000 -6.31500000 0.00000000 8 0
|
||||
Mg 8.42000000 -4.21000000 -2.10500000 12 1
|
||||
O 6.31500000 -4.21000000 -2.10500000 8 0
|
||||
Mg 6.31500000 0.00000000 -12.63000000 12 1
|
||||
O 6.31500000 -2.10500000 -12.63000000 8 0
|
||||
Mg 6.31500000 -2.10500000 -10.52500000 12 1
|
||||
O 8.42000000 -2.10500000 -10.52500000 8 0
|
||||
Mg 6.31500000 0.00000000 -8.42000000 12 1
|
||||
O 8.42000000 0.00000000 -8.42000000 8 0
|
||||
Mg 8.42000000 -2.10500000 -8.42000000 12 1
|
||||
O 6.31500000 -2.10500000 -8.42000000 8 0
|
||||
Mg 8.42000000 0.00000000 -10.52500000 12 1
|
||||
O 6.31500000 0.00000000 -10.52500000 8 0
|
||||
Mg 6.31500000 -2.10500000 -6.31500000 12 1
|
||||
O 8.42000000 -2.10500000 -6.31500000 8 0
|
||||
Mg 6.31500000 0.00000000 -4.21000000 12 1
|
||||
O 8.42000000 0.00000000 -4.21000000 8 0
|
||||
Mg 8.42000000 -2.10500000 -4.21000000 12 1
|
||||
O 6.31500000 -2.10500000 -4.21000000 8 0
|
||||
Mg 8.42000000 0.00000000 -6.31500000 12 1
|
||||
O 6.31500000 0.00000000 -6.31500000 8 0
|
||||
Mg 6.31500000 -2.10500000 -2.10500000 12 1
|
||||
O 8.42000000 -2.10500000 -2.10500000 8 0
|
||||
Mg 6.31500000 0.00000000 0.00000000 12 1
|
||||
O 8.42000000 0.00000000 0.00000000 8 0
|
||||
Mg 8.42000000 -2.10500000 0.00000000 12 1
|
||||
O 6.31500000 -2.10500000 0.00000000 8 0
|
||||
Mg 8.42000000 0.00000000 -2.10500000 12 1
|
||||
O 6.31500000 0.00000000 -2.10500000 8 0
|
||||
Mg 6.31500000 4.21000000 -12.63000000 12 1
|
||||
O 6.31500000 2.10500000 -12.63000000 8 0
|
||||
Mg 6.31500000 2.10500000 -10.52500000 12 1
|
||||
O 8.42000000 2.10500000 -10.52500000 8 0
|
||||
Mg 6.31500000 4.21000000 -8.42000000 12 1
|
||||
O 8.42000000 4.21000000 -8.42000000 8 0
|
||||
Mg 8.42000000 2.10500000 -8.42000000 12 1
|
||||
O 6.31500000 2.10500000 -8.42000000 8 0
|
||||
Mg 8.42000000 4.21000000 -10.52500000 12 1
|
||||
O 6.31500000 4.21000000 -10.52500000 8 0
|
||||
Mg 6.31500000 2.10500000 -6.31500000 12 1
|
||||
O 8.42000000 2.10500000 -6.31500000 8 0
|
||||
Mg 6.31500000 4.21000000 -4.21000000 12 1
|
||||
O 8.42000000 4.21000000 -4.21000000 8 0
|
||||
Mg 8.42000000 2.10500000 -4.21000000 12 1
|
||||
O 6.31500000 2.10500000 -4.21000000 8 0
|
||||
Mg 8.42000000 4.21000000 -6.31500000 12 1
|
||||
O 6.31500000 4.21000000 -6.31500000 8 0
|
||||
Mg 6.31500000 2.10500000 -2.10500000 12 1
|
||||
O 8.42000000 2.10500000 -2.10500000 8 0
|
||||
Mg 6.31500000 4.21000000 0.00000000 12 1
|
||||
O 8.42000000 4.21000000 0.00000000 8 0
|
||||
Mg 8.42000000 2.10500000 0.00000000 12 1
|
||||
O 6.31500000 2.10500000 0.00000000 8 0
|
||||
Mg 8.42000000 4.21000000 -2.10500000 12 1
|
||||
O 6.31500000 4.21000000 -2.10500000 8 0
|
||||
Mg 6.31500000 6.31500000 -10.52500000 12 1
|
||||
Mg 6.31500000 8.42000000 -8.42000000 12 1
|
||||
O 8.42000000 8.42000000 -8.42000000 8 0
|
||||
Mg 8.42000000 6.31500000 -8.42000000 12 1
|
||||
O 6.31500000 6.31500000 -8.42000000 8 0
|
||||
Mg 6.31500000 6.31500000 -6.31500000 12 1
|
||||
O 8.42000000 6.31500000 -6.31500000 8 0
|
||||
Mg 6.31500000 8.42000000 -4.21000000 12 1
|
||||
O 8.42000000 8.42000000 -4.21000000 8 0
|
||||
Mg 8.42000000 6.31500000 -4.21000000 12 1
|
||||
O 6.31500000 6.31500000 -4.21000000 8 0
|
||||
Mg 8.42000000 8.42000000 -6.31500000 12 1
|
||||
O 6.31500000 8.42000000 -6.31500000 8 0
|
||||
Mg 6.31500000 6.31500000 -2.10500000 12 1
|
||||
O 8.42000000 6.31500000 -2.10500000 8 0
|
||||
Mg 6.31500000 8.42000000 0.00000000 12 1
|
||||
O 8.42000000 8.42000000 0.00000000 8 0
|
||||
Mg 8.42000000 6.31500000 0.00000000 12 1
|
||||
O 6.31500000 6.31500000 0.00000000 8 0
|
||||
Mg 8.42000000 8.42000000 -2.10500000 12 1
|
||||
O 6.31500000 8.42000000 -2.10500000 8 0
|
||||
Mg 6.31500000 10.52500000 -6.31500000 12 1
|
||||
Mg 6.31500000 12.63000000 -4.21000000 12 1
|
||||
Mg 8.42000000 10.52500000 -4.21000000 12 1
|
||||
O 6.31500000 10.52500000 -4.21000000 8 0
|
||||
Mg 6.31500000 10.52500000 -2.10500000 12 1
|
||||
O 8.42000000 10.52500000 -2.10500000 8 0
|
||||
Mg 6.31500000 12.63000000 0.00000000 12 1
|
||||
Mg 8.42000000 10.52500000 0.00000000 12 1
|
||||
O 6.31500000 10.52500000 0.00000000 8 0
|
||||
O 6.31500000 12.63000000 -2.10500000 8 0
|
||||
Mg 10.52500000 -8.42000000 -4.21000000 12 1
|
||||
Mg 10.52500000 -8.42000000 0.00000000 12 1
|
||||
O 10.52500000 -8.42000000 -2.10500000 8 0
|
||||
Mg 10.52500000 -4.21000000 -8.42000000 12 1
|
||||
Mg 10.52500000 -6.31500000 -6.31500000 12 1
|
||||
Mg 10.52500000 -4.21000000 -4.21000000 12 1
|
||||
O 12.63000000 -4.21000000 -4.21000000 8 0
|
||||
Mg 12.63000000 -6.31500000 -4.21000000 12 1
|
||||
O 10.52500000 -6.31500000 -4.21000000 8 0
|
||||
Mg 12.63000000 -4.21000000 -6.31500000 12 1
|
||||
O 10.52500000 -4.21000000 -6.31500000 8 0
|
||||
Mg 10.52500000 -6.31500000 -2.10500000 12 1
|
||||
O 12.63000000 -6.31500000 -2.10500000 8 0
|
||||
Mg 10.52500000 -4.21000000 0.00000000 12 1
|
||||
O 12.63000000 -4.21000000 0.00000000 8 0
|
||||
Mg 12.63000000 -6.31500000 0.00000000 12 1
|
||||
O 10.52500000 -6.31500000 0.00000000 8 0
|
||||
Mg 12.63000000 -4.21000000 -2.10500000 12 1
|
||||
O 10.52500000 -4.21000000 -2.10500000 8 0
|
||||
Mg 10.52500000 0.00000000 -8.42000000 12 1
|
||||
O 10.52500000 -2.10500000 -8.42000000 8 0
|
||||
Mg 10.52500000 -2.10500000 -6.31500000 12 1
|
||||
O 12.63000000 -2.10500000 -6.31500000 8 0
|
||||
Mg 10.52500000 0.00000000 -4.21000000 12 1
|
||||
O 12.63000000 0.00000000 -4.21000000 8 0
|
||||
Mg 12.63000000 -2.10500000 -4.21000000 12 1
|
||||
O 10.52500000 -2.10500000 -4.21000000 8 0
|
||||
Mg 12.63000000 0.00000000 -6.31500000 12 1
|
||||
O 10.52500000 0.00000000 -6.31500000 8 0
|
||||
Mg 10.52500000 -2.10500000 -2.10500000 12 1
|
||||
O 12.63000000 -2.10500000 -2.10500000 8 0
|
||||
Mg 10.52500000 0.00000000 0.00000000 12 1
|
||||
O 12.63000000 0.00000000 0.00000000 8 0
|
||||
Mg 12.63000000 -2.10500000 0.00000000 12 1
|
||||
O 10.52500000 -2.10500000 0.00000000 8 0
|
||||
Mg 12.63000000 0.00000000 -2.10500000 12 1
|
||||
O 10.52500000 0.00000000 -2.10500000 8 0
|
||||
Mg 10.52500000 4.21000000 -8.42000000 12 1
|
||||
O 10.52500000 2.10500000 -8.42000000 8 0
|
||||
Mg 10.52500000 2.10500000 -6.31500000 12 1
|
||||
O 12.63000000 2.10500000 -6.31500000 8 0
|
||||
Mg 10.52500000 4.21000000 -4.21000000 12 1
|
||||
O 12.63000000 4.21000000 -4.21000000 8 0
|
||||
Mg 12.63000000 2.10500000 -4.21000000 12 1
|
||||
O 10.52500000 2.10500000 -4.21000000 8 0
|
||||
Mg 12.63000000 4.21000000 -6.31500000 12 1
|
||||
O 10.52500000 4.21000000 -6.31500000 8 0
|
||||
Mg 10.52500000 2.10500000 -2.10500000 12 1
|
||||
O 12.63000000 2.10500000 -2.10500000 8 0
|
||||
Mg 10.52500000 4.21000000 0.00000000 12 1
|
||||
O 12.63000000 4.21000000 0.00000000 8 0
|
||||
Mg 12.63000000 2.10500000 0.00000000 12 1
|
||||
O 10.52500000 2.10500000 0.00000000 8 0
|
||||
Mg 12.63000000 4.21000000 -2.10500000 12 1
|
||||
O 10.52500000 4.21000000 -2.10500000 8 0
|
||||
Mg 10.52500000 6.31500000 -6.31500000 12 1
|
||||
Mg 10.52500000 8.42000000 -4.21000000 12 1
|
||||
Mg 12.63000000 6.31500000 -4.21000000 12 1
|
||||
O 10.52500000 6.31500000 -4.21000000 8 0
|
||||
Mg 10.52500000 6.31500000 -2.10500000 12 1
|
||||
O 12.63000000 6.31500000 -2.10500000 8 0
|
||||
Mg 10.52500000 8.42000000 0.00000000 12 1
|
||||
Mg 12.63000000 6.31500000 0.00000000 12 1
|
||||
O 10.52500000 6.31500000 0.00000000 8 0
|
||||
O 10.52500000 8.42000000 -2.10500000 8 0
|
||||
Mg 14.73500000 0.00000000 0.00000000 12 1
|
|
@ -0,0 +1,59 @@
|
|||
57
|
||||
Phtalocyanine
|
||||
Ti 0.0000 0.0000 0.0000
|
||||
N 1.9944 0.0000 -0.6270
|
||||
N 0.0000 1.9944 -0.6270
|
||||
N -1.9944 0.0000 -0.6270
|
||||
N 0.0000 -1.9944 -0.6270
|
||||
N 2.3811 -2.3811 -0.7747
|
||||
N 2.3811 2.3811 -0.7747
|
||||
N -2.3811 2.3811 -0.7747
|
||||
N -2.3811 -2.3811 -0.7747
|
||||
C 2.7862 -1.1243 -0.7216
|
||||
C 1.1243 2.7862 -0.7216
|
||||
C -2.7862 1.1243 -0.7216
|
||||
C -1.1243 2.7862 -0.7216
|
||||
C -2.7862 -1.1243 -0.7216
|
||||
C 1.1243 -2.7862 -0.7216
|
||||
C 2.7862 1.1243 -0.7216
|
||||
C -1.1243 -2.7862 -0.7216
|
||||
C 4.1814 -0.7098 -0.8274
|
||||
C 0.7098 4.1814 -0.8274
|
||||
C -4.1814 0.7098 -0.8274
|
||||
C -0.7098 4.1814 -0.8274
|
||||
C -4.1814 -0.7098 -0.8274
|
||||
C 0.7098 -4.1814 -0.8274
|
||||
C 4.1814 0.7098 -0.8274
|
||||
C -0.7098 -4.1814 -0.8274
|
||||
C 5.3704 -1.4277 -0.9433
|
||||
C 1.4277 5.3704 -0.9433
|
||||
C -5.3704 1.4277 -0.9433
|
||||
C -1.4277 5.3704 -0.9433
|
||||
C -5.3704 -1.4277 -0.9433
|
||||
C 1.4277 -5.3704 -0.9433
|
||||
C 5.3704 1.4277 -0.9433
|
||||
C -1.4277 -5.3704 -0.9433
|
||||
C 6.5622 -0.7166 -1.0358
|
||||
C 0.7166 6.5622 -1.0358
|
||||
C -6.5622 0.7166 -1.0358
|
||||
C -0.7166 6.5622 -1.0358
|
||||
C -6.5622 -0.7166 -1.0358
|
||||
C 0.7166 -6.5622 -1.0358
|
||||
C 6.5622 0.7166 -1.0358
|
||||
C -0.7166 -6.5622 -1.0358
|
||||
H 5.4029 -2.4925 -0.9609
|
||||
H 2.4925 5.4029 -0.9609
|
||||
H -5.4029 2.4925 -0.9609
|
||||
H -2.4925 5.4029 -0.9609
|
||||
H -5.4029 -2.4925 -0.9609
|
||||
H 2.4925 -5.4029 -0.9609
|
||||
H 5.4029 2.4925 -0.9609
|
||||
H -2.4925 -5.4029 -0.9609
|
||||
H 7.5337 -1.1483 -1.1224
|
||||
H 1.1483 7.5337 -1.1224
|
||||
H -7.5337 1.1483 -1.1224
|
||||
H -1.1483 7.5337 -1.1224
|
||||
H -7.5337 -1.1483 -1.1224
|
||||
H 1.1483 -7.5337 -1.1224
|
||||
H 7.5337 1.1483 -1.1224
|
||||
H -1.1483 -7.5337 -1.1224
|
|
@ -0,0 +1,312 @@
|
|||
310
|
||||
#A (5,5) Tube material is: C , radius is: 3.43 bond legth used: 1.42
|
||||
C 3.427256 .000000 .000000
|
||||
C 3.351267 -.717699 1.222920
|
||||
C 1.059080 -3.259514 .000000
|
||||
C .353026 -3.409025 1.222920
|
||||
C -2.772708 -2.014490 .000000
|
||||
C -3.133085 -1.389195 1.222920
|
||||
C -2.772708 2.014491 .000000
|
||||
C -2.289379 2.550456 1.222920
|
||||
C 1.059081 3.259514 .000000
|
||||
C 1.718171 2.965462 1.222920
|
||||
C 2.772708 2.014490 1.222921
|
||||
C 3.133085 1.389195 2.445841
|
||||
C 2.772708 -2.014490 1.222921
|
||||
C 2.289379 -2.550456 2.445841
|
||||
C -1.059080 -3.259514 1.222921
|
||||
C -1.718170 -2.965463 2.445841
|
||||
C -3.427256 .000000 1.222921
|
||||
C -3.351267 .717699 2.445841
|
||||
C -1.059081 3.259514 1.222921
|
||||
C -.353026 3.409025 2.445841
|
||||
C 1.059080 3.259514 2.445841
|
||||
C 1.718170 2.965463 3.668761
|
||||
C 3.427256 .000000 2.445841
|
||||
C 3.351267 -.717699 3.668761
|
||||
C 1.059080 -3.259514 2.445841
|
||||
C .353026 -3.409025 3.668761
|
||||
C -2.772708 -2.014490 2.445841
|
||||
C -3.133085 -1.389195 3.668761
|
||||
C -2.772708 2.014491 2.445841
|
||||
C -2.289379 2.550456 3.668761
|
||||
C -1.059080 3.259514 3.668762
|
||||
C -.353027 3.409025 4.891682
|
||||
C 2.772708 2.014490 3.668762
|
||||
C 3.133085 1.389195 4.891682
|
||||
C 2.772708 -2.014490 3.668762
|
||||
C 2.289379 -2.550456 4.891682
|
||||
C -1.059080 -3.259514 3.668762
|
||||
C -1.718170 -2.965463 4.891682
|
||||
C -3.427256 .000000 3.668762
|
||||
C -3.351267 .717699 4.891682
|
||||
C -2.772708 2.014490 4.891682
|
||||
C -2.289379 2.550456 6.114603
|
||||
C 1.059080 3.259514 4.891682
|
||||
C 1.718170 2.965463 6.114603
|
||||
C 3.427256 .000000 4.891682
|
||||
C 3.351267 -.717699 6.114603
|
||||
C 1.059080 -3.259514 4.891682
|
||||
C .353026 -3.409025 6.114603
|
||||
C -2.772708 -2.014490 4.891682
|
||||
C -3.133085 -1.389195 6.114603
|
||||
C -3.427256 -.000000 6.114603
|
||||
C -3.351267 .717698 7.337523
|
||||
C -1.059080 3.259514 6.114603
|
||||
C -.353027 3.409025 7.337523
|
||||
C 2.772708 2.014490 6.114603
|
||||
C 3.133085 1.389195 7.337523
|
||||
C 2.772708 -2.014490 6.114603
|
||||
C 2.289379 -2.550456 7.337523
|
||||
C -1.059080 -3.259514 6.114603
|
||||
C -1.718170 -2.965463 7.337523
|
||||
C -2.772708 -2.014491 7.337523
|
||||
C -3.133084 -1.389195 8.560444
|
||||
C -2.772708 2.014490 7.337523
|
||||
C -2.289379 2.550456 8.560444
|
||||
C 1.059080 3.259514 7.337523
|
||||
C 1.718170 2.965463 8.560444
|
||||
C 3.427256 .000000 7.337523
|
||||
C 3.351267 -.717699 8.560444
|
||||
C 1.059080 -3.259514 7.337523
|
||||
C .353026 -3.409025 8.560444
|
||||
C -1.059081 -3.259514 8.560444
|
||||
C -1.718171 -2.965462 9.783364
|
||||
C -3.427256 .000001 8.560444
|
||||
C -3.351267 .717699 9.783364
|
||||
C -1.059080 3.259514 8.560444
|
||||
C -.353026 3.409025 9.783364
|
||||
C 2.772708 2.014490 8.560444
|
||||
C 3.133085 1.389194 9.783364
|
||||
C 2.772707 -2.014491 8.560444
|
||||
C 2.289378 -2.550456 9.783364
|
||||
C 1.059081 -3.259514 9.783364
|
||||
C .353026 -3.409025 11.006285
|
||||
C -2.772708 -2.014491 9.783364
|
||||
C -3.133084 -1.389195 11.006285
|
||||
C -2.772708 2.014490 9.783364
|
||||
C -2.289379 2.550456 11.006285
|
||||
C 1.059080 3.259514 9.783364
|
||||
C 1.718170 2.965463 11.006285
|
||||
C 3.427256 .000000 9.783364
|
||||
C 3.351267 -.717699 11.006285
|
||||
C 2.772709 -2.014489 11.006285
|
||||
C 2.289380 -2.550455 12.229205
|
||||
C -1.059079 -3.259514 11.006285
|
||||
C -1.718169 -2.965464 12.229205
|
||||
C -3.427256 -.000001 11.006285
|
||||
C -3.351267 .717698 12.229205
|
||||
C -1.059081 3.259513 11.006285
|
||||
C -.353027 3.409025 12.229205
|
||||
C 2.772707 2.014491 11.006285
|
||||
C 3.133084 1.389196 12.229205
|
||||
C 3.427256 .000001 12.229205
|
||||
C 3.351267 -.717697 13.452126
|
||||
C 1.059081 -3.259514 12.229205
|
||||
C .353026 -3.409025 13.452126
|
||||
C -2.772708 -2.014491 12.229205
|
||||
C -3.133084 -1.389195 13.452126
|
||||
C -2.772708 2.014490 12.229205
|
||||
C -2.289379 2.550456 13.452126
|
||||
C 1.059080 3.259514 12.229205
|
||||
C 1.718170 2.965463 13.452126
|
||||
C 2.772708 2.014490 13.452126
|
||||
C 3.133085 1.389194 14.675046
|
||||
C 2.772708 -2.014491 13.452126
|
||||
C 2.289380 -2.550455 14.675046
|
||||
C -1.059081 -3.259514 13.452126
|
||||
C -1.718171 -2.965462 14.675046
|
||||
C -3.427256 .000001 13.452126
|
||||
C -3.351267 .717699 14.675046
|
||||
C -1.059080 3.259514 13.452126
|
||||
C -.353026 3.409025 14.675046
|
||||
C 1.059080 3.259514 14.675047
|
||||
C 1.718170 2.965463 15.897967
|
||||
C 3.427256 .000001 14.675047
|
||||
C 3.351267 -.717697 15.897967
|
||||
C 1.059081 -3.259514 14.675047
|
||||
C .353026 -3.409025 15.897967
|
||||
C -2.772708 -2.014491 14.675047
|
||||
C -3.133084 -1.389195 15.897967
|
||||
C -2.772708 2.014490 14.675047
|
||||
C -2.289379 2.550456 15.897967
|
||||
C -1.059082 3.259513 15.897967
|
||||
C -.353027 3.409025 17.120888
|
||||
C 2.772707 2.014492 15.897967
|
||||
C 3.133084 1.389197 17.120888
|
||||
C 2.772709 -2.014489 15.897967
|
||||
C 2.289380 -2.550455 17.120888
|
||||
C -1.059079 -3.259514 15.897967
|
||||
C -1.718169 -2.965464 17.120888
|
||||
C -3.427256 -.000001 15.897967
|
||||
C -3.351267 .717698 17.120888
|
||||
C -2.772708 2.014491 17.120888
|
||||
C -2.289378 2.550457 18.343807
|
||||
C 1.059081 3.259513 17.120888
|
||||
C 1.718170 2.965463 18.343807
|
||||
C 3.427256 -.000001 17.120888
|
||||
C 3.351267 -.717700 18.343807
|
||||
C 1.059079 -3.259514 17.120888
|
||||
C .353026 -3.409025 18.343807
|
||||
C -2.772709 -2.014489 17.120888
|
||||
C -3.133085 -1.389194 18.343807
|
||||
C -3.427256 -.000000 18.343807
|
||||
C -3.351267 .717699 19.566727
|
||||
C -1.059079 3.259514 18.343807
|
||||
C -.353024 3.409025 19.566727
|
||||
C 2.772708 2.014490 18.343807
|
||||
C 3.133085 1.389194 19.566727
|
||||
C 2.772708 -2.014491 18.343807
|
||||
C 2.289380 -2.550455 19.566727
|
||||
C -1.059081 -3.259514 18.343807
|
||||
C -1.718171 -2.965462 19.566727
|
||||
C -2.772707 -2.014491 19.566729
|
||||
C -3.133085 -1.389195 20.789650
|
||||
C -2.772708 2.014491 19.566729
|
||||
C -2.289378 2.550457 20.789650
|
||||
C 1.059080 3.259514 19.566729
|
||||
C 1.718170 2.965463 20.789650
|
||||
C 3.427256 .000001 19.566729
|
||||
C 3.351267 -.717697 20.789650
|
||||
C 1.059081 -3.259514 19.566729
|
||||
C .353026 -3.409025 20.789650
|
||||
C -1.059079 -3.259514 20.789650
|
||||
C -1.718169 -2.965464 22.012569
|
||||
C -3.427256 -.000000 20.789650
|
||||
C -3.351267 .717699 22.012569
|
||||
C -1.059082 3.259513 20.789650
|
||||
C -.353027 3.409025 22.012569
|
||||
C 2.772707 2.014492 20.789650
|
||||
C 3.133084 1.389197 22.012569
|
||||
C 2.772709 -2.014489 20.789650
|
||||
C 2.289380 -2.550455 22.012569
|
||||
C 1.059083 -3.259513 22.012569
|
||||
C .353028 -3.409025 23.235489
|
||||
C -2.772707 -2.014491 22.012569
|
||||
C -3.133085 -1.389195 23.235489
|
||||
C -2.772710 2.014488 22.012569
|
||||
C -2.289381 2.550455 23.235489
|
||||
C 1.059078 3.259515 22.012569
|
||||
C 1.718168 2.965465 23.235489
|
||||
C 3.427256 .000002 22.012569
|
||||
C 3.351267 -.717697 23.235489
|
||||
C 2.772708 -2.014490 23.235491
|
||||
C 2.289379 -2.550456 24.458412
|
||||
C -1.059079 -3.259514 23.235491
|
||||
C -1.718169 -2.965464 24.458412
|
||||
C -3.427256 -.000000 23.235491
|
||||
C -3.351267 .717699 24.458412
|
||||
C -1.059079 3.259514 23.235491
|
||||
C -.353024 3.409025 24.458412
|
||||
C 2.772708 2.014490 23.235491
|
||||
C 3.133085 1.389194 24.458412
|
||||
C 3.427256 .000001 24.458410
|
||||
C 3.351267 -.717698 25.681332
|
||||
C 1.059083 -3.259513 24.458410
|
||||
C .353028 -3.409025 25.681332
|
||||
C -2.772707 -2.014491 24.458410
|
||||
C -3.133085 -1.389195 25.681332
|
||||
C -2.772708 2.014491 24.458410
|
||||
C -2.289378 2.550457 25.681332
|
||||
C 1.059080 3.259514 24.458410
|
||||
C 1.718170 2.965463 25.681332
|
||||
C 2.772707 2.014492 25.681332
|
||||
C 3.133084 1.389196 26.904251
|
||||
C 2.772710 -2.014488 25.681332
|
||||
C 2.289381 -2.550454 26.904251
|
||||
C -1.059079 -3.259514 25.681332
|
||||
C -1.718169 -2.965464 26.904251
|
||||
C -3.427256 -.000000 25.681332
|
||||
C -3.351267 .717699 26.904251
|
||||
C -1.059082 3.259513 25.681332
|
||||
C -.353027 3.409025 26.904251
|
||||
C 1.059081 3.259514 26.904251
|
||||
C 1.718171 2.965462 28.127171
|
||||
C 3.427256 -.000002 26.904251
|
||||
C 3.351266 -.717701 28.127171
|
||||
C 1.059080 -3.259514 26.904251
|
||||
C .353025 -3.409025 28.127171
|
||||
C -2.772707 -2.014491 26.904251
|
||||
C -3.133085 -1.389195 28.127171
|
||||
C -2.772708 2.014491 26.904251
|
||||
C -2.289378 2.550457 28.127171
|
||||
C -1.059081 3.259514 28.127172
|
||||
C -.353026 3.409025 29.350094
|
||||
C 2.772709 2.014489 28.127172
|
||||
C 3.133085 1.389193 29.350094
|
||||
C 2.772708 -2.014490 28.127172
|
||||
C 2.289379 -2.550456 29.350094
|
||||
C -1.059079 -3.259514 28.127172
|
||||
C -1.718169 -2.965464 29.350094
|
||||
C -3.427256 -.000000 28.127172
|
||||
C -3.351267 .717699 29.350094
|
||||
C -2.772709 2.014489 29.350094
|
||||
C -2.289380 2.550455 30.573013
|
||||
C 1.059081 3.259514 29.350094
|
||||
C 1.718171 2.965462 30.573013
|
||||
C 3.427256 .000001 29.350094
|
||||
C 3.351267 -.717698 30.573013
|
||||
C 1.059083 -3.259513 29.350094
|
||||
C .353028 -3.409025 30.573013
|
||||
C -2.772707 -2.014491 29.350094
|
||||
C -3.133085 -1.389195 30.573013
|
||||
C -3.427256 -.000002 30.573013
|
||||
C -3.351267 .717697 31.795933
|
||||
C -1.059081 3.259514 30.573013
|
||||
C -.353026 3.409025 31.795933
|
||||
C 2.772707 2.014492 30.573013
|
||||
C 3.133084 1.389196 31.795933
|
||||
C 2.772710 -2.014488 30.573013
|
||||
C 2.289381 -2.550454 31.795933
|
||||
C -1.059079 -3.259514 30.573013
|
||||
C -1.718169 -2.965464 31.795933
|
||||
C -2.772706 -2.014493 31.795935
|
||||
C -3.133082 -1.389200 33.018856
|
||||
C -2.772709 2.014489 31.795935
|
||||
C -2.289380 2.550455 33.018856
|
||||
C 1.059077 3.259515 31.795935
|
||||
C 1.718169 2.965464 33.018856
|
||||
C 3.427256 .000004 31.795935
|
||||
C 3.351268 -.717695 33.018856
|
||||
C 1.059083 -3.259513 31.795935
|
||||
C .353028 -3.409025 33.018856
|
||||
C -1.059076 -3.259515 33.018856
|
||||
C -1.718165 -2.965466 34.241776
|
||||
C -3.427256 -.000002 33.018856
|
||||
C -3.351267 .717697 34.241776
|
||||
C -1.059084 3.259513 33.018856
|
||||
C -.353030 3.409025 34.241776
|
||||
C 2.772705 2.014495 33.018856
|
||||
C 3.133083 1.389199 34.241776
|
||||
C 2.772710 -2.014488 33.018856
|
||||
C 2.289381 -2.550454 34.241776
|
||||
C 1.059079 -3.259514 34.241776
|
||||
C .353027 -3.409025 35.464695
|
||||
C -2.772710 -2.014488 34.241776
|
||||
C -3.133085 -1.389194 35.464695
|
||||
C -2.772707 2.014492 34.241776
|
||||
C -2.289377 2.550457 35.464695
|
||||
C 1.059081 3.259514 34.241776
|
||||
C 1.718171 2.965462 35.464695
|
||||
C 3.427256 -.000002 34.241776
|
||||
C 3.351266 -.717701 35.464695
|
||||
C 2.772708 -2.014491 35.464695
|
||||
C 2.289381 -2.550455 36.687614
|
||||
C -1.059083 -3.259513 35.464695
|
||||
C -1.718170 -2.965463 36.687614
|
||||
C -3.427256 .000001 35.464695
|
||||
C -3.351267 .717700 36.687614
|
||||
C -1.059081 3.259514 35.464695
|
||||
C -.353026 3.409025 36.687614
|
||||
C 2.772709 2.014489 35.464695
|
||||
C 3.133085 1.389193 36.687614
|
||||
C 3.427256 .000000 36.687614
|
||||
C 3.351267 -.717696 37.910534
|
||||
C 1.059079 -3.259514 36.687614
|
||||
C .353027 -3.409025 37.910534
|
||||
C -2.772710 -2.014488 36.687614
|
||||
C -3.133085 -1.389194 37.910534
|
||||
C -2.772709 2.014489 36.687614
|
||||
C -2.289380 2.550455 37.910534
|
||||
C 1.059081 3.259514 36.687614
|
||||
C 1.718171 2.965462 37.910534
|
Binary file not shown.
|
@ -0,0 +1,72 @@
|
|||
#! /bin/bash -f
|
||||
echo " "
|
||||
echo " "
|
||||
echo "****************************************************"
|
||||
echo "* *"
|
||||
echo "* CLUSTER GEOMETRY ANALYSIS CODE *"
|
||||
echo "* *"
|
||||
echo "****************************************************"
|
||||
echo " "
|
||||
echo " "
|
||||
#
|
||||
time -p ./es_mod/Sym_Analys/clus_sym <<Fin >& error.dat
|
||||
Cluster.xyz # Input cluster file
|
||||
0 # Tetrahedra detection
|
||||
0 # Octahedra detection
|
||||
0 # Cube detection
|
||||
0 # Hollow molecules detection
|
||||
0 # Nanotube detection
|
||||
0 # Regular polygons detection
|
||||
0 # Iregular polygons detection
|
||||
1 # Symmetries detection
|
||||
Fin
|
||||
#
|
||||
# Checking for errors in the execution
|
||||
#
|
||||
cat error.dat | sed -e '1,35d' \
|
||||
-e '/real/,/ /d' > error.txt
|
||||
#
|
||||
# Checking for a blend of dialog
|
||||
#
|
||||
DIAL=`which dialog | cut -d: -f2 | grep -c 'dialog'`
|
||||
XDIA=`which Xdialog | cut -d: -f2 | grep -c 'Xdialog'`
|
||||
KDIA=`which kdialog | cut -d: -f2 | grep -c 'kdialog'`
|
||||
ZENI=`which zenity | cut -d: -f2 | grep -c 'zenity'`
|
||||
#
|
||||
if [ "$ZENI" -ne "0" ]; then
|
||||
DIALOG=zenity
|
||||
else
|
||||
if [ "$XDIA" -ne "0" ]; then
|
||||
DIALOG=Xdialog
|
||||
else
|
||||
if [ "$KDIA" -ne "0" ]; then
|
||||
DIALOG=kdialog
|
||||
else
|
||||
if [ "$DIAL" -ne "0" ]; then
|
||||
DIALOG=dialog
|
||||
else
|
||||
DIALOG=none
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#
|
||||
FILE=`ls -at | grep .lis | awk '{print $1}'`
|
||||
tail --lines=10 $FILE | grep '<<<<' | sed 's/<</ /g' | sed 's/>>/ /g' >> run.txt
|
||||
cat run.txt >> error.txt
|
||||
ERR=`cat error.txt`
|
||||
NLINE=`cat error.txt | wc -l`
|
||||
#
|
||||
if [ $NLINE != 0 ]; then
|
||||
if [ "$DIALOG" = "zenity" ]; then
|
||||
zenity --width 400 --height 180 \
|
||||
--title "MsSpec-1.1 runtime error" \
|
||||
--info --text "The code has stopped with the message : \n \n \n $ERR" \
|
||||
--timeout 5
|
||||
fi
|
||||
fi
|
||||
#
|
||||
rm -f error.dat error.txt run.txt
|
||||
#
|
||||
exit
|
||||
|
|
@ -0,0 +1 @@
|
|||
__all__ = ["Delaunay_Intersphere","Convex_Hull_Cover"]
|
|
@ -0,0 +1,712 @@
|
|||
# coding: utf-8
|
||||
|
||||
import unittest
|
||||
import math
|
||||
import numpy as np
|
||||
import delaunay.core as delc
|
||||
import es_sym_analys as essyma
|
||||
import es_tools as tool
|
||||
import es_clustering as esclus
|
||||
from scipy.spatial import ConvexHull, Voronoi
|
||||
|
||||
# ===========
|
||||
from ase import Atoms
|
||||
from ase.visualize import view
|
||||
from ase.data import covalent_radii
|
||||
|
||||
# ===================================================================
|
||||
# List of routines :
|
||||
"""
|
||||
=================
|
||||
ES_AllRadius_Updater(NewES,Structure,[list]) : Update ES_AllRadius global variable with new radius of empty spheres
|
||||
given as NewES
|
||||
|
||||
Voronoi_Vertex(Structure) : Computes Voronoi Vertices of Structure
|
||||
Delaunay_Tetrahedral_ES(Structure,[minsize],[maxsize],[tol]) : Creates a tetrehedral mesh from the structure,
|
||||
then returns for each center the perfect sphere going in.
|
||||
Convex_Hull_Cover(Structure,[es_radius],[tol],[missing]) : Finds the exterior Hull from the set, create triangular
|
||||
mesh then returns cover coordinates. tol=0 => no fusion
|
||||
Select_Int_Ext(Centroid,E1,E2,IE) : Clean the Cover, taking only internal or external
|
||||
Internal_Hull_Cover(Structure,[es_radius],[tol],[missing]) : Finds the interior Hull from the set, create triangular
|
||||
mesh then returns cover coordinates
|
||||
Internal_Hull_Centers(set) : Finds the interior Hull from the set, create triangular mesh then returns centers coordinates
|
||||
ES_Fusion(set, structure, size) : Change the set by clustering spheres near from size to each other. No size given => take shortest
|
||||
Maintain distances with structure, compared with the ancient set.
|
||||
Fusion_Overlap(Spheres_Data,tol) : Find Spheres touching each other, and fusions them. Don't return radius : only final coordinates
|
||||
Flat_Covering(Structure,[R],[tol],[Curved]) : For flat (or almost) set : Searchs major plane, triangulates,
|
||||
and cover the 2 sides.
|
||||
Plane_Triangulation(Plane3D,Plane_eq): Return triangulation of a 3D Plane (convert into 2D, uses Delny)
|
||||
Atom_Radius(Structure,n,list) : Returns radius of n°th atom in Structure (Angstrom). Regroup different radius lists.
|
||||
Convex_Hull_InterCover(set) : Return list of internal cover using ConvexHull : Different from Delaunay_Intersphere :
|
||||
made for empty clusters
|
||||
|
||||
=================
|
||||
"""
|
||||
|
||||
ES_AllRadius = [] # Global Variable taking all empty spheres radius
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def ES_AllRadius_Updater(NewES, Structure, list=1):
|
||||
# Update ES_AllRadius global variable with new radius of empty spheres given as NewES
|
||||
global ES_AllRadius
|
||||
StrPos = np.ndarray.tolist(Structure.positions)
|
||||
for ES in np.ndarray.tolist(NewES.positions):
|
||||
# print("ES = {}".format(ES))
|
||||
Tempo = []
|
||||
for A in StrPos:
|
||||
# print("A = {}".format(A))
|
||||
d = tool.distance(ES, A)
|
||||
Tempo.append(d - Atom_Radius(Structure, StrPos.index(A), list))
|
||||
ES_AllRadius.append(min(Tempo))
|
||||
return ES_AllRadius
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Voronoi_Vertex(Structure):
|
||||
# Uses Delaunay triangulation to create a tetrahedral mesh from the set of
|
||||
# atoms-centers coordinates, then computes each tetrahedron's inerty center
|
||||
# Returns list of tetrahedrons-centers coordinates
|
||||
# Be careful on tetrahedralisation done : the cube for example will not be correctly tesselated (6tetrahedrons in)
|
||||
"""
|
||||
Triag=delc.Triangulation(set)
|
||||
tetracenters=[]
|
||||
for tetra in Triag.indices:
|
||||
x = y = z = 0.0
|
||||
for vertex in tetra:
|
||||
x += set[vertex][0] / 4.0
|
||||
y += set[vertex][1] / 4.0
|
||||
z += set[vertex][2] / 4.0
|
||||
tetracenters.append((x, y, z))
|
||||
"""
|
||||
struct = np.ndarray.tolist(Structure.positions)
|
||||
Vor = Voronoi(struct)
|
||||
return np.ndarray.tolist(Vor.vertices)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Delaunay_Tetrahedral_ES(Structure, minsize=0, maxsize=999, tol=0.6):
|
||||
# Uses Delaunay triangulation to create a tetrahedral mesh from the Structure of
|
||||
# atoms-centers coordinates, then adds empty sphere in each tetrahedron, equidistant to all Atoms
|
||||
es_data = []
|
||||
allradius = []
|
||||
set = Structure.positions
|
||||
All_Spheres_Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
# print All_Spheres_Data
|
||||
Triag = delc.Triangulation(set)
|
||||
for tetra in Triag.indices:
|
||||
Data = []
|
||||
for vertex in tetra:
|
||||
Data.append(All_Spheres_Data[vertex])
|
||||
tetra_es = esclus.Tetrahedron_ES(Data)
|
||||
if tetra_es == 999:
|
||||
EV = []
|
||||
for vertex in tetra:
|
||||
EV.append(vertex)
|
||||
print(
|
||||
"Error by computing tangent solution to tetrahedron {} : delta < 0 for radius determination ".format(EV))
|
||||
elif tetra_es != 999: # 999 is returned when the problem has no solution due to singular matrix
|
||||
if tetra_es[1] <= maxsize:
|
||||
if tetra_es[1] >= minsize:
|
||||
es_data.append(tetra_es)
|
||||
# output.append(tetra_es[0])
|
||||
|
||||
"""Verify result : ________________
|
||||
print("E_S created at position {}, in center of tetrahedron {}".format(output[0],tetra))
|
||||
viewer = [output[-1]]
|
||||
for vertex in tetra :
|
||||
d=tool.distance(set[vertex],tetra_es[0])
|
||||
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex,d,All_Spheres_Data[vertex][1],tetra_es[1],All_Spheres_Data[vertex][1]+tetra_es[1]))
|
||||
#viewer.append(set[vertex])
|
||||
#View=Atoms("XC4",positions = viewer)
|
||||
#view(View)
|
||||
#raw_input("\nPress Enter to continue ...\n")
|
||||
#""" # _______________________________
|
||||
|
||||
# print "allradius : \n",allradius
|
||||
output = Fusion_Overlap(es_data, tol)
|
||||
return output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Convex_Hull_Cover(Structure, radius_es=0, tol=0.6, missing=False, Int_Ext=0):
|
||||
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
|
||||
# an empty sphere on each hull triangle.
|
||||
# Default tol value is used for Fusion Overlap
|
||||
# Returns list of cover coordinates
|
||||
if Int_Ext == 0:
|
||||
print("Select wich covering you wish :\n0: Internal Cover\n1: External Cover\n2: Both Cover")
|
||||
CovChoice = input()
|
||||
|
||||
Cover_Data = []
|
||||
set = np.ndarray.tolist(Structure.positions)
|
||||
if radius_es == 0:
|
||||
radius_es = input("Please select the radius of empty spheres you desire : ")
|
||||
hull = ConvexHull(set)
|
||||
xc = yc = zc = 0
|
||||
lh = len(hull.vertices)
|
||||
for hpt in hull.vertices:
|
||||
xc += set[hpt][0] / lh
|
||||
yc += set[hpt][1] / lh
|
||||
zc += set[hpt][2] / lh
|
||||
Centroid = [xc, yc, zc]
|
||||
|
||||
if missing == False: # It means we don't care if some hull points are not implemented because they are in facet
|
||||
# Computes the centroïd of the hull
|
||||
|
||||
AllData = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
|
||||
Data = []
|
||||
for vertex in facet:
|
||||
Data.append(AllData[vertex])
|
||||
ES1, ES2 = esclus.Triangle_ES(Data, radius_es)
|
||||
if ES1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, facet))
|
||||
if ES1 != 666:
|
||||
ES = Select_Int_Ext(Centroid, ES1, ES2, 1) # Last parameter != 0 => external cover
|
||||
if tool.distance(Centroid, ES) < 1000000000:
|
||||
Cover_Data.append([ES, radius_es])
|
||||
|
||||
|
||||
|
||||
elif missing == True:
|
||||
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
HullPlane = tool.cleanlist(np.ndarray.tolist(hull.equations))
|
||||
for HP in HullPlane:
|
||||
a, b, c, d = HP
|
||||
HPList = []
|
||||
HPIndex = []
|
||||
for pt in set:
|
||||
x, y, z = pt
|
||||
if abs(a * x + b * y + c * z + d) < 0.01: # Then pt is in Plane HP
|
||||
HPList.append(pt)
|
||||
HPIndex.append(set.index(pt))
|
||||
|
||||
Tria = Plane_Triangulation(HPList, HP)
|
||||
for tria in Tria.indices:
|
||||
Spheres_data = [Data[HPIndex[tria[0]]], Data[HPIndex[tria[1]]], Data[HPIndex[tria[2]]]]
|
||||
ES1, ES2 = esclus.Triangle_ES(Spheres_data, radius_es)
|
||||
if ES1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, [HPIndex[tria[0]],
|
||||
HPIndex[tria[1]],
|
||||
HPIndex[tria[2]]]))
|
||||
if ES1 != 666:
|
||||
# ES = Select_Int_Ext(Centroid, ES1, ES2, 1) # Last parameter != 1 => external cover
|
||||
|
||||
# Cover_Data.append([ES, radius_es])
|
||||
if CovChoice > 1:
|
||||
Cover_Data.append([ES1, radius_es])
|
||||
Cover_Data.append([ES2, radius_es])
|
||||
else:
|
||||
ES = Select_Int_Ext(Centroid, ES1, ES2, CovChoice) # 1 : external; 0 : internal
|
||||
Cover_Data.append([ES, radius_es])
|
||||
|
||||
"""Verify result : ________________
|
||||
print("E_S created at position {}, defined on triangle {}".format(ES, facet))
|
||||
viewer = [ES,ES2]
|
||||
for vertex in facet:
|
||||
d = tool.distance(set[vertex], ES)
|
||||
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex, d,AllData[vertex][1],radius_es,AllData[vertex][1]+radius_es))
|
||||
viewer.append(set[vertex])
|
||||
View=Atoms("XH4",positions = viewer)
|
||||
view(View)
|
||||
# raw_input("\nPress Enter to continue ...\n")
|
||||
# """ # _______________________________
|
||||
|
||||
# Fusion overlapping spheres
|
||||
|
||||
Output = Fusion_Overlap(Cover_Data, tol) # tol at 1% : means fusion if d < (r1 + r2) * 0.01
|
||||
|
||||
return Output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Select_Int_Ext(Centroid, E1, E2, IE):
|
||||
# Returns only internal or external part of cover, using the fact hull is convex : so using his centroid
|
||||
# IE = 0 : take internal part, IE != 0 : take external part
|
||||
d1 = tool.distance(Centroid, E1)
|
||||
d2 = tool.distance(Centroid, E2)
|
||||
if IE == 0: # Internal part : the closest to centroid
|
||||
if d1 < d2:
|
||||
return E1
|
||||
else: # External part : the farest from centroïd
|
||||
if d2 < d1:
|
||||
return E1
|
||||
# Excepted this 2 double conditions : we have to take E2
|
||||
return E2
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
|
||||
def Internal_Hull_Cover(Structure, radius_es=0, tol=0.6, missing=False):
|
||||
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
|
||||
# an empty sphere on each hull triangle.
|
||||
# Default tol value is used for Fusion Overlap
|
||||
# Returns list of cover coordinates
|
||||
Cover_Data = []
|
||||
set = np.ndarray.tolist(Structure.positions)
|
||||
|
||||
if radius_es == 0:
|
||||
radius_es = input("Please select the radius of empty spheres you desire : ")
|
||||
|
||||
hull = ConvexHull(set)
|
||||
|
||||
xc = yc = zc = 0
|
||||
lh = len(hull.vertices)
|
||||
for hpt in hull.vertices:
|
||||
xc += set[hpt][0] / lh
|
||||
yc += set[hpt][1] / lh
|
||||
zc += set[hpt][2] / lh
|
||||
Centroid = [xc, yc, zc]
|
||||
|
||||
invset = tool.Invert_Coord(set, Centroid, 10)
|
||||
hull = ConvexHull(invset)
|
||||
|
||||
if missing == False: # It means we don't care if some hull points are not implemented because they are in facet
|
||||
# Computes the centroïd of the hull
|
||||
|
||||
AllData = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
|
||||
Data = []
|
||||
for vertex in facet:
|
||||
Data.append(AllData[vertex])
|
||||
ES1, ES2 = esclus.Triangle_ES(Data, radius_es)
|
||||
if ES1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, facet))
|
||||
if ES1 != 666:
|
||||
ES = Select_Int_Ext(Centroid, ES1, ES2, 0) # Last parameter = 0 => internal cover
|
||||
|
||||
Cover_Data.append([ES, radius_es])
|
||||
|
||||
elif missing == True:
|
||||
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
HullPlane = tool.cleanlist(np.ndarray.tolist(hull.equations))
|
||||
for HP in HullPlane:
|
||||
a, b, c, d = HP
|
||||
HPList = []
|
||||
HPIndex = []
|
||||
for pt in set:
|
||||
x, y, z = pt
|
||||
if abs(a * x + b * y + c * z + d) < 0.01: # Then pt is in Plane HP
|
||||
HPList.append(pt)
|
||||
HPIndex.append(set.index(pt))
|
||||
|
||||
Tria = Plane_Triangulation(HPList, HP)
|
||||
for tria in Tria.indices:
|
||||
Spheres_data = [Data[HPIndex[tria[0]]], Data[HPIndex[tria[1]]], Data[HPIndex[tria[2]]]]
|
||||
ES1, ES2 = esclus.Triangle_ES(Spheres_data, radius_es)
|
||||
if ES1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, [HPIndex[tria[0]],
|
||||
HPIndex[tria[1]],
|
||||
HPIndex[tria[2]]]))
|
||||
if ES1 != 666:
|
||||
ES = Select_Int_Ext(Centroid, ES1, ES2, 0) # Last parameter != 0 => internal cover
|
||||
|
||||
Cover_Data.append([ES, radius_es])
|
||||
|
||||
"""Verify result : ________________
|
||||
print("E_S created at position {}, defined on triangle {}".format(ES, facet))
|
||||
viewer = [ES,ES2]
|
||||
for vertex in facet:
|
||||
d = tool.distance(set[vertex], ES)
|
||||
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex, d,AllData[vertex][1],radius_es,AllData[vertex][1]+radius_es))
|
||||
viewer.append(set[vertex])
|
||||
View=Atoms("XH4",positions = viewer)
|
||||
view(View)
|
||||
# raw_input("\nPress Enter to continue ...\n")
|
||||
# """ # _______________________________
|
||||
|
||||
# Fusion overlapping spheres
|
||||
|
||||
Output = Fusion_Overlap(Cover_Data, tol) # tol at 1% : means fusion if d < (r1 + r2) * 0.01
|
||||
|
||||
return Output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Internal_Hull_Centers(set):
|
||||
# Uses ConvexHull to find the intern triangular Hull from the set, then computes all centers of simplices
|
||||
# Returns list of centers coordinates
|
||||
invset = tool.Invert_Coord(set, [0, 0, 0], 10)
|
||||
hull = ConvexHull(invset)
|
||||
output = []
|
||||
for facet in hull.simplices:
|
||||
x = y = z = 0.0
|
||||
for vertex in facet:
|
||||
x += set[vertex][0] / 3.0
|
||||
y += set[vertex][1] / 3.0
|
||||
z += set[vertex][2] / 3.0
|
||||
facet_center = [x, y, z] # Center of the triangular facet
|
||||
output.append(facet_center)
|
||||
output = np.array(output).tolist()
|
||||
return output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def ES_Fusion(set, structure, size=0):
|
||||
# study the given set, and fusion some empty spheres to create a better set. Size is used for the partitionnal
|
||||
# clustering, and structure assures the clustering will not reduce the distances. It it set basically to the min distance in the set.
|
||||
if size == 0:
|
||||
size = tool.shortest_dist(set)
|
||||
# print("initial size :", size)
|
||||
size = size * 11. / 10 # we add 10%, to include the very similar cases
|
||||
# print("with error correction size :", size)
|
||||
fusion_set = [] # output : defined like set
|
||||
dmin = tool.set_set_proximity(set, structure)
|
||||
hull = ConvexHull(structure)
|
||||
# simplice_centers=Convex_Hull_Centers(structure+set)
|
||||
while len(set) > 0:
|
||||
# Define a new cluster, add as much empty spheres as possible, and regroup around the centroid
|
||||
cluster = [set[0]]
|
||||
centroid = cluster[0] # Initialisation of the next cluster (it may rest one element, to progress until set=void
|
||||
set.pop(0)
|
||||
reroll = 1
|
||||
while reroll == 1:
|
||||
d0_error = 0
|
||||
reroll = 0 # We must scan the set everytime we change centroid, or we could miss some ES in set
|
||||
for ES in set:
|
||||
# Other possible condition : tool.point_set_proximity(ES, cluster)<=size
|
||||
if tool.distance(ES, centroid) < size: # We can fusion to the cluster
|
||||
reroll = 1 # Centroid will be updated, so reroll the scan of set
|
||||
print("Fusionned a sphere to the cluster")
|
||||
cluster.append(ES)
|
||||
set.remove(ES) # It is in the cluster, so remove from set : we studied it
|
||||
centroid = tool.Isobarycenter(cluster)
|
||||
if tool.point_set_proximity(centroid,
|
||||
structure) < dmin: # We have to put centroid farer to balance fusion
|
||||
Nearest = tool.search_nearest(centroid, structure,
|
||||
tool.point_set_proximity(centroid, structure))
|
||||
V = np.ndarray.tolist(
|
||||
np.array(centroid) - np.array(Nearest)) # So we need nearest structure point
|
||||
d = tool.distance(centroid, Nearest)
|
||||
if d == 0: # it means the centroid came right on existing structure
|
||||
print("Cluster centered exactly in the structure. Size must be revised : cluster cancelled")
|
||||
d0_error = 1
|
||||
fusion_set += cluster
|
||||
reroll = 0
|
||||
break
|
||||
else:
|
||||
V = np.multiply(V, 1. / d) # set V as norm 1
|
||||
V = np.multiply(V, dmin)
|
||||
#
|
||||
print("\n\n We put away form:\n{}\ndmin={}\n".format(tool.vector_norm(V), dmin))
|
||||
#
|
||||
centroid = np.ndarray.tolist(
|
||||
np.array(Nearest) + V) # get centroid away from Nearest to dmin
|
||||
#
|
||||
#
|
||||
#
|
||||
if d0_error == 0:
|
||||
fusion_set.append(centroid)
|
||||
#
|
||||
#
|
||||
return fusion_set
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Fusion_Overlap(Spheres_Data, tol):
|
||||
# Find Spheres touching each other, and fusions them. Don't return radius : only final coordinates
|
||||
Output = []
|
||||
ls = len(Spheres_Data)
|
||||
Index = range(ls)
|
||||
for iS in range(ls):
|
||||
if iS in Index: # The we have to treat this case
|
||||
FusionIndex = [iS]
|
||||
for iOS in range(iS + 1, ls):
|
||||
if iOS in Index:
|
||||
S = Spheres_Data[iS][0]
|
||||
OS = Spheres_Data[iOS][0]
|
||||
rS = Spheres_Data[iS][1]
|
||||
rOS = Spheres_Data[iOS][1]
|
||||
# print("S : {}\nOS : {}".format(S,OS))
|
||||
if tool.distance(S, OS) < (rS + rOS) * tol:
|
||||
# print("Overlap detected : d= {}, r1 ={}, r2 = {}".format(tool.distance(S, OS),rS,rOS))
|
||||
Index.remove(iOS) # S and OS are same coord or almost : we remove the last : OS
|
||||
FusionIndex.append(iOS)
|
||||
lf = len(FusionIndex)
|
||||
x = y = z = 0
|
||||
for i in FusionIndex:
|
||||
x += Spheres_Data[i][0][0] / lf
|
||||
y += Spheres_Data[i][0][1] / lf
|
||||
z += Spheres_Data[i][0][2] / lf
|
||||
Output.append([x, y, z])
|
||||
# else : iS correspond to coord already fusionned
|
||||
return Output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
# ===================================================================
|
||||
def Flat_Covering(Structure, R=0, tol=0.6, Curved=False):
|
||||
# Designed for quite flat set : Searchs major plane, triangulates it, and cover both sides with empty spheres wich radius=size.
|
||||
|
||||
if R != 0:
|
||||
NoAsk = 1
|
||||
else:
|
||||
NoAsk = 0
|
||||
|
||||
if Curved == False:
|
||||
|
||||
flatness = input("Please describe cluster :\nOnly one major plane : Enter 0\nMore major planes : Enter 1\n")
|
||||
|
||||
set = np.ndarray.tolist(Structure.positions)
|
||||
struct = set
|
||||
FlatCover = []
|
||||
# Search major plane(s)
|
||||
if flatness != 0:
|
||||
AllPlanes = essyma.major_plane(struct, multiple=True)
|
||||
|
||||
else:
|
||||
[Plane3D, Plane_eq] = essyma.major_plane(struct)
|
||||
AllPlanes = [[Plane3D, Plane_eq]]
|
||||
|
||||
"""
|
||||
for P in AllPlanes :
|
||||
PlaneView = Atoms(positions=P[0])
|
||||
#view(Structure+PlaneView)
|
||||
view(PlaneView)
|
||||
print("Plane n°{} : \nContains : {}\n PlaneEq = {}".format(AllPlanes.index(P)+1,P[0],P[1]))
|
||||
|
||||
#"""
|
||||
|
||||
# Build empty spheres for all major planes :
|
||||
|
||||
for AP in AllPlanes:
|
||||
Plane3D, Plane_eq = AP
|
||||
|
||||
if NoAsk == 0:
|
||||
Pview = Atoms(positions=Plane3D)
|
||||
view(Pview)
|
||||
print("Please select the radius of empty spheres you desire : ")
|
||||
R = input("(See the view of current plane to get help)\n")
|
||||
|
||||
Index = []
|
||||
for Ppt in Plane3D:
|
||||
Index.append(struct.index(Ppt))
|
||||
|
||||
""" Show Details on Plane
|
||||
#print("Plane : Equation is {}x+{}y+{}z+{}=0\n Plane norm is {}".format(Plane_eq[0],Plane_eq[1],Plane_eq[2],Plane_eq[3],Norm))
|
||||
Lset = len(set)
|
||||
name = "C" + str(Lset)
|
||||
Structure = Atoms(name, positions=struct)
|
||||
Plane3DView = Atoms(positions=Plane3D)
|
||||
view(Structure + Plane3DView)
|
||||
#""" # =====
|
||||
|
||||
Triang = Plane_Triangulation(Plane3D, Plane_eq)
|
||||
# print("Triangulation 2D : {}".format(Triang.indices))
|
||||
# Extract DataforTangent_Fourth_Sphere fromStructure
|
||||
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
simplicenters = []
|
||||
for tria in Triang.indices:
|
||||
Spheres_data = [Data[Index[tria[0]]], Data[Index[tria[1]]], Data[Index[tria[2]]]]
|
||||
"""
|
||||
h = (Spheres_data[0][1] + Spheres_data[1][1] + Spheres_data[2][1]) #h is set as 3 times the average covalent radii of atoms
|
||||
xc = 1. / 3 * (Spheres_data[0][0][0] + Spheres_data[1][0][0] + Spheres_data[2][0][0])
|
||||
yc = 1. / 3 * (Spheres_data[0][0][1] + Spheres_data[1][0][1] + Spheres_data[2][0][1])
|
||||
zc = 1. / 3 * (Spheres_data[0][0][2] + Spheres_data[1][0][2] + Spheres_data[2][0][2])
|
||||
Addpoint = np.ndarray.tolist(np.array([xc,yc,zc])+np.multiply(Norm,h))
|
||||
Spheres_data.append([Addpoint,1]) # We add one sphere with radius=0, that must be tangent to solution
|
||||
"""
|
||||
|
||||
P1, P2 = esclus.Triangle_ES(Spheres_data, R)
|
||||
if P1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(R, [Index[tria[0]],
|
||||
Index[tria[1]],
|
||||
Index[tria[2]]]))
|
||||
if P1 != 666:
|
||||
FlatCover.append([P1, R])
|
||||
FlatCover.append([P2, R])
|
||||
"""
|
||||
ViewPos=[Spheres_data[0][0],Spheres_data[1][0],Spheres_data[2][0],Addpoint,Cov1[0]]
|
||||
print "preview :",ViewPos
|
||||
Viewer=Atoms("H2OClX",positions=[Spheres_data[0][0],Spheres_data[1][0],Spheres_data[2][0],Addpoint,Cov1[0]])
|
||||
view(Viewer)
|
||||
Spheres_data.remove([Addpoint, 1])
|
||||
Addpoint = np.ndarray.tolist(np.array([xc, yc, zc]) - np.multiply(Norm, h))
|
||||
Spheres_data.append([Addpoint, 1]) # Our
|
||||
print Spheres_data
|
||||
Cov2 = esclus.Tetrahedron_ES (Spheres_data)
|
||||
ViewPos = [Spheres_data[0][0], Spheres_data[1][0], Spheres_data[2][0], Addpoint, Cov2]
|
||||
print "preview :", ViewPos
|
||||
Viewer = Atoms("H2OClX", positions=[Spheres_data[0][0], Spheres_data[1][0], Spheres_data[2][0], Addpoint, Cov2[0]])
|
||||
view(Viewer)
|
||||
FlatCover.append(Cov1[0])
|
||||
FlatCover.append(Cov2[0])
|
||||
|
||||
"""
|
||||
"""
|
||||
for tria in Triang.indices: #compute classic simplice center :
|
||||
x=y=z=0
|
||||
for vertex in tria:
|
||||
x += set[vertex][0] / 3.0
|
||||
y += set[vertex][1] / 3.0
|
||||
z += set[vertex][2] / 3.0
|
||||
simcen=[x,y,z]
|
||||
for simcen in simplicenters :
|
||||
C1=np.ndarray.tolist(np.array(simcen) + np.array(Norm))
|
||||
C2=np.ndarray.tolist(np.array(simcen) - np.array(Norm))
|
||||
FlatCover.append(C1)
|
||||
FlatCover.append(C2)
|
||||
#""" #
|
||||
|
||||
|
||||
|
||||
elif Curved == True: # With curve : Complicated to define planes... So we need to do as if it was pure 2D.
|
||||
# In Case of Curved routine, ALL spheres will be included in Cover iteration.
|
||||
FlatCover = []
|
||||
Plane3D = Structure.positions
|
||||
Plane2D = []
|
||||
Projection_selected = 0
|
||||
while Projection_selected == 0:
|
||||
PProj = input(
|
||||
"To create mesh, we need to project on a plane : please select him.\n1 for x=0\n2 for y=0\n3 for z=0\n")
|
||||
if PProj == 1:
|
||||
for pt in Plane3D:
|
||||
Plane2D.append(pt[1:])
|
||||
Projection_selected = 1
|
||||
elif PProj == 3:
|
||||
for pt in Plane3D:
|
||||
Plane2D.append(pt[:2])
|
||||
Projection_selected = 1
|
||||
elif PProj == 2:
|
||||
for pt in Plane3D:
|
||||
Plane2D.append([pt[0], pt[2]])
|
||||
Projection_selected = 1
|
||||
Triang = delc.Triangulation(Plane2D)
|
||||
if R == 0:
|
||||
R = input("Please select the radius of empty spheres you desire : ")
|
||||
|
||||
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
|
||||
simplicenters = []
|
||||
for tria in Triang.indices:
|
||||
Spheres_data = [Data[tria[0]], Data[tria[1]], Data[tria[2]]]
|
||||
P1, P2 = esclus.Triangle_ES(Spheres_data, R)
|
||||
if P1 == 666:
|
||||
print("For radius {}, no solution of tangent to triangle {} ".format(R, [tria[0], tria[1],
|
||||
tria[2]]))
|
||||
if P1 != 666:
|
||||
FlatCover.append([P1, R])
|
||||
FlatCover.append([P2, R])
|
||||
|
||||
FlatCover = Fusion_Overlap(FlatCover, tol)
|
||||
|
||||
return FlatCover
|
||||
|
||||
|
||||
# ====================================================================
|
||||
def Plane_Triangulation(Plane3D, Plane_eq):
|
||||
# Transform plane into same z
|
||||
Norm = Plane_eq[:3]
|
||||
a, b, c, d = Plane_eq
|
||||
Plane2D = []
|
||||
NormZ0 = [0, 0, 1]
|
||||
Alpha = tool.angle_vector(Norm, NormZ0) # Angle of rotation
|
||||
|
||||
if Alpha % math.pi == 0: # Plane already at z=K : no rotation needed
|
||||
for pt in Plane3D:
|
||||
Plane2D.append(pt[:2])
|
||||
else: # We must see the axis of rotation, then rotate all points :
|
||||
if a == 0:
|
||||
u = [1, 0, 0]
|
||||
M = [-d / b, 0, 0]
|
||||
else:
|
||||
u = [-b / a, 1, 0]
|
||||
M = [-d / a, 0, 0]
|
||||
# print("Norm of Plane = {}, so : \nAlpha = {}° \nu={} and M = {}".format(Norm,Alpha * 180 / math.pi, u,M))
|
||||
# Plane def by ax + by + cz + d = 0. Intercect z=0 at array ax + by + d = 0, so vector directing intercection is (-b,a,0)
|
||||
nu = tool.vector_norm(u)
|
||||
u = np.ndarray.tolist(np.multiply(u, 1. / nu))
|
||||
# print("Intercection is line defined by vector u={} and point p={}\nAngle of rotation will be {}°".format(u,M,Alpha*180/math.pi))
|
||||
# Now rotate all points with rotation angled Alpha round u.
|
||||
Rview = []
|
||||
for pt in Plane3D: # Translate point until axis of rotation include origin : for easier rotation
|
||||
x, y, z = pt
|
||||
Mm = [-M[0], -M[1], -M[2]]
|
||||
pt = tool.vector_trslt(pt, Mm)
|
||||
rpt = tool.rot3D(pt, Alpha, u)
|
||||
rpt = tool.vector_trslt(rpt, M)
|
||||
|
||||
# print("pt = {}, rotate to rpt = {} (Verify same z !)".format(pt,rpt))
|
||||
Rview.append(rpt)
|
||||
Plane2D.append(rpt[:2])
|
||||
|
||||
# Triangulate with Delny : indices will be the same as original Plane : no need to invert transformation
|
||||
Triang = delc.Triangulation(Plane2D)
|
||||
return Triang
|
||||
|
||||
|
||||
# ====================================================================
|
||||
|
||||
# ====================================================================
|
||||
def Atom_Radius(Structure, n, list):
|
||||
# Returns the radius of the n°th atom in Structure. 0 are placed to keep information. Unit = Angstrom
|
||||
# List variable determines wich information we need
|
||||
global ES_AllRadius
|
||||
|
||||
if 0 in Structure.numbers:
|
||||
FirstES = np.ndarray.tolist(Structure.numbers).index(0) # Number of the first Empty_Spheres
|
||||
N = Structure.numbers[n]
|
||||
if N == 0: # Atom is X : empty sphere
|
||||
Atom_Radius = ES_AllRadius[n - FirstES]
|
||||
|
||||
if list == 1: # Covalent Radii
|
||||
Atom_Radius_List = covalent_radii
|
||||
else:
|
||||
print("No list found, verify list variable. Routine returns 0")
|
||||
return 0
|
||||
if Atom_Radius_List[N] == 0:
|
||||
print ("No information on this atom, or false n° given. Routine returns 0")
|
||||
return Atom_Radius_List[N]
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def Convex_Hull_InterCover(set):
|
||||
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
|
||||
# an empty sphere on each hull triangle.
|
||||
# Returns list of cover coordinates
|
||||
reverset = tool.Invert_Coord(set, [0, 0, 0], 2)
|
||||
hull = ConvexHull(reverset)
|
||||
cover_coord = []
|
||||
counter = 0
|
||||
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
|
||||
x = y = z = 0.0
|
||||
for vertex in facet:
|
||||
x += reverset[vertex][0] / 3.0
|
||||
y += reverset[vertex][1] / 3.0
|
||||
z += reverset[vertex][2] / 3.0
|
||||
facet_center = [x, y, z] # Center of the triangular facet
|
||||
normal_facet = hull.equations[counter][:3] # The exterior normal of the facet
|
||||
addpoint = facet_center + normal_facet
|
||||
cover_coord.append(addpoint)
|
||||
counter += 1
|
||||
cover_coord = np.array(cover_coord).tolist()
|
||||
cover_coord = tool.Invert_Coord(cover_coord, [0, 0, 0], 2)
|
||||
return cover_coord
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def lookhull(Structure, hull):
|
||||
# view the structure and his hull
|
||||
hullview = []
|
||||
for i in hull.vertices:
|
||||
hullview.append(Structure.positions[i])
|
||||
L = len(hullview)
|
||||
Lookhull = Atoms(positions=hullview)
|
||||
view(Lookhull)
|
||||
view(Structure + Lookhull)
|
||||
return 0
|
|
@ -0,0 +1,61 @@
|
|||
# coding: utf-8
|
||||
|
||||
import unittest
|
||||
from subprocess import call
|
||||
import numpy as np
|
||||
from ase import Atoms
|
||||
from ase.io import read,write
|
||||
import empty_spheres as esph
|
||||
import es_tools as tool
|
||||
|
||||
#===================================================================
|
||||
|
||||
def Apollonius_CCC(circles_data) :
|
||||
# From the circle_data list defined like : [[list of centers coordinates 3D],[list of radius]]
|
||||
# defines the Apollonius point, equidistant to the 3 circles
|
||||
#___________________________________________________________
|
||||
# Regroup data, and define the case
|
||||
O1=circles_data[0][0]
|
||||
O2=circles_data[0][1]
|
||||
O3=circles_data[0][2]
|
||||
r1 = circles_data[1][0]
|
||||
r2 = circles_data[1][1]
|
||||
r3 = circles_data[1][2]
|
||||
rmin=min(circles_data[1])
|
||||
nbmin=circles_data[1].count(rmin)#So it is the number of circles with the smallest radius
|
||||
if nbmin==1 : #then we have 1 little circle, we go to Apollonius CCP
|
||||
|
||||
elif nbmin==2: #then we have 2 little circles, we go to Apollonius CPP
|
||||
if r1!=rmin :
|
||||
data=[[O1,r1],O2,O3]
|
||||
elif r2!=rmin :
|
||||
data = [[O2, r2], O1, O3]
|
||||
elif r3!=rmin :
|
||||
data = [[O3, r3], O1, O2]
|
||||
Apollo=Apollonius_CPP(data)
|
||||
|
||||
elif nbmin==3 :#then the 3 circles have the same radius, so we search simply the centroid, form Apollonius PPP
|
||||
data=[O1,O2,O3]
|
||||
Apollo=tool.Isobarycenter(data)
|
||||
|
||||
return Apollo
|
||||
|
||||
# ===================================================================
|
||||
def Apollonius_CPP(set) :
|
||||
#From a set define like this : [[Coordinates of circle center,Circle radius],Coord P1, Coord P2]
|
||||
# define the point equidistant to P1, P2 and the circle. We will use circular inversion method
|
||||
Apollo=0
|
||||
return Apollo
|
||||
# ===================================================================
|
||||
def Circular_Inversion (P,O,R) :
|
||||
#Computes P' the image of circular inversion of P by the circle (O,R). It means OP*OP'=R²
|
||||
OP=tool.vector_def(O,P)
|
||||
nOP=tool.vector_norm(OP)
|
||||
OPprim = (OP * R ** 2) / (nOP ** 2)
|
||||
Apollo = np.ndarray.tolist(np.array(OPprim)-np.array(O))
|
||||
return Apollo
|
||||
# ===================================================================
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
# coding: utf-8
|
||||
import unittest
|
||||
from subprocess import call
|
||||
import numpy as np
|
||||
from numpy import sqrt, dot, cross
|
||||
from numpy.linalg import norm
|
||||
from ase import Atoms
|
||||
from ase.io import read, write
|
||||
from ase.visualize import view
|
||||
import empty_spheres as esph
|
||||
import es_tools as tool
|
||||
|
||||
# ===================================================================
|
||||
# List of routines :
|
||||
"""
|
||||
=================
|
||||
Tangent_Fourth_Sphere(Spheres_data, r, inout=1) : From 3 tangent spheres, returns the fourth, tangent to others, with radius r.
|
||||
inout determines the side of the coordinate.
|
||||
Spheres_Data_Structure_Extractor (Structure,list) : From Structure, returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
|
||||
Spheres_Data_XYZ_Extractor (name,list) : From name, given as "_____.xyz", returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
|
||||
Tetrahedron_ES (Spheres_data) : From 4 spheres forming a tetrahedron,returns the sphere tangent to others, with radius r.
|
||||
|
||||
Triangle_ES (Spheres_data,R) : Returns the 2 solutions of tangent of 3 spheres with radius R.
|
||||
=================
|
||||
"""
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def Tangent_Fourth_Sphere(Spheres_data, r, inout=1):
|
||||
# From Spheres_data build like : [[S1],[S2],[S3]] containing spheres informations Si=[[xi,yi,zi],ri], the wanted radius r,
|
||||
# and with the inout variable (equal to 1 or -1, depends on the facet, to build empty sphere in or out the hull)
|
||||
# computes the center of the fourth sphere tangent to S1, S2 and S3, and with radius r
|
||||
# WARNING : Require S1, S2 and S3 are tangent each to another !
|
||||
# ================
|
||||
# Read information
|
||||
S1, S2, S3 = Spheres_data
|
||||
|
||||
d1 = S1[1] + r
|
||||
d2 = S2[1] + r
|
||||
d3 = S3[1] + r
|
||||
# Solve the problem in the right space : using u,v,t as base to solution
|
||||
u = tool.vector_def(S2[0], S1[0])
|
||||
v = tool.vector_def(S3[0], S1[0])
|
||||
unorm = tool.vector_norm(u)
|
||||
vnorm = tool.vector_norm(v)
|
||||
u = np.multiply(u, 1. / unorm)
|
||||
v = np.multiply(v, 1. / vnorm)
|
||||
w = np.multiply(S1[0], -2)
|
||||
# =====
|
||||
a = (d2 ** 2 - d1 ** 2 + S1[0][0] ** 2 - S2[0][0] ** 2 + S1[0][1] ** 2 - S2[0][1] ** 2 + S1[0][2] ** 2 -
|
||||
S2[0][2] ** 2) / (2 * unorm)
|
||||
b = (d3 ** 2 - d1 ** 2 + S1[0][0] ** 2 - S3[0][0] ** 2 + S1[0][1] ** 2 - S3[0][1] ** 2 + S1[0][2] ** 2 -
|
||||
S3[0][2] ** 2) / (2 * vnorm)
|
||||
c = d1 ** 2 - S1[0][0] ** 2 - S1[0][1] ** 2 - S1[0][2] ** 2
|
||||
# =====
|
||||
t = np.ndarray.tolist(np.cross(u, v))
|
||||
tnorm = tool.vector_norm(t)
|
||||
t = np.multiply(t, 1 / tnorm)
|
||||
#
|
||||
alpha = (a - b * np.dot(u, v)) / (1 - np.dot(u, v) ** 2)
|
||||
beta = (b - a * np.dot(u, v)) / (1 - np.dot(u, v) ** 2)
|
||||
d = alpha ** 2 + beta ** 2 + 2 * alpha * beta * np.dot(u, v) + alpha * np.dot(u, w) + beta * np.dot(v, w) - c
|
||||
theta = (-1 * np.dot(w, t) + inout * np.sqrt(np.dot(w, t) ** 2 - 4 * d)) / 2
|
||||
#
|
||||
solution = np.ndarray.tolist(np.multiply(u, alpha) + np.multiply(v, beta) + np.multiply(t, theta))
|
||||
return solution
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Spheres_Data_Structure_Extractor(Structure, list):
|
||||
# From Structure, returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
# used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
# list determines wich radius we take : 1 for covalent
|
||||
|
||||
set_pos = np.ndarray.tolist(Structure.positions)
|
||||
set_nb = np.ndarray.tolist(Structure.numbers)
|
||||
data = []
|
||||
for i in range(0, len(set_nb)):
|
||||
data.append([set_pos[i], esph.Atom_Radius(Structure, i, list)])
|
||||
return data
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Spheres_Data_XYZ_Extractor(name, list):
|
||||
# From name, given as "_____.xyz", returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
|
||||
# used in Tangent_Fourth routine. List determines the radius we will use.
|
||||
# list determines wich radius we take : 1 for covalent
|
||||
set = read(name)
|
||||
set_pos = np.ndarray.tolist(set.positions)
|
||||
set_nb = np.ndarray.tolist(set.numbers)
|
||||
data = []
|
||||
for i in range(0, len(set_nb)):
|
||||
data.append([set_pos[i], esph.Atom_Radius(set, i, list)])
|
||||
return data
|
||||
|
||||
|
||||
# ===================================================================
|
||||
def Tetrahedron_ES(Spheres_data):
|
||||
# print "Spheres datas :",Spheres_data
|
||||
S1, S2, S3, S4 = Spheres_data
|
||||
x1, y1, z1 = S1[0]
|
||||
r1 = S1[1]
|
||||
x2, y2, z2 = S2[0]
|
||||
r2 = S2[1]
|
||||
x3, y3, z3 = S3[0]
|
||||
r3 = S3[1]
|
||||
x4, y4, z4 = S4[0]
|
||||
r4 = S4[1]
|
||||
|
||||
# Solve this problem equals to found V=[x,y,z] and r as : MV = S + r P with :
|
||||
M = np.array([[x1 - x2, y1 - y2, z1 - z2], [x2 - x3, y2 - y3, z2 - z3], [x3 - x4, y3 - y4, z3 - z4]])
|
||||
P = np.array([r2 - r1, r3 - r2, r4 - r3])
|
||||
S = np.array([(r1 ** 2 - r2 ** 2) + (x2 ** 2 - x1 ** 2) + (y2 ** 2 - y1 ** 2) + (z2 ** 2 - z1 ** 2),
|
||||
(r2 ** 2 - r3 ** 2) + (x3 ** 2 - x2 ** 2) + (y3 ** 2 - y2 ** 2) + (z3 ** 2 - z2 ** 2),
|
||||
(r3 ** 2 - r4 ** 2) + (x4 ** 2 - x3 ** 2) + (y4 ** 2 - y3 ** 2) + (z4 ** 2 - z3 ** 2)])
|
||||
S = np.multiply(S, 0.5)
|
||||
|
||||
"""
|
||||
print("M= {}".format(M))
|
||||
print("P= {}".format(P))
|
||||
print("S= {}".format(S))
|
||||
#"""
|
||||
|
||||
# MV = S + r P <=> V = Minv S + r MinvP, rewrite as V = Mbar + r Pbar
|
||||
if np.linalg.det(M) == 0:
|
||||
print("Singular matrix on the Tetrahedron Fourth SPhere Problem : So we cancel routine")
|
||||
return 999
|
||||
|
||||
Minv = np.linalg.inv(M)
|
||||
Sbar = np.matmul(Minv, S)
|
||||
Pbar = np.matmul(Minv, P)
|
||||
"""
|
||||
print("Minv= {}\n So verify inversion : Minv * M = \n{}".format(Minv,np.matmul(M,Minv)))
|
||||
print("Pbar= {}".format(Pbar))
|
||||
print("Sbar= {}".format(Sbar))
|
||||
#"""
|
||||
# V = [x,y,z] depends on r : We need to solve r first : as a root of a 2 degree polygon
|
||||
Vi = np.array(S1[0])
|
||||
ri = np.array(S1[1])
|
||||
D = Sbar - Vi
|
||||
a = np.linalg.norm(Pbar) ** 2 - 1
|
||||
b = 2 * (np.dot(D, P) - ri)
|
||||
c = np.linalg.norm(Sbar) ** 2 - 2 * np.dot(Vi, Sbar) - ri ** 2
|
||||
|
||||
delta = b ** 2 - 4 * a * c # Theorically delta >=0
|
||||
if delta < 0:
|
||||
return 999
|
||||
rsol = (-b - np.sqrt(delta)) / (2 * a) # radius of sphere internally tangent
|
||||
# print("rsol found : {}".format(r))
|
||||
# print("Verify solution : ar² + br + c = {}".format(a * rsol**2 + b * rsol + c))
|
||||
|
||||
# Now we can compute the solution V = x,y,z
|
||||
V = -1 * (Sbar + np.multiply(Pbar, rsol))
|
||||
r = tool.distance(V, Vi) - ri
|
||||
# print("r that should really be : {}".format(r))
|
||||
# print("Verify solution : ar² + br + c = {}".format(a * r ** 2 + b * r + c))
|
||||
return [np.ndarray.tolist(V), r]
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def Triangle_ES(Spheres_data, R):
|
||||
# Routine posted by Andrew Wagner, Thanks to him.
|
||||
# Implementaton based on Wikipedia Trilateration article, about intercection of 3 spheres
|
||||
# This problem solves sphere tangent to 3 spheres : and returns the 2 solutions
|
||||
P1 = np.array(Spheres_data[0][0])
|
||||
r1 = Spheres_data[0][1] + R
|
||||
P2 = np.array(Spheres_data[1][0])
|
||||
r2 = Spheres_data[1][1] + R
|
||||
P3 = np.array(Spheres_data[2][0])
|
||||
r3 = Spheres_data[2][1] + R
|
||||
temp1 = P2 - P1
|
||||
if norm(temp1) == 0:
|
||||
return 666, 666
|
||||
e_x = temp1 / norm(temp1)
|
||||
temp2 = P3 - P1
|
||||
i = dot(e_x, temp2)
|
||||
temp3 = temp2 - i * e_x
|
||||
if norm(temp3) == 0:
|
||||
return 666, 666
|
||||
e_y = temp3 / norm(temp3)
|
||||
e_z = cross(e_x, e_y)
|
||||
d = norm(P2 - P1)
|
||||
j = dot(e_y, temp2)
|
||||
x = (r1 * r1 - r2 * r2 + d * d) / (2 * d)
|
||||
y = (r1 * r1 - r3 * r3 - 2 * i * x + i * i + j * j) / (2 * j)
|
||||
temp4 = r1 * r1 - x * x - y * y
|
||||
if temp4 < 0:
|
||||
# print("The three spheres do not intersect!")
|
||||
return 666, 666
|
||||
else:
|
||||
z = sqrt(temp4)
|
||||
p_12_a = P1 + x * e_x + y * e_y + z * e_z
|
||||
p_12_b = P1 + x * e_x + y * e_y - z * e_z
|
||||
|
||||
p_12_a = np.ndarray.tolist(p_12_a)
|
||||
p_12_b = np.ndarray.tolist(p_12_b)
|
||||
|
||||
return p_12_a, p_12_b
|
||||
|
||||
# ===================================================================
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,553 @@
|
|||
# coding: utf-8
|
||||
|
||||
import unittest
|
||||
import delaunay.core as delc
|
||||
from subprocess import call
|
||||
import numpy as np
|
||||
from scipy.spatial import ConvexHull
|
||||
from ase import Atoms
|
||||
from ase.io import read,write
|
||||
from ase.visualize import view
|
||||
import empty_spheres as esph
|
||||
import es_tools as tool
|
||||
import math
|
||||
#===================================================================
|
||||
# List of routines :
|
||||
"""
|
||||
=================
|
||||
sym_analyse(Cluster) : Convert set into xyz folder, then finds all symetries using. Uses compare_sym and read_sym_file
|
||||
major_plane(set,[multiple]) : Search the major(s) plane with max nb of points in set and returns all of his points, and his equation
|
||||
if multiple = True, returns all Plane equations of planes containing enough points
|
||||
Cluster_flatness_informations(set) : Returns set's hull's total volume and area
|
||||
Cluster_search_hollow(set,tol) : Returns the hollow datas : hollow=[[list,center,volume]...] where list is hollow' vertice's
|
||||
Cluster_emptyness_informations(Structure) : Returns the % of volume of hull occupied by his spheres
|
||||
index,[center,volume] his hollow's center and volume. tol defines hollow's diagonale
|
||||
Vertice_Sphere_Proportion(O,hull) : Returns the proportion of the sphere centered in Oth pt in the hull (proportion in ]O,1])
|
||||
hull_search_neighbors(O,Simplices) : Returns list including O and neighbors (list contains only index, no coordinates)
|
||||
convex_base(Neil,Simplices) : Returns all Neil[0] neighbors so as ConvBase is the base of pyramid from top Neil[0]
|
||||
Neighbors_List(numAtom,Structure) : Returns index list in Structure of neighbors of Atom indexed numAtom. numatom not included
|
||||
Hull_Tetracut(O,Structure,hull) : Returns index list in Structure of centers of spheres defining the terahedron cuting the vertice O
|
||||
facet_cap(O,Structure,hull) : Return the proportion of sphere centered in O out from the hull
|
||||
(ie return the cap proportion of the sphere defined by cuting sphere with hull facets)
|
||||
=================
|
||||
"""
|
||||
#===================================================================
|
||||
def sym_analyse(Cluster) :
|
||||
#This function convert the given set into a xyz folder, then uses modified clus_geom executable to find
|
||||
# the set symmetries. Then, it computes and returns the list of symmetries the list into "sym"
|
||||
#Sym begins with the integer number of symetries, then with all symmetry-names on strings
|
||||
sym=[]
|
||||
write('Cluster.xyz',Cluster) #Sym_Analys folder contains actually the cluster_geom executables
|
||||
call(["./es_mod/Sym_Analys/proc_geom"])
|
||||
sym=read_sym_file('sym_analysis.lis')
|
||||
#For the moment, the file isn't removed...
|
||||
return sym
|
||||
# ===================================================================
|
||||
def compare_sym(Nsym,Osym):
|
||||
# Compare the new list of symmetries Nsym to the old one (Osym).
|
||||
# If the lists are not the same, it tells user to fusion more empty-spheres, to keep sym unchanged
|
||||
# Situation will be defined with output : 1 if symmetry conserved, 0 if not.
|
||||
# If there was no symmetry except identity : (-1). And if symmetries have been gained : 2.
|
||||
N=Nsym[0]
|
||||
O=Osym[0]
|
||||
print("Old number of sym :",O)
|
||||
print("New number of sym :", N)
|
||||
if N>O :
|
||||
output=2
|
||||
print "Symmetries gained. Symmetry-list updated"
|
||||
elif O==1 :
|
||||
output=-1
|
||||
print "No symmetry existing to help at decision"
|
||||
elif N == O:
|
||||
output=1
|
||||
print "Symmetry has been conserved."
|
||||
elif N<O :
|
||||
output=0
|
||||
print "Symmetries Lost: Fusion-Operation on last empty-spheres advised"
|
||||
return output
|
||||
# ===================================================================
|
||||
def read_sym_file(symfile) :
|
||||
#The function reads the .lis file. The first argument is a 2space-int, after this,
|
||||
# we have 5 space-string. Each time, we have to delete the \n readed
|
||||
sym = []
|
||||
file=open(symfile,"r")
|
||||
NbSym=int(file.read(2))
|
||||
sym.append(NbSym)
|
||||
delete=file.read(1)#to delete the \n
|
||||
for i in range(0,NbSym) :
|
||||
Namesym=str(file.read(5))#read the 5 sym-name char, + the 2 empty spaces as char
|
||||
delete=file.read(1)#to delete the \n
|
||||
sym.append(Namesym)
|
||||
file.close()
|
||||
return sym
|
||||
# ===================================================================
|
||||
def major_plane(set,multiple=False) :
|
||||
#This function search the major plane where we can find the maximum of set-points.
|
||||
#In case of a draw, it takes the plane where the points are the closest (to be conform to molecules planes)
|
||||
l=len(set)
|
||||
PlanesLen=[]
|
||||
#We will use this lists :
|
||||
AllPlanes=[]
|
||||
Plane_list=[]#List of all planes, defined by every points in this plane
|
||||
Plane_eq=[] #List of 4-uplet corresponding to plane-equation for each Plane. Linked to Plane_list
|
||||
#
|
||||
#Regroup every unordered combinaisons of 3-uplet from set :
|
||||
Pts_Combi=combinaison_3_noorder_norepeat(set)
|
||||
#print("Pts_Combi={}".format(Pts_Combi))
|
||||
|
||||
#For every Plane in Pts_Combi, define plane equation from the 3 pts defining P
|
||||
for P in Pts_Combi :
|
||||
#print("3 points Combi studied : {}".format(P))
|
||||
#Check caracteristics of the plane P, then update Plane_list and Plane_eq
|
||||
v1=tool.vector_def(P[0],P[1])
|
||||
n1=tool.vector_norm(v1)
|
||||
if n1 == 0 :
|
||||
print("n1 = 0 on this Pt Combi :{}\nVerify no double coordinates given".format(P))
|
||||
u1=np.ndarray.tolist(np.multiply(v1,1./n1))
|
||||
v2 = tool.vector_def(P[0],P[2])
|
||||
n2 = tool.vector_norm(v2)
|
||||
if n2 == 0 :
|
||||
print("n2 = 0 on this Pt Combi :{}\nVerify no double coordinates given".format(P))
|
||||
u2 = np.ndarray.tolist(np.multiply(v2, 1. / n2))
|
||||
#print("The 2vectors for the potential plane : \nv1={}, of norm{}, normed to {}\nv2={},of norm{}, normed to {}".format(v1,n1,u1,v2,n2,u2))
|
||||
|
||||
if tool.ColinearTest(u1,u2) != 1 :#IE the 3 points are not aligned
|
||||
norm=np.cross(v1,v2) #normal vector to the plane
|
||||
#print("u1 :{}\nu2 :{}\n So norm is {} ".format(u1,u2,norm))
|
||||
a, b, c = norm
|
||||
d=np.dot(norm,P[2])
|
||||
Equation=[a,b,c,d]
|
||||
|
||||
"""print("Equation of plane found : {}\nFrom Combi {}\n".format(Equation,P))
|
||||
if abs(a)+abs(b)+abs(c)==0 :
|
||||
print("More Details :\n\nv1 = {}\nv2={}\nSo norm ={}\n\n".format(v1,v2,norm))
|
||||
#"""
|
||||
|
||||
# Add points on Plane list
|
||||
|
||||
if Equation not in Plane_eq :
|
||||
Plane_pts=[]
|
||||
Plane_eq.append(Equation)
|
||||
for pt in set :
|
||||
x, y, z = pt
|
||||
#print("Pt : {}, Eq : {}\nSo ax +by + cz +d = {}".format(pt, Equation, a * x + b * y + c * z + d))
|
||||
if abs(a * x + b * y + c * z + d) < 0.02:
|
||||
Plane_pts.append(pt)
|
||||
Plane_list.append(Plane_pts)
|
||||
PlanesLen.append(len(Plane_pts))
|
||||
|
||||
#Else : Case already found
|
||||
|
||||
#Find now the plane with the most points in :
|
||||
|
||||
#print("Plane_list = {}".format(Plane_list))
|
||||
|
||||
|
||||
if multiple == False :
|
||||
|
||||
Plane = Plane_list[0]
|
||||
|
||||
for P in Plane_list[1:]:
|
||||
if len(P) > len(Plane):
|
||||
Plane = P
|
||||
elif len(P) == len(Plane):
|
||||
if tool.longest_dist(P) < tool.longest_dist(Plane):
|
||||
Plane = P
|
||||
# Else, Plane remains the one with the most and the nearest points
|
||||
#
|
||||
index = Plane_list.index(Plane)
|
||||
Plane_eq = Plane_eq[index]
|
||||
return [Plane, Plane_eq]
|
||||
|
||||
elif multiple == True :
|
||||
#print("We have Planelist at {} element and Planeeq at {} elements".format(len(Plane_list),len(Plane_eq)))
|
||||
CleanLen=tool.cleanlist(PlanesLen)
|
||||
print("Planes include number of spheres in {}".format(sorted(CleanLen)))
|
||||
planesize = input("Please select minimal size of plane we should take : ")
|
||||
AllPlanes = []
|
||||
|
||||
for P in Plane_list :
|
||||
if len(P) >= planesize :
|
||||
index=Plane_list.index(P)
|
||||
PEQ=Plane_eq[index]
|
||||
|
||||
#AllPlanes.append(PEQ)
|
||||
AllPlanes.append([P,PEQ])
|
||||
"""
|
||||
print("In total we have {} planes".format(len(AllPlanes)))
|
||||
for P in AllPlanes:
|
||||
print("P n°{} : Equation : {}\nPoints : {}\n".format(AllPlanes.index(P) + 1, P[1],P[0]))
|
||||
|
||||
#"""
|
||||
|
||||
#Clean Double Planes : ax + by + cz + d = 0 <=> -ax -by -cz -d =0
|
||||
|
||||
AllPlanesIndex = range(len(AllPlanes))
|
||||
Output = []
|
||||
|
||||
for iP in range(len(AllPlanes)):
|
||||
if iP in AllPlanesIndex:
|
||||
for iOP in range(iP + 1, len(AllPlanes)):
|
||||
Plist = AllPlanes[iP][0]
|
||||
OPlist = AllPlanes[iOP][0]
|
||||
if Plist == OPlist:
|
||||
AllPlanesIndex.remove(iOP) # P and OP are same Planes as they include same points
|
||||
for I in AllPlanesIndex:
|
||||
Output.append(AllPlanes[I])
|
||||
|
||||
return Output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def combinaison_3_noorder_norepeat(set) :
|
||||
#returns every 3-uplet combinaison of the set, with no repetition, and no order variation (ie :{1,2,3}={1,3,2})
|
||||
l=len(set)
|
||||
Combi = []
|
||||
for i in range(0, l - 2):
|
||||
for j in range(i + 1, l - 1):
|
||||
for k in range(j + 1, l):
|
||||
Combi.append([set[i], set[j], set[k]])
|
||||
return Combi
|
||||
# ===================================================================
|
||||
def Cluster_flatness_informations(set) :
|
||||
#returns the total volume and area of the set's hull, then
|
||||
hull= ConvexHull(set)
|
||||
set_area=hull.area
|
||||
set_volume=hull.volume
|
||||
return[set_volume,set_area]
|
||||
# ===================================================================
|
||||
def Cluster_search_hollow(set,tol) :
|
||||
#Returns the hollow datas : hollow=[[[list],center,volume]...] where list is hollow vertices, and center and volume respectively
|
||||
#his center and volume.'Tol' can be 2 (big) or sqrt(3) to consider cubes as hollow, or even sqrt(2) to consider terahedrons as hollow
|
||||
#Notice that no hollows are detected fo a pentagone, due to his particular alignment of vertices...
|
||||
#Compute Centroid of Cluster :
|
||||
dmin = tool.shortest_dist(set)
|
||||
#Search for hollows
|
||||
hollow = []
|
||||
L=len(set)
|
||||
for i in range(0,L-1):
|
||||
for j in range(i+1,L) :
|
||||
#Data print (debug) :
|
||||
P1=set[i]
|
||||
P2=set[j]
|
||||
print("i,j : {},{}\nP1 :{}\nP2 :{}\ndistance(P1,P2):{}\n dmin={} so tol becomes {}\n".format(i+1,j+1,P1,P2,tool.distance(P1,P2),dmin,tol*0.99*dmin))
|
||||
if tool.distance(P1,P2) > 0.99*tol * dmin :#Then we may have find a hole center between P1 and P2
|
||||
hcenter=tool.Midpoint(P1,P2)
|
||||
d = tool.distance(P1,P2)/2
|
||||
print("hcenter is distant form set to {}...\nMust be > {}".format(tool.point_set_proximity(hcenter, set),0.98*d))
|
||||
if tool.point_set_proximity(hcenter, set) > 0.98*d :#We have a hollow there :
|
||||
hollow_list=[]
|
||||
hollowinvset=tool.Invert_Coord(set,hcenter,10)#10 is just decent : the volumewill be multiplied by 1000
|
||||
hollowhull=ConvexHull(hollowinvset)
|
||||
hollow_index =np.ndarray.tolist(hollowhull.vertices)
|
||||
hollow_volume=hollowhull.volume/1000
|
||||
hollow.append([hollow_index,hcenter,hollow_volume])
|
||||
print("Hollow founded :{}\n".format(hcenter))
|
||||
#
|
||||
#else : no hollow finally
|
||||
# else : P1 and P2 pretty neighboors
|
||||
#
|
||||
#
|
||||
return hollow
|
||||
# ===================================================================
|
||||
def Cluster_emptyness_informations(Structure) :
|
||||
#returns the % of volume of hull occupied by his spheres
|
||||
Allproportions=[]
|
||||
set=Structure.positions
|
||||
hull= ConvexHull(set)
|
||||
#esph.lookhull(Structure,hull)
|
||||
set_volume=hull.volume
|
||||
spheres_volume=0
|
||||
for pt in range(0,len(set)) : #Study all set point from index.
|
||||
print("Pt n°{}".format(pt))
|
||||
if pt in hull.vertices :
|
||||
#pt is a boundary point. We need to know the proportion of the sphere in the hull.
|
||||
proportion=Vertice_Sphere_Proportion(pt,hull,Structure)
|
||||
else : #We must verify sphere is completely is the cluster or not : it can have a cap out :
|
||||
NeibPt=Neighbors_List(pt,Structure)
|
||||
NeibInHull=tool.commonlist(NeibPt,hull.vertices)
|
||||
if len(NeibInHull) != 0 :#We must search the cap cutting the sphere.
|
||||
cap=facet_cap(pt,Structure,hull)
|
||||
proportion = 1 - cap
|
||||
else :
|
||||
proportion = 1
|
||||
|
||||
previous = spheres_volume
|
||||
spheres_volume += proportion * (4 * math.pi / 3) * (esph.Atom_Radius(Structure,pt,1)**3)
|
||||
print("So sphere n°{} is at proportion {} in hull : We add {} to total sphere volume, being at {} now".format(pt,proportion,spheres_volume-previous,spheres_volume))
|
||||
raw_input("\nPress Enter to continue ...\n")
|
||||
|
||||
Allproportions.append(proportion)
|
||||
print "AllProportions : ",Allproportions
|
||||
|
||||
print("Hull volume = {}".format(set_volume))
|
||||
return spheres_volume / set_volume * 100
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
def Vertice_Sphere_Proportion(O,hull,Structure) :
|
||||
#Returns the proportion of the sphere centered in O in the hull (proportion in ]O,1]).
|
||||
#O is the index of the center in hull vertices list.
|
||||
R = esph.Atom_Radius(Structure, O, 1) # Radius of O
|
||||
Vertices_list = np.ndarray.tolist(hull.vertices)
|
||||
Simplices_list = np.ndarray.tolist(hull.simplices)
|
||||
Point_list = np.ndarray.tolist(hull.points)
|
||||
Norm_list = np.ndarray.tolist(hull.equations)
|
||||
#print("Vertices : {}\nSimplices :{}\nPoints : {}\nO :{}".format(Vertices_list,Simplices_list,Point_list,O))
|
||||
Proportion = 0 #initialisation
|
||||
Neighbors=Hull_Neighbors_List(O,Structure,hull)
|
||||
print("Neighbors :{}".format(Neighbors))
|
||||
#Neighbors=tool.commonlist(Neighbors,Vertices_list)#We delete all neighbors not included in the hull
|
||||
Neighbors.insert(0,O)
|
||||
#print("Neighbors in hull :{}".format(Neighbors))
|
||||
|
||||
if len(Neighbors) == 3 : #O is on a ridge
|
||||
Pt=[]
|
||||
for pt in Neighbors :
|
||||
Pt.append(hull.points[pt])
|
||||
Ox = tool.vector_def(Pt[0],Pt[1])
|
||||
Oy = tool.vector_def(Pt[0], Pt[2])
|
||||
Alpha=tool.angle_vector(Ox,Oy)
|
||||
Proportion=Alpha / (2 * math.pi)
|
||||
|
||||
else :
|
||||
|
||||
if len(Neighbors) < 3 : #There is an error here
|
||||
print("Hull is strangely defined : you can check on view")
|
||||
#Test if O is in a big facet :
|
||||
norms=[]
|
||||
for Sim in Simplices_list :
|
||||
if O in Sim :
|
||||
indx=Simplices_list.index(Sim)
|
||||
norm=Norm_list[indx][:3]
|
||||
if norm not in norms :
|
||||
norms.append(norm)
|
||||
if len(norms)==1 : #Only one norm for all facets containing O : O in center of a big facet
|
||||
Proportion = 0.5
|
||||
else : #Here the work begins : O top of a pyramid
|
||||
|
||||
Triag = delc.Triangulation(Structure.positions)
|
||||
for tetra in Triag.indices :
|
||||
if O in tetra: #A tetrahedron containing O : O will be counted as top
|
||||
print("Tetraedron found with {} in : {}".format(O,tetra))
|
||||
tetra.remove(O)
|
||||
P1 = Point_list[tetra[0]]
|
||||
P2 = Point_list[tetra[1]]
|
||||
P3 = Point_list[tetra[2]]
|
||||
#View tetrahedron :
|
||||
Tetraview=Atoms("XH3",positions=[Point_list[O],P1,P2,P3])
|
||||
view(Structure+Tetraview)
|
||||
V1 = tool.vector_def(Point_list[O], P1)
|
||||
V2 = tool.vector_def(Point_list[O], P2)
|
||||
V3 = tool.vector_def(Point_list[O], P3)
|
||||
print "Vectors from top of pyramid to base points" , [V1, V2, V3]
|
||||
a = tool.angle_vector(V1, V2)
|
||||
b = tool.angle_vector(V2, V3)
|
||||
c = tool.angle_vector(V3, V1)
|
||||
|
||||
# """____________________________________________________________
|
||||
# L'Huilier Theorem :
|
||||
p = (a + b + c) / 2
|
||||
#print("p = {} , or {}° , Angles : {}, {}, and {}".format(p,p*180/math.pi,a*180/math.pi,b*180/math.pi,c*180/math.pi))
|
||||
S = 4 * np.arctan(
|
||||
np.sqrt(np.tan(p / 2) * np.tan((p - a) / 2) * np.tan((p - b) / 2) * np.tan((p - c) / 2)))
|
||||
#print("L'huilier pocess :\nS =4arctan( Sqrt( tan(p/2)*tan(p-a/2)* tan(p-b/2) * tan(p-c/2) ) )\n =4arctan(sqrt(tan({})*tan({})*tan({})*tan({})))\n =4arctan(sqrt({}*{}*{}*{}))\n =4arctan({})\n = {} \n\n Sphere Volume = {}, so proportion = {}".format(p/2,(p-a)/2,(p-b)/2,(p-c)/2,np.tan(p/2),np.tan((p-a)/2),np.tan((p-b)/2),np.tan((p-c)/2),np.tan(p/2)*np.tan((p-a)/2)*np.tan((p-b)/2)*np.tan((p-c)/2),S,(4 * math.pi),S / (4 * math.pi)))
|
||||
print("S with Huilier = {}".format(S))
|
||||
# """____________________________________________________________
|
||||
|
||||
# """____________________________________________________________
|
||||
# Spherical area computing : Sinus formula + Girard's formula
|
||||
cosT = (np.cos(c) - np.cos(a)*np.cos(b)) / (np.sin(a)*np.sin(b))
|
||||
Theta = np.arccos(cosT)
|
||||
sinT = np.sin(Theta)
|
||||
sinA = np.sin(a) * sinT / np.sin(c)
|
||||
sinB = np.sin(b) * sinT / np.sin(c)
|
||||
print("sinB detail : b = {}, sin(b) = {}, sinT = {},c = {} sin(c) = {}\So : sinB = {}".format(b,np.sin(b),sinT,c,np.sin(c),sinB))
|
||||
if sinA >=1 :
|
||||
Alpha=math.pi/2
|
||||
else :
|
||||
Alpha = np.arcsin(sinA)
|
||||
if sinB >=1 :
|
||||
Beta=math.pi/2
|
||||
else :
|
||||
Beta = np.arcsin(sinB)
|
||||
S = (Alpha + Beta + Theta - math.pi)
|
||||
print("S with sinus and Girard's formulaes = {}".format(S))
|
||||
#"""____________________________________________________________
|
||||
|
||||
|
||||
|
||||
Proportion += S / (4 * math.pi)
|
||||
print("We had {} to actual {} Proportion, being now at {}".format(S / (4*math.pi), Proportion - S / (4 * math.pi), Proportion))
|
||||
#else the tetraedron in disconnected to the center of sphere : no need to study him
|
||||
|
||||
print("proportion : {}".format(Proportion, 1. / Proportion))
|
||||
|
||||
"""Little thing to see exactly what happens
|
||||
print("proportion : {} , or 1/{}".format(Proportion, 1./Proportion))
|
||||
Lset=len(Neighbors)-1
|
||||
set = []
|
||||
Lhul=len(np.ndarray.tolist(hull.points))
|
||||
namehull= "C" +str(Lhul)
|
||||
HullView=Atoms(namehull,positions=np.ndarray.tolist(hull.points))
|
||||
for pt in Neighbors:
|
||||
set.append(Point_list[pt])
|
||||
set.remove(Point_list[O])
|
||||
nameset= "H" + str(Lset)
|
||||
ViewNeig=Atoms(nameset,positions=set)
|
||||
Ostr=Atoms(positions=[Point_list[O]])
|
||||
view(HullView+ViewNeig+Ostr)
|
||||
#"""
|
||||
|
||||
return Proportion
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def hull_search_neighbors(O,Simplices) :
|
||||
#Returns the list including O in fist position and all of his neighbors (list contains only index, no coordinates)
|
||||
Neil=[O]
|
||||
print("O in the search neighbors routine :",O)
|
||||
for facet in Simplices :
|
||||
if O in facet :
|
||||
for i in facet :
|
||||
if i not in Neil :
|
||||
Neil.append(i)
|
||||
#else : i still in neighbors list
|
||||
return Neil
|
||||
# ===================================================================
|
||||
|
||||
def convex_base(Neil,Simplices) :
|
||||
#Order the neighbors list by listing them so as each consecutives elements in the list are themselves neighbors.
|
||||
#So it returns all Neil[0] neighbors so as ConvBase is the base of pyramid from top Neil[0]
|
||||
ConvBase=[]
|
||||
ConvBase.append(Neil[1])
|
||||
Rest=Neil[2:]
|
||||
print("Initial Neil : ",Neil)
|
||||
while len(Rest)>0 :
|
||||
for i in Rest :
|
||||
print("Convbase :{}\nRest :{}\nactual i:{}".format(ConvBase, Rest,i))
|
||||
Last=ConvBase[-1]
|
||||
print(Last)
|
||||
Neighborsi=hull_search_neighbors(i,Simplices)
|
||||
print ("The last elements :{}\nHis neighbors : {}".format(ConvBase[-1],Neighborsi))
|
||||
if ConvBase[-1] in Neighborsi :
|
||||
ConvBase.append(i)
|
||||
Rest.remove(i)
|
||||
|
||||
return ConvBase
|
||||
|
||||
# ===================================================================
|
||||
|
||||
def Neighbors_List(numAtom,Structure) :
|
||||
# Returns the list of all atom's indexes in Structure wich are neighbors of Atom indexed numAtom (numAtom must be int, 0 included)
|
||||
set=np.ndarray.tolist(Structure.positions)
|
||||
radAtom=esph.Atom_Radius(Structure,numAtom,1)
|
||||
posAtom=set[numAtom]
|
||||
Neilist=[]
|
||||
for i in range(0,len(set)) :
|
||||
int(i)
|
||||
radi=esph.Atom_Radius(Structure,i,1)
|
||||
D = tool.distance(set[i],posAtom) - radi - radAtom
|
||||
if D < 0.1 :
|
||||
Neilist.append(i)
|
||||
Neilist.remove(numAtom)
|
||||
return Neilist
|
||||
|
||||
# ===================================================================
|
||||
def Hull_Neighbors_List(O,Structure,hull) :
|
||||
# Returns the list of all atom's indexes in hull wich are neighbors of Atom indexed numAtom (numAtom must be int, 0 included)
|
||||
Allfacets = np.ndarray.tolist(hull.simplices)
|
||||
Hull_Neilist = []
|
||||
print Allfacets
|
||||
for facet in Allfacets :
|
||||
if O in facet :
|
||||
for index in facet :
|
||||
if index not in Hull_Neilist :
|
||||
Hull_Neilist.append(index)
|
||||
Hull_Neilist.remove(O)
|
||||
return Hull_Neilist
|
||||
# ===================================================================
|
||||
|
||||
def facet_cap(O,Structure,hull) :
|
||||
#From the hull, the Structure and the index of the sphere center O (int), return the proportion of sphere cuted by hull facets
|
||||
#(ie the caps out of the hull). cap is returned proportionnal to total sphere volume (ie cap in [0,1[)
|
||||
cap = 0
|
||||
Center = Structure.positions[O]
|
||||
Radius = esph.Atom_Radius(Structure,O,1)
|
||||
AllFacets = np.ndarray.tolist(hull.equations)
|
||||
hullplan=tool.cleanlist(AllFacets)
|
||||
cutplan = [] # List of all cuting plan equations
|
||||
cuthigh = [] # List of cuting high, corelated to cutplan
|
||||
cutpoint = [] #List containing for each plan one point of the hull included in this plan (used for overlap routine)
|
||||
for facet_plan in AllFacets:
|
||||
d=tool.dist_point_plan(Center,facet_plan)
|
||||
if d<Radius : #Then we can have a cap out : but first verify that the facet isn't to far away
|
||||
plan_index = AllFacets.index(facet_plan)
|
||||
FacetPtsIndex = np.ndarray.tolist(hull.simplices[plan_index])
|
||||
FacetPtsCoord=[]
|
||||
for facetpt in FacetPtsIndex :
|
||||
FacetPtsCoord.append(np.ndarray.tolist(hull.points[facetpt]))
|
||||
Facetnorm=facet_plan[:3]
|
||||
GoOnPlan=np.multiply(Facetnorm,d/tool.vector_norm(Facetnorm))
|
||||
OrthoProjO = np.ndarray.tolist(hull.points[O] + GoOnPlan) #OrthoProjO is the orthogonal projection of O on facet_plan
|
||||
# Sphere cut by facet only if OrtoProjO is in the facet : sum of angles with facet points must be 2*pi
|
||||
V0 = tool.vector_def(OrthoProjO, FacetPtsCoord[0])
|
||||
V1 = tool.vector_def(OrthoProjO, FacetPtsCoord[1])
|
||||
V2 = tool.vector_def(OrthoProjO, FacetPtsCoord[2])
|
||||
SumAngle= np.abs(tool.angle_vector(V0,V1))+np.abs(tool.angle_vector(V0,V2))+np.abs(tool.angle_vector(V1,V2))
|
||||
print("Sum of angle : {} = {}°".format(SumAngle,SumAngle*180/math.pi))
|
||||
|
||||
if SumAngle > 1.99 * math.pi :#considering a little error here from 1% : because soon OrthoProjO is in a facet ridge
|
||||
if facet_plan not in cutplan :#If OrthoProjO in a ridge, the plan can be counted twice or more...
|
||||
h=Radius-d
|
||||
cutplan.append(facet_plan)
|
||||
cuthigh.append(h)
|
||||
cutpoint.append(FacetPtsCoord[0]) # Just one point is enough :
|
||||
|
||||
# Draw to see better :===================
|
||||
Draw = FacetPtsCoord
|
||||
Draw.append(np.ndarray.tolist(hull.points[O]))
|
||||
Draw.append(OrthoProjO)
|
||||
DrawView = Atoms("X3CH", positions=Draw)
|
||||
view(DrawView)
|
||||
# =======================================
|
||||
|
||||
|
||||
|
||||
"""________________________________________________________________
|
||||
# Little thing to see exactly what happens
|
||||
hullview = []
|
||||
for i in hull.vertices:
|
||||
hullview.append(Structure.positions[i])
|
||||
L = len(hullview)
|
||||
Lookhull = Atoms(positions=hullview)
|
||||
set = []
|
||||
for ffp in AllFacets :
|
||||
if ffp == cutplan :
|
||||
FacetPts = np.ndarray.tolist(hull.simplices[plan_index])
|
||||
for pt in FacetPts:
|
||||
if pt not in set :
|
||||
set.append(np.ndarray.tolist(hull.points[pt]))
|
||||
nameset = "H" + str(len(set))
|
||||
ViewFacet = Atoms(nameset, positions=set)
|
||||
Ostr = Atoms("C", positions=[np.ndarray.tolist(hull.points[O])])
|
||||
view(Lookhull + ViewFacet + Ostr)
|
||||
# __________________________________________________________________"""
|
||||
|
||||
if len(cutplan) <1 :
|
||||
print("Finally no plan cutting our sphere...\n\n" )
|
||||
|
||||
if len(cutplan) == 1 :#Only one plan cuting sphere : One cap to be calculated :
|
||||
h = cuthigh[0]
|
||||
print("Sphere (radius {}) is cuted by one plan at cap from high {}\n Plan equation :{} ".format(Radius, h,
|
||||
facet_plan))
|
||||
|
||||
Vtot = 4 * math.pi * (Radius ** 3) / 3
|
||||
Vcap = math.pi * (h ** 2) / 3 * (3 * Radius - h)
|
||||
cap += Vcap / Vtot
|
||||
|
||||
if len(cutplan) > 1:
|
||||
print("We have {} plans cutting the sphere.... We have to calculate overlap... ".format(len(cutplan)))
|
||||
|
||||
return cap
|
|
@ -0,0 +1,278 @@
|
|||
# coding: utf-8
|
||||
from ase import Atoms
|
||||
from ase.visualize import view
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
# List of tools :
|
||||
"""
|
||||
=================Vector tools==================
|
||||
vector_def(A,B) : returns simply the vector translating A to B
|
||||
vector_norm(V) : return euclidian norm of a vector
|
||||
vector_trslt(P,V) : return image of translation of P from vector V
|
||||
throw_away (P,O,d) : returns P' so as O,P and P' are aligned, and OP'=d. So it translate P from O with distance d to direction OP
|
||||
angle_vector(u,v) : returns the value of the convex angle defined by vectors u and v, in radians.
|
||||
ColinearTest(u,v) : returns 1 if u and v colinear, and 0 if not.
|
||||
|
||||
===========Distance and Proximity tools========
|
||||
distance(a,b) : calculate distance between 2 points
|
||||
search_nearest(point,set,d) : search the point in set wich is the nearest from point. We must know that the min distance is d
|
||||
point_set_proximity(point, set) : returns the min distance between the point and the set of points
|
||||
set_set_proximity(S1,S2) : returns minimal distance between each points of each sets.
|
||||
longest_dist(set) : returns the longest distance between the points in the set
|
||||
shortest_dist(set) : returns the shortest distance between the points in the set
|
||||
dist_point_plan(Pt,Plan) : From Pt=[x,y,z] and Plan=[a,b,c,d], return distance beetween Pt and Plan
|
||||
|
||||
===============Construction tools===============
|
||||
Isobarycenter(set) : Calculate isobarycenter of a set of points. Returns his coordinates
|
||||
Invert_Coord(set,O,r) : Apply circular inversion to every point in the set, excepted for origin, remaining origin.
|
||||
Midpoint(P1,P2) : Computes the middle point of P1 and P2
|
||||
rot3D(P,A,u) : Returns P' image of P by rotation from angle A around unity vector u
|
||||
===================Data tools===================
|
||||
commonlist(L1,L2) : Returns a list of elements common to L1 and L2, ie output is L1 intercection with L2
|
||||
cleanlist(list) : Returns a list without repeated elements (each element in cleaned list appears only once)
|
||||
"""
|
||||
|
||||
|
||||
# ========================Vector Tools=============================
|
||||
def vector_def(A, B):
|
||||
# returns simply the vector translating A to B
|
||||
l = len(A)
|
||||
if l != len(B):
|
||||
print("Major Error : The 2 given points aren't same dimensioned :\nA = {}\nB={}".format(A, B))
|
||||
V = []
|
||||
for i in range(0, l):
|
||||
V.append(B[i] - A[i])
|
||||
return V
|
||||
|
||||
|
||||
# ==========================================
|
||||
def vector_norm(V):
|
||||
# return euclidian norm of a vector
|
||||
l = (len(V))
|
||||
Origin = np.ndarray.tolist(np.zeros(l))
|
||||
return distance(V, Origin)
|
||||
|
||||
|
||||
# ==========================================
|
||||
def vector_trslt(P, V):
|
||||
x, y, z = P
|
||||
a, b, c = V
|
||||
return [x + a, y + b, z + c]
|
||||
|
||||
|
||||
# ==========================================
|
||||
def throw_away(P, O, d):
|
||||
# returns P' so as O,P and P' are aligned, and OP'=d. So it translate P from O with distance d to direction OP
|
||||
OP = tool.vector_def(O, P)
|
||||
OP = np.ndarray.tolist(np.multiply(OP, 1. / tool.vector_norm(OP)))
|
||||
output = np.ndarray.tolist(np.multiply(OP, d))
|
||||
return output
|
||||
|
||||
|
||||
# ==========================================
|
||||
def angle_vector(u, v):
|
||||
# returns the value in radian of the convex angle defined by vectors u and v.
|
||||
U = vector_norm(u)
|
||||
V = vector_norm(v)
|
||||
UV = np.dot(u, v)
|
||||
# print("U : {}\nV : {}\n u.v : {}".format(U,U,UV,))
|
||||
if UV == 0:
|
||||
# print("u.v = 0, on a donc un angle de {}, soit {}°".format(math.pi/2,math.pi/2*180/math.pi))
|
||||
return math.pi / 2
|
||||
else:
|
||||
# print("L'angle est donc donc arccos({}) = {} = {}°".format(UV / (U * V),np.arccos(UV/(U*V)),np.arccos(UV/(U*V))*180/math.pi))
|
||||
return np.arccos(UV / (U * V))
|
||||
|
||||
|
||||
# ==========================================
|
||||
def ColinearTest(u, v):
|
||||
# Tests if u and v colinear
|
||||
L = len(u)
|
||||
k = 999999999999 # initial value, to be sure first ktest will become k
|
||||
if L != len(v):
|
||||
print(
|
||||
"Error : u and v ant same dimension : \nu={}\nv={}\nSo we return u and v not aligned... but verify consequences".format(
|
||||
u, v))
|
||||
else:
|
||||
for i in range(0, L):
|
||||
if u[i] == 0 or v[i] == 0:
|
||||
if u[i] != 0 or v[i] != 0:
|
||||
return 1
|
||||
else:
|
||||
ktest = u[i] / v[i]
|
||||
if k == 999999999999:
|
||||
k = ktest
|
||||
elif np.abs(k - ktest) < 0.0000001: # We accept almost colinearité at 1/10^6
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
# ==========================================
|
||||
|
||||
# ==========================================
|
||||
# ==========================================
|
||||
# ==============Distance and Proximity Tools=======================
|
||||
def distance(a, b):
|
||||
# Calculate distance between 2 points
|
||||
d = 0.0
|
||||
dim = len(a)
|
||||
for i in range(0, dim):
|
||||
d += (b[i] - a[i]) ** 2
|
||||
d = np.sqrt(d)
|
||||
return d
|
||||
|
||||
|
||||
# ==========================================
|
||||
def search_nearest(point, set, d):
|
||||
# search the point in set wich is the nearest from point. We must know that the min distance is d
|
||||
for p in set:
|
||||
if distance(point, p) == d:
|
||||
return p
|
||||
|
||||
|
||||
# ==========================================
|
||||
# ==========================================
|
||||
def point_set_proximity(point, set):
|
||||
# returns the min distance between the point and the set of points
|
||||
d = distance(point, set[0])
|
||||
for p in set[1:]:
|
||||
d = min(d, distance(point, p))
|
||||
return d
|
||||
|
||||
|
||||
# ==========================================
|
||||
def set_set_proximity(S1, S2):
|
||||
# returns minimal distance between each points of each sets.
|
||||
d = point_set_proximity(S1[0], S2)
|
||||
for s in S1[1:]:
|
||||
d = min(d, point_set_proximity(s, S2))
|
||||
return d
|
||||
|
||||
|
||||
# ==========================================
|
||||
def longest_dist(set):
|
||||
# returns the longest distance between the points in the set
|
||||
L = len(set)
|
||||
if L < 2:
|
||||
print("Major ERROR : the set has become a 1-UPLET")
|
||||
quit()
|
||||
else:
|
||||
d = distance(set[L - 2], set[L - 1]) # The last ones
|
||||
for i in range(0, L - 2):
|
||||
for j in range(i + 1, L - 2):
|
||||
d = max(d, distance(set[i], set[j]))
|
||||
return d
|
||||
|
||||
|
||||
# ==========================================
|
||||
def shortest_dist(set):
|
||||
# returns the shortest distance between the points in the set
|
||||
L = len(set)
|
||||
if L < 2:
|
||||
print("Major ERROR : the set has become a 1-UPLET")
|
||||
quit()
|
||||
else:
|
||||
d = distance(set[L - 2], set[L - 1]) # The last ones
|
||||
for i in range(0, L - 2):
|
||||
for j in range(i + 1, L - 2):
|
||||
d = min(d, distance(set[i], set[j]))
|
||||
return d
|
||||
|
||||
|
||||
# ==========================================
|
||||
def dist_point_plan(Pt, Plan):
|
||||
# From Pt=[x,y,z] and Plan=[a,b,c,d] corresponding to plan's equation ax + by + cz + d = 0, give the distance beetween Pt and the Plan
|
||||
x, y, z = Pt
|
||||
a, b, c, d = Plan
|
||||
dist = np.abs(a * x + b * y + c * z + d) / np.sqrt(a ** 2 + b ** 2 + c ** 2)
|
||||
return dist
|
||||
|
||||
|
||||
# ======================Construction tools=========================
|
||||
def Isobarycenter(set):
|
||||
# Calculate isobarycenter of a set of points. Returns his coordinates
|
||||
x = y = z = 0.0
|
||||
l = len(set)
|
||||
for point in set:
|
||||
x += point[0] / l
|
||||
y += point[1] / l
|
||||
z += point[2] / l
|
||||
# print("New Centroid of cluter:",[x,y,z])
|
||||
return [x, y, z]
|
||||
|
||||
|
||||
# ==========================================
|
||||
def Invert_Coord(set, O, r):
|
||||
# Apply circular inversion to every point in the set, excepted for origin, remaining origin.
|
||||
output = []
|
||||
for point in set:
|
||||
if point == O:
|
||||
output.append(point)
|
||||
else:
|
||||
D = distance(O, point)
|
||||
OP = vector_def(O, point)
|
||||
OP = np.multiply(OP, 1. / D) # set OP to unity vector
|
||||
add = np.ndarray.tolist(np.array(O) + np.multiply(OP, r ** 2 / D))
|
||||
output.append(add)
|
||||
return output
|
||||
|
||||
|
||||
# ==========================================
|
||||
def Midpoint(P1, P2):
|
||||
# From points coordinates P1 and P2 : construct the middle point of [P1,P2]
|
||||
output = np.ndarray.tolist(np.multiply((np.array(P1) + np.array(P2)), 0.5))
|
||||
return output
|
||||
|
||||
|
||||
# ==========================================
|
||||
def rot3D(P, A, u):
|
||||
# Verify first if u is unity vector :
|
||||
if vector_norm(u) != 1:
|
||||
u = np.ndarray.tolist(np.multiply(u, 1. / vector_norm(u)))
|
||||
# From P in R3, A in R and u in R3, returns the image from P's rotation around u from angle A
|
||||
x, y, z = P
|
||||
ux, uy, uz = u
|
||||
c = np.cos(A)
|
||||
s = np.sin(A)
|
||||
# We compute directly the result : see the 3D rotation matrix
|
||||
X = x * (ux ** 2 * (1 - c) + c) + y * (ux * uy * (1 - c) - uz * s) + z * (ux * uz * (1 - c) + uy * s)
|
||||
Y = x * (ux * uy * (1 - c) + uz * s) + y * (uy ** 2 * (1 - c) + c) + z * (uy * uz * (1 - c) - ux * s)
|
||||
Z = x * (ux * uz * (1 - c) - uy * s) + y * (uy * uz * (1 - c) + ux * s) + z * (uz ** 2 * (1 - c) + c)
|
||||
|
||||
return [X, Y, Z]
|
||||
|
||||
|
||||
# =========================Data Tools==============================
|
||||
def commonlist(L1, L2):
|
||||
# Returns a list of elements common to L1 and L2, ie output is L1 intercection with L2
|
||||
output = []
|
||||
for i in L1:
|
||||
if i in L2:
|
||||
output.append(i)
|
||||
return output
|
||||
|
||||
|
||||
# ==========================================
|
||||
def cleanlist(list):
|
||||
# Returns a list without repeated elements (each element in cleaned list appears only once)
|
||||
ls = len(list)
|
||||
Index = range(ls)
|
||||
Output = []
|
||||
|
||||
for iS in range(ls):
|
||||
if iS in Index:
|
||||
for iOS in range(iS + 1, ls):
|
||||
S = list[iS]
|
||||
OS = list[iOS]
|
||||
if S == OS:
|
||||
Index.remove(iOS) # S and OS are same coord or almost : we remove the last : OS
|
||||
# else : iS correspond to coord needed to be deleted, so we don't add it
|
||||
|
||||
for I in Index:
|
||||
Output.append(list[I])
|
||||
|
||||
return Output
|
||||
|
||||
# ==========================================
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# coding: utf-8
|
||||
|
||||
from ase import Atoms
|
||||
from ase.io import write,read
|
||||
from ase.visualize import view
|
||||
from scipy.spatial import ConvexHull
|
||||
from es_mod import empty_spheres as esph
|
||||
|
||||
|
||||
"""=============Generate empty spheres in copper cluster
|
||||
Structure = read('cluster_examples/copper.xyz')
|
||||
struct = np.ndarray.tolist(Structure.positions)
|
||||
set = esph.Delaunay_Intersphere(struct)
|
||||
Set=Atoms(positions=set)
|
||||
view(Structure+Set)
|
||||
view(Set)
|
||||
#"""#====================================================
|
||||
|
||||
from msspec.calculator import MSSPEC
|
||||
from msspec.utils import *
|
||||
|
||||
#"""=============Use Python MsSpec
|
||||
cluster = read('cluster_examples/GeCl4.xyz')
|
||||
|
||||
# Set the absorber (the deepest atom centered in the xy-plane)
|
||||
cluster.absorber = 0
|
||||
# Create a calculator for the PhotoElectron Diffration
|
||||
calc = MSSPEC(spectroscopy='PED')
|
||||
# Set the cluster to use for the calculation
|
||||
calc.set_atoms(cluster)
|
||||
|
||||
# Run the calculation
|
||||
data = calc.get_theta_scan(level='2p3/2', kinetic_energy=[320,325,5])
|
||||
|
||||
# Show the results
|
||||
data.view()
|
||||
#"""#===============================
|
||||
|
||||
#"""===================MsSpec on ClusterC Test :
|
||||
cluster = read('ClusterFinal.xyz')
|
||||
# Set the absorber (the deepest atom centered in the xy-plane)
|
||||
cluster.absorber = 0
|
||||
# Create a calculator for the PhotoElectron Diffration
|
||||
calc = MSSPEC(spectroscopy='PED')
|
||||
# Set the cluster to use for the calculation
|
||||
calc.set_atoms(cluster)
|
||||
|
||||
# Run the calculation
|
||||
data = calc.get_theta_scan(level='2p3/2', kinetic_energy=[320,325,5])
|
||||
|
||||
# Show the results
|
||||
data.view()
|
||||
#"""#===============================
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
|||
# coding: utf-8
|
||||
"""
|
||||
Module misc
|
||||
===========
|
||||
|
||||
"""
|
||||
import logging
|
||||
from pint import UnitRegistry
|
||||
import numpy as np
|
||||
import inspect
|
||||
import re
|
||||
|
||||
class XRaySource(object):
|
||||
MG_KALPHA = 1253.6
|
||||
AL_KALPHA = 1486.6
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
UREG = UnitRegistry()
|
||||
UREG.define('rydberg = c * h * rydberg_constant = Ry')
|
||||
UREG.define('bohr_radius = 4 * pi * epsilon_0 * hbar**2 / electron_mass / e**2 = a0')
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
LOGGER = logging.getLogger('msspec')
|
||||
|
||||
np.set_printoptions(formatter={'float': lambda x:'%.2f' % x}, threshold=5)
|
||||
|
||||
def set_log_level(level):
|
||||
lvl = getattr(logging, level.upper())
|
||||
LOGGER.setLevel(lvl)
|
||||
|
||||
def set_log_output(stream):
|
||||
LOGGER.parent.handlers[0].stream = stream
|
||||
|
||||
def get_call_info(frame):
|
||||
args, varargs, varkw, loc = inspect.getargvalues(frame)
|
||||
_, _, function, _, _ = inspect.getframeinfo(frame)
|
||||
s = '%s called with:\n' % function
|
||||
|
||||
for kws in (args, varargs, varkw):
|
||||
if kws != None:
|
||||
if isinstance(kws, (tuple, list)):
|
||||
for kw in kws:
|
||||
s += '\t\t%s = %r\n' % (kw, loc[kw])
|
||||
else:
|
||||
s += '\t\t%s = %r\n' % (kws, loc[kws])
|
||||
return s.strip()
|
||||
|
||||
|
||||
def log_process_output(process, logger=None, severity=logging.INFO):
|
||||
if logger == None:
|
||||
logger = logging
|
||||
else:
|
||||
logger = logging.getLogger(logger)
|
||||
logger.setLevel(LOGGER.getEffectiveLevel())
|
||||
|
||||
for line in iter(process.stdout.readline, b''):
|
||||
logger.log(severity, line.rstrip('\n'))
|
||||
|
||||
process.stdout.close()
|
||||
process.wait()
|
||||
|
||||
|
||||
def get_level_from_electron_configuration(notation):
|
||||
l_letters = 'spdfghiklmnoqrtuv'
|
||||
n_letters = 'klmnopqrstuv'
|
||||
pattern = re.compile(r'(?P<n>\d+)(?P<l>[%s%s])'
|
||||
r'((?P<j>\d)/2)?' % (l_letters, l_letters.upper()))
|
||||
m = pattern.match(notation)
|
||||
assert m, 'Not a valid notation'
|
||||
n, l, _, j = m.groups()
|
||||
n = int(n)
|
||||
l = l_letters.index(l.lower())
|
||||
assert (l < n), 'Invalid l'
|
||||
j1 = abs(l - 0.5)
|
||||
j2 = abs(l + 0.5)
|
||||
if j:
|
||||
j = int(j) / 2.
|
||||
else:
|
||||
j = j1
|
||||
assert j in (j1, j2), 'Invalid j'
|
||||
letter = n_letters[n - 1]
|
||||
subscript = str(int((2 * (l + j) + 1) / 2))
|
||||
if letter == 'k':
|
||||
subscript = ''
|
||||
return '{}{}'.format(letter, subscript)
|
|
@ -0,0 +1 @@
|
|||
/contribs/
|
|
@ -0,0 +1 @@
|
|||
__version__ = '1.2rc3.post152'
|
|
@ -0,0 +1,8 @@
|
|||
# from __future__ import absolute_import
|
||||
from .operator import Operator
|
||||
from .ioperatorcreator import IOperatorCreator
|
||||
from .idataflowserializer import IDataflowSerializer
|
||||
from msspecgui.dataflow.dataflow import DataFlow
|
||||
from .idatatype import IDataType
|
||||
from .plug import Plug
|
||||
from .wire import Wire
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
|
||||
class Attribute(object):
|
||||
def __init__(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
|
||||
"""
|
||||
:param attr_name: the name of this attribute, eg 'file_name'
|
||||
:type attr_name: str
|
||||
:param attr_type_name: the id of the type of this attribute (eg 'string'). Note that this type of attribute should have been registered in the dataflow first
|
||||
:type attr_type_name: str
|
||||
:param attr_desc: a string describing the role of this attribute
|
||||
:type attr_desc: str
|
||||
"""
|
||||
self._attr_name = attr_name
|
||||
self._attr_type_name = attr_type_name
|
||||
self._attr_desc = attr_desc
|
||||
self.is_pluggable = is_pluggable # indicates that this attribute can be plugged to another one using a wire. If not, then the value of this attribute is set using other means (eg graphical user interface)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._attr_name
|
||||
|
||||
@property
|
||||
def type_name(self):
|
||||
return self._attr_type_name
|
|
@ -0,0 +1,229 @@
|
|||
# from __future__ import absolute_import
|
||||
# from .plug import Plug
|
||||
from .wire import Wire
|
||||
|
||||
|
||||
class DataFlow(object):
|
||||
''' a flow of operators, each of them having inputs and outputs that are connected together
|
||||
'''
|
||||
|
||||
class IDataFlowEventsHandler(object):
|
||||
"""an abstract class that listens and responds to events affecting a flow of operators
|
||||
|
||||
"""
|
||||
def on_added_operator(self, operator):
|
||||
"""this method is invoked whenever an operator is added to the flow
|
||||
|
||||
:param dataflow.Operator operator: the operator that has just been added to the flow
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_deleted_operator(self, operator):
|
||||
"""this method is invoked whenever an operator is deleted from the data flow
|
||||
|
||||
:param dataflow.Operator operator: the operator that has just been deleted
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_modified_operator(self, operator):
|
||||
"""this method is invoked whenever an operator is modified in the flow
|
||||
|
||||
:param operator: the operator that has just been modified
|
||||
:type operator: dataflow.Operator
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_added_wire(self, wire):
|
||||
"""this method is invoked whenever a wire is added to the flow
|
||||
|
||||
:param wire: the wire that has just been added to the flow
|
||||
:type wire: dataflow.Wire
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_deleted_wire(self, wire):
|
||||
"""this method is invoked whenever a wire is deleted from the flow
|
||||
|
||||
:param dataflow.Wire wire: the wire that has just been deleted
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
self._last_created_operator_id = 0 # each operator (node) has a uniqueid in the flow
|
||||
self._operators = {} #: :type self._operators: dict[int, msspec.dataflow.Operator]
|
||||
self._registered_creators = {} # This factory lists and creates cluster creator instances
|
||||
self._registered_data_types = {} #: :type self._registered_data_types: dict(str, msspecgui.dataflow.IDataType) ; the list of known data types (for links between nodes) in this dataflow
|
||||
self._dataflow_events_handlers = []
|
||||
self._wires = []
|
||||
|
||||
@property
|
||||
def last_created_operator_id(self):
|
||||
return self._last_created_operator_id
|
||||
|
||||
@last_created_operator_id.setter
|
||||
def last_created_operator_id(self, operator_id):
|
||||
"""initializes the operator id generator
|
||||
|
||||
useful for restoring the dataflow state, eg after loading
|
||||
|
||||
:param int operator_id:
|
||||
"""
|
||||
self._last_created_operator_id = operator_id
|
||||
|
||||
def add_dataflow_events_handler(self, dataflow_events_handler):
|
||||
"""adds a dataflow events handler to the list of dataflow events handler
|
||||
|
||||
:type dataflow_events_handler: IDataFlowEventsHandler
|
||||
"""
|
||||
self._dataflow_events_handlers.append(dataflow_events_handler)
|
||||
|
||||
def remove_dataflow_events_handler(self, dataflow_events_handler):
|
||||
"""removes a dataflow events handler to the list of dataflow events handler
|
||||
|
||||
:type dataflow_events_handler: IDataFlowEventsHandler
|
||||
"""
|
||||
self._dataflow_events_handlers.remove(dataflow_events_handler)
|
||||
|
||||
def add_operator(self, operator):
|
||||
"""
|
||||
:type operator: msspec.dataflow.Operator
|
||||
"""
|
||||
self._operators[operator.id] = operator
|
||||
for dataflow_events_handler in self._dataflow_events_handlers:
|
||||
dataflow_events_handler.on_added_operator(operator)
|
||||
|
||||
def delete_operator(self, operator):
|
||||
"""
|
||||
:param msspec.dataflow.Operator operator:
|
||||
"""
|
||||
data_flow = operator.data_flow
|
||||
|
||||
# delete incoming wires
|
||||
for plug in operator.get_input_plugs():
|
||||
if plug.is_connected():
|
||||
wire = plug.incoming_wire
|
||||
data_flow.delete_wire(wire)
|
||||
|
||||
# delete outgoing wires
|
||||
for plug in operator.get_output_plugs(): #: :type plug: Plug
|
||||
if plug.is_connected():
|
||||
for wire in plug.outgoing_wires:
|
||||
data_flow.delete_wire(wire)
|
||||
|
||||
self._operators.pop(operator.id)
|
||||
for dataflow_events_handler in self._dataflow_events_handlers:
|
||||
dataflow_events_handler.on_deleted_operator(operator)
|
||||
|
||||
def on_modified_operator(self, operator):
|
||||
"""tells the dataflow that the given operator has been modified (eg its paraeters have been changed)
|
||||
|
||||
:type operator: Operator
|
||||
"""
|
||||
# print("on_modified_operator : operator %d" % operator.id)
|
||||
operator.set_dirty()
|
||||
for deh in self._dataflow_events_handlers:
|
||||
deh.on_modified_operator(operator)
|
||||
|
||||
def get_operator(self, operator_id):
|
||||
"""
|
||||
:param int operator_id:
|
||||
:rtype: msspec.dataflow.Operator
|
||||
"""
|
||||
return self._operators[operator_id]
|
||||
|
||||
def find_operator(self, operator_name):
|
||||
"""
|
||||
:param str operator_name:
|
||||
:rtype: msspec.dataflow.Operator
|
||||
"""
|
||||
for op in self.operators:
|
||||
if op.name == operator_name:
|
||||
return op
|
||||
return None
|
||||
|
||||
@property
|
||||
def operators(self):
|
||||
"""
|
||||
:rtype: list(Operator)
|
||||
"""
|
||||
return list(self._operators.itervalues())
|
||||
|
||||
def get_new_operator_id(self):
|
||||
"""
|
||||
:rtype: int
|
||||
"""
|
||||
self._last_created_operator_id += 1
|
||||
return self._last_created_operator_id
|
||||
|
||||
def register_operator_creator(self, operator_creator):
|
||||
'''
|
||||
|
||||
:param operator_creator:
|
||||
:type operator_creator: derived from IOperatorCreator
|
||||
'''
|
||||
self._registered_creators[operator_creator.get_operator_type_id()] = operator_creator
|
||||
|
||||
def register_data_type(self, data_type):
|
||||
'''
|
||||
|
||||
:param data_type: the type of this argument is expected to be derived from IDataType
|
||||
:type data_type: derived from IDataType
|
||||
'''
|
||||
self._registered_data_types[data_type.get_type_id()] = data_type
|
||||
|
||||
def get_data_type(self, data_type_id):
|
||||
"""
|
||||
:return msspecggui.dataflow.IDataType:
|
||||
"""
|
||||
return self._registered_data_types[data_type_id]
|
||||
|
||||
def get_operator_creators(self):
|
||||
"""
|
||||
:rtype: list(dataflow.ioperatorcreator.IOperatorCreator)
|
||||
"""
|
||||
return list(self._registered_creators.itervalues())
|
||||
|
||||
@property
|
||||
def wires(self):
|
||||
"""
|
||||
:rtype: list(msspec.dataflow.Wire)
|
||||
"""
|
||||
return self._wires
|
||||
|
||||
def create_operator(self, operator_type_id):
|
||||
""" creates an operator of the given type and adds it to this flow
|
||||
|
||||
:param operator_type_id: the type of operator to create. This type must be one of the allowed types for this flow (in other words, this type must have been registered to this flow)
|
||||
"""
|
||||
creator = self._registered_creators[operator_type_id]
|
||||
return creator.create_operator(self)
|
||||
|
||||
def create_wire(self, input_plug, output_plug):
|
||||
"""
|
||||
:param Plug input_plug: the input plug
|
||||
:param Plug output_plug:
|
||||
:return dataflow.Wire:
|
||||
"""
|
||||
wire = Wire(input_plug, output_plug)
|
||||
input_plug.add_outgoing_wire(wire)
|
||||
output_plug.incoming_wire = wire
|
||||
self._wires.append(wire)
|
||||
|
||||
for dataflow_events_handler in self._dataflow_events_handlers:
|
||||
dataflow_events_handler.on_added_wire(wire)
|
||||
|
||||
return wire
|
||||
|
||||
def delete_wire(self, wire):
|
||||
"""
|
||||
:param Wire wire: the wire that needs to be destroyed
|
||||
"""
|
||||
wire.input_plug.detach_wire(wire)
|
||||
wire.output_plug.detach_wire(wire)
|
||||
|
||||
self._wires.remove(wire)
|
||||
|
||||
for dataflow_events_handler in self._dataflow_events_handlers:
|
||||
dataflow_events_handler.on_deleted_wire(wire)
|
||||
|
||||
return wire
|
|
@ -0,0 +1,69 @@
|
|||
from .idatatype import IDataType
|
||||
|
||||
|
||||
class StringDataType(IDataType):
|
||||
def __init__(self):
|
||||
IDataType.__init__(self)
|
||||
|
||||
def get_type_id(self):
|
||||
"""
|
||||
Returns a string uniquely identifying this data type
|
||||
"""
|
||||
return 'string'
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
see IDataType.get_python_class
|
||||
"""
|
||||
return str
|
||||
|
||||
|
||||
class FloatDataType(IDataType):
|
||||
def __init__(self):
|
||||
IDataType.__init__(self)
|
||||
|
||||
def get_type_id(self):
|
||||
"""
|
||||
Returns a string uniquely identifying this data type
|
||||
"""
|
||||
return 'float'
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
see IDataType.get_python_class
|
||||
"""
|
||||
return float
|
||||
|
||||
|
||||
class BoolDataType(IDataType):
|
||||
def __init__(self):
|
||||
IDataType.__init__(self)
|
||||
|
||||
def get_type_id(self):
|
||||
"""
|
||||
Returns a string uniquely identifying this data type
|
||||
"""
|
||||
return 'bool'
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
see IDataType.get_python_class
|
||||
"""
|
||||
return bool
|
||||
|
||||
|
||||
class IntDataType(IDataType):
|
||||
def __init__(self):
|
||||
IDataType.__init__(self)
|
||||
|
||||
def get_type_id(self):
|
||||
"""
|
||||
Returns a string uniquely identifying this data type
|
||||
"""
|
||||
return 'int'
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
see IDataType.get_python_class
|
||||
"""
|
||||
return int
|
|
@ -0,0 +1,26 @@
|
|||
import abc
|
||||
|
||||
|
||||
class IDataflowSerializer(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def save_dataflow(self, dataflow, filepath):
|
||||
""" saves the given dataflow at the given file location
|
||||
|
||||
:param dataflow: the dataflow to save
|
||||
:type dataflow: msspecgui.dataflow.DataFlow
|
||||
:param file_path: the path of the file that will store this dataflow in serialized form
|
||||
:type file_path: str
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def load_dataflow(self, file_path, dataflow):
|
||||
"""loads the dataflow from the given serialized dataflow file
|
||||
|
||||
:param str file_path: the xml file describng the dataflow to load
|
||||
:param msspecgui.dataflow.DataFlow dataflow: an empty dataflow that will be filled
|
||||
|
||||
:rtype: DataFlow
|
||||
"""
|
||||
return None
|
|
@ -0,0 +1,59 @@
|
|||
import abc
|
||||
|
||||
|
||||
class IDataType(object):
|
||||
|
||||
class IAction(object):
|
||||
"""an abstract class that represents an action that can be performed on a data of the given data type
|
||||
|
||||
for example, the export cluster is an action (with a gui that allows the user to select a file path) that can be performed on a data of type physics.atomscluster. This mechanism is used in the dataflow editor : when the user right-clicks on a wire, a list of actions related to the wire's datatype is offered to the user.
|
||||
"""
|
||||
def __init__(self, datatype):
|
||||
"""
|
||||
:param msspecgui.dataflow.IDataType datatype: the datatype on which this action is expected to perform
|
||||
"""
|
||||
self.datatype = datatype
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_name(self):
|
||||
""" returns a name that uniquely identifies the action amongst actions associated with a given IDataType
|
||||
"""
|
||||
assert False
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute_on_data(self, data):
|
||||
"""executes this action on the given data
|
||||
|
||||
:param data: the data on which this action is performed
|
||||
"""
|
||||
assert False
|
||||
|
||||
def __init__(self):
|
||||
self._actions = {} #: :type self._actions: dict(str, IDataType.IAction)
|
||||
|
||||
def get_type_id(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
Returns a string iuniquely identifying this data type
|
||||
:rtype: str
|
||||
"""
|
||||
assert False # this method is expected to be defined in a derived class
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
returns the python class associated with this data type
|
||||
"""
|
||||
assert False # this method is expected to be defined in a derived class
|
||||
|
||||
def register_datatype_action(self, action):
|
||||
"""
|
||||
|
||||
:type action: IDataType.IAction
|
||||
"""
|
||||
assert action.datatype == self
|
||||
self._actions[action.get_name()] = action
|
||||
|
||||
def get_actions(self):
|
||||
"""
|
||||
:rtype: list(IDataType.IAction)
|
||||
"""
|
||||
return list(self._actions.itervalues())
|
|
@ -0,0 +1,101 @@
|
|||
|
||||
from .attribute import Attribute
|
||||
import abc
|
||||
|
||||
|
||||
class IOperatorCreator(object):
|
||||
'''abstract base class that allows registration of operator creators
|
||||
'''
|
||||
|
||||
class IAction(object):
|
||||
"""an abstract class that represents an action that can be performed on an operator type
|
||||
|
||||
for example, the ase.lattice.bulk operator's gui (property settings) is an action that can be performed on an operator of type ase.lattice.bulk
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_name(self):
|
||||
""" returns a name that uniquely identifies the action amongst actions associated with a given IOperatorCreator
|
||||
"""
|
||||
assert False
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute_on_operator(self, operator):
|
||||
"""executes this action on the given operator
|
||||
|
||||
:param operator: the operator on which this action is performed
|
||||
:type operator: dataflow.Operator
|
||||
"""
|
||||
assert False
|
||||
|
||||
def __init__(self):
|
||||
self._input_attrs = {}
|
||||
self._output_attrs = {}
|
||||
self._actions = {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_operator_type_id(self): # pylint: disable=no-self-use
|
||||
'''returns the unique id of the type of the operator that this creator creates (eg ipr.msspec.simpleclustercreator)
|
||||
'''
|
||||
assert False # this method should be implemented in a derived class
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_operator(self, dflow): # pylint: disable=no-self-use
|
||||
'''creates an application specific operator in the given Dataflow dflow
|
||||
|
||||
:type dflow: DataFlow
|
||||
'''
|
||||
assert False # this method should be implemented in a derived class
|
||||
|
||||
def register_operator_action(self, action):
|
||||
"""
|
||||
|
||||
:type action: dataflow.ioperatorcreator.IOperatorCreator.IAction
|
||||
"""
|
||||
self._actions[action.get_name()] = action
|
||||
|
||||
def get_actions(self):
|
||||
"""
|
||||
:rtype: list(dataflow.ioperatorcreator.IOperatorCreator.IAction)
|
||||
"""
|
||||
return list(self._actions.itervalues())
|
||||
|
||||
def add_input_attribute(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
|
||||
"""Declares a new input attribute for the related operator
|
||||
|
||||
:param attr_name: the name of the attribute
|
||||
:type attr_name: str
|
||||
:param attr_type_name: the name of the type of the attribute (eg 'math.position3')
|
||||
:type attr_type_name: str
|
||||
:param attr_desc: the description of the attribute
|
||||
:type attr_desc: str
|
||||
|
||||
:rtype: Attribute
|
||||
"""
|
||||
attr = Attribute(attr_name, attr_type_name, attr_desc, is_pluggable=is_pluggable)
|
||||
self._input_attrs[attr_name] = attr
|
||||
return attr
|
||||
|
||||
def add_output_attribute(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
|
||||
"""Declares a new output attribute for the related operator
|
||||
|
||||
:param attr_name: the name of the attribute
|
||||
:type attr_name: str
|
||||
:param attr_type_name: the name of the type of the attribute (eg 'math.position3')
|
||||
:type attr_type_name: str
|
||||
:param attr_desc: the description of the attribute
|
||||
:type attr_desc: str
|
||||
|
||||
:rtype: Attribute
|
||||
"""
|
||||
attr = Attribute(attr_name, attr_type_name, attr_desc, is_pluggable=is_pluggable)
|
||||
self._output_attrs[attr_name] = attr
|
||||
return attr
|
||||
|
||||
def get_input_attributes(self):
|
||||
return self._input_attrs.itervalues()
|
||||
|
||||
def get_output_attributes(self):
|
||||
return self._output_attrs.itervalues()
|
|
@ -0,0 +1,120 @@
|
|||
# import plug
|
||||
# from .dflow import DataFlow
|
||||
from .plug import Plug
|
||||
|
||||
|
||||
class Operator(object):
|
||||
'''
|
||||
an abstract operator that generates outputs from its inputs
|
||||
'''
|
||||
|
||||
def __init__(self, data_flow, creator):
|
||||
'''
|
||||
Constructor
|
||||
|
||||
:param data_flow: the data_flow that will contain this operator
|
||||
:type data_flow: DataFlow
|
||||
:param creator: an instance of IOperatorCreator, the creator of this object (consider it as an object that stores informations that are common to all operators of this type).
|
||||
:type creator: dataflow.ioperatorcreator.IOperatorCreator
|
||||
'''
|
||||
self._data_flow = data_flow
|
||||
self._creator = creator
|
||||
self._id = data_flow.get_new_operator_id()
|
||||
self._input_plugs = {}
|
||||
self._output_plugs = {}
|
||||
for attr in self._creator.get_input_attributes():
|
||||
data_type = self._data_flow.get_data_type(attr.type_name)
|
||||
assert data_type is not None
|
||||
p = Plug(attr, data_type, Plug.PlugSide.INPUT, self)
|
||||
self._input_plugs[p.name] = p
|
||||
for attr in self._creator.get_output_attributes():
|
||||
data_type = self._data_flow.get_data_type(attr.type_name)
|
||||
assert data_type is not None
|
||||
p = Plug(attr, data_type, Plug.PlugSide.OUTPUT, self)
|
||||
self._output_plugs[p.name] = p
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '%s_%d' % (self._creator.get_operator_type_id(), self._id)
|
||||
|
||||
def get_input_plugs(self):
|
||||
"""
|
||||
:rtype: list(msspecgui.dataflow.Plug)
|
||||
"""
|
||||
return list(self._input_plugs.itervalues())
|
||||
|
||||
def get_output_plugs(self):
|
||||
"""
|
||||
:rtype: list(msspecgui.dataflow.Plug)
|
||||
"""
|
||||
return list(self._output_plugs.itervalues())
|
||||
|
||||
def get_plug(self, plug_name):
|
||||
"""
|
||||
:type plug_name: str
|
||||
:rtype: msspecgui.dataflow.Plug
|
||||
"""
|
||||
if plug_name in self._input_plugs:
|
||||
return self._input_plugs[plug_name]
|
||||
if plug_name in self._output_plugs:
|
||||
return self._output_plugs[plug_name]
|
||||
|
||||
@property
|
||||
def wires(self):
|
||||
"""
|
||||
:return list(msspecgui.dataflow.Wire)
|
||||
"""
|
||||
wires = []
|
||||
for p in self._input_plugs.itervalues():
|
||||
if p.is_connected():
|
||||
wires.append(p.incoming_wire)
|
||||
for p in self._output_plugs.itervalues():
|
||||
if p.is_connected():
|
||||
for wire in p.outgoing_wires:
|
||||
wires.append(wire)
|
||||
return wires
|
||||
|
||||
def set_dirty(self):
|
||||
"""
|
||||
indicates that the output values of this operator are obsolete and need to be recomputed
|
||||
"""
|
||||
print("setting operator %d as dirty" % self.id)
|
||||
for plug in self.get_output_plugs():
|
||||
plug.set_dirty()
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
compute this operator's output values from its input values
|
||||
|
||||
this method is supposed to be overriden in derived classes
|
||||
"""
|
||||
assert False # should be implemented in derived classes
|
||||
|
||||
def all_inputs_are_available(self):
|
||||
for input_plug in self.get_input_plugs():
|
||||
if not input_plug.value_is_available():
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
:rtype: int
|
||||
"""
|
||||
return self._id
|
||||
|
||||
@id.setter
|
||||
def id(self, operator_id):
|
||||
"""changes the unique identifier of this operator (use at your own risk)
|
||||
|
||||
:param int operator_id: the new unique identifier for this operator
|
||||
"""
|
||||
self._id = operator_id
|
||||
|
||||
@property
|
||||
def creator(self):
|
||||
return self._creator
|
||||
|
||||
@property
|
||||
def data_flow(self):
|
||||
return self._data_flow
|
|
@ -0,0 +1,219 @@
|
|||
# from __builtin__ import False
|
||||
|
||||
|
||||
class Plug(object):
|
||||
"""
|
||||
A Plug represents an input or output attribute on a dataflow operator
|
||||
|
||||
:param __operator: the operator that contains this plug
|
||||
:type __operator: dataflow.Operator
|
||||
"""
|
||||
|
||||
class PlugSide(object):
|
||||
INPUT = 1
|
||||
OUTPUT = 2
|
||||
|
||||
def __init__(self, attr, data_type, plug_side, operator):
|
||||
"""Constructor
|
||||
|
||||
:param attr: an instance of Attribute
|
||||
:type attr: msspec.dataflow.Attribute
|
||||
:param data_type: the type of the attribute
|
||||
:type data_type: IDataType
|
||||
:param plug_side: tells if this plug is an input plug or an output plug
|
||||
:type plug_side: Plug.PlugSide enum
|
||||
:param operator: the operator that contains this plug
|
||||
:type operator: msspec.dataflow.Operator
|
||||
"""
|
||||
self._attr = attr
|
||||
self._data_type = data_type
|
||||
self._plug_side = plug_side
|
||||
self._operator = operator
|
||||
self._outgoing_wires = []
|
||||
self._incoming_wire = None
|
||||
self._value = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return self._attr.name
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return '%s.%s' % (self.operator.name, self.name)
|
||||
|
||||
@property
|
||||
def data_type(self):
|
||||
return self._data_type
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
"""
|
||||
:rtype: msspec.dataflow.Operator
|
||||
"""
|
||||
return self._operator
|
||||
|
||||
@property
|
||||
def is_pluggable(self):
|
||||
return self._attr.is_pluggable
|
||||
|
||||
def get_value(self):
|
||||
print("getting value of plug %s" % self.id)
|
||||
assert self.value_is_available()
|
||||
if self.is_dirty():
|
||||
self._update_value()
|
||||
assert not self.is_dirty()
|
||||
return self._value
|
||||
|
||||
def set_value(self, value):
|
||||
"""
|
||||
:type value: any type of data that is compatible with this plug's datatype
|
||||
"""
|
||||
assert isinstance(value, self._data_type.get_python_class())
|
||||
if self.is_pluggable:
|
||||
assert self.is_dirty() # we expect to only have to use this method when this plug is dirty
|
||||
self._value = value
|
||||
|
||||
def value_is_available(self):
|
||||
"""
|
||||
indicates if the value of this plug can be computed
|
||||
"""
|
||||
if self.is_destination():
|
||||
if not self.is_pluggable:
|
||||
return True # if this plug is not pluggable, then its value is available since it doesn't depend on the result of other operators
|
||||
# print('value_is_available: self.is_connected() = %s' % str(self.is_connected()))
|
||||
if self.is_connected():
|
||||
src_plug = self.get_incoming_plug()
|
||||
return src_plug.value_is_available()
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
# this is an output plug; its value is available if all the operators's input plugs are available
|
||||
return self.operator.all_inputs_are_available()
|
||||
|
||||
def set_dirty(self):
|
||||
"""
|
||||
indicates that the value of this plug is obsolete and needs to be recomputed
|
||||
"""
|
||||
print("setting plug %s as dirty" % self.id)
|
||||
self._value = None
|
||||
if self.is_output_plug():
|
||||
if self.is_connected():
|
||||
for connected_plug in self.get_outgoing_plugs():
|
||||
connected_plug.set_dirty()
|
||||
else:
|
||||
# this is an operator's input plug. Propagate the dirtiness to its out plugs
|
||||
self.operator.set_dirty()
|
||||
|
||||
def is_dirty(self):
|
||||
"""
|
||||
indicates if the value needs to be computed
|
||||
"""
|
||||
assert self.value_is_available()
|
||||
if not self.is_pluggable:
|
||||
return False # a non-pluggable plug is never dirty, it always has a valid value
|
||||
return self._value is None
|
||||
|
||||
def _update_value(self):
|
||||
print("updating value of plug %s" % self.id)
|
||||
assert self.value_is_available()
|
||||
assert self.is_dirty()
|
||||
if self.is_destination():
|
||||
if self.is_connected():
|
||||
self._value = self.get_incoming_plug().get_value()
|
||||
else:
|
||||
assert False # if we are in this case, this would mean that the value is not available
|
||||
else:
|
||||
print("updating operator %s" % self._operator.name)
|
||||
self._operator.update()
|
||||
assert not self.is_dirty()
|
||||
|
||||
def is_connected(self):
|
||||
if self.is_source():
|
||||
return len(self._outgoing_wires) != 0
|
||||
else:
|
||||
return self._incoming_wire is not None
|
||||
|
||||
def is_source(self):
|
||||
return self._plug_side == Plug.PlugSide.OUTPUT
|
||||
|
||||
def is_destination(self):
|
||||
return self._plug_side == Plug.PlugSide.INPUT
|
||||
|
||||
def is_output_plug(self):
|
||||
return self._plug_side == Plug.PlugSide.OUTPUT
|
||||
|
||||
def is_input_plug(self):
|
||||
return self._plug_side == Plug.PlugSide.INPUT
|
||||
|
||||
def connect_to(self, other_plug):
|
||||
"""
|
||||
:type other_plug: dataflow.Plug
|
||||
:rtype: dataflow.Wire
|
||||
"""
|
||||
return self._operator.data_flow.create_wire(self, other_plug)
|
||||
|
||||
def add_outgoing_wire(self, wire):
|
||||
"""
|
||||
:type wire: dataflow.Wire
|
||||
"""
|
||||
assert self.is_source()
|
||||
self._outgoing_wires.append(wire)
|
||||
|
||||
@property
|
||||
def incoming_wire(self):
|
||||
assert self.is_destination()
|
||||
assert self.is_connected()
|
||||
return self._incoming_wire
|
||||
|
||||
@incoming_wire.setter
|
||||
def incoming_wire(self, wire):
|
||||
"""
|
||||
:type wire: dataflow.Wire
|
||||
"""
|
||||
assert self.is_destination()
|
||||
self._incoming_wire = wire
|
||||
self.operator.set_dirty() # one of the operator's input plugs has changed, therefore its output plugs values are obsolete
|
||||
|
||||
@property
|
||||
def outgoing_wires(self):
|
||||
"""
|
||||
:return list(dataflow.Wire):
|
||||
"""
|
||||
assert self.is_source()
|
||||
assert self.is_connected()
|
||||
return self._outgoing_wires
|
||||
|
||||
def detach_wire(self, wire):
|
||||
"""
|
||||
:param msspec.dataflow.Wire wire:
|
||||
"""
|
||||
assert self.is_connected()
|
||||
if self.is_destination():
|
||||
assert self._incoming_wire == wire
|
||||
self.incoming_wire = None
|
||||
self.set_dirty() # the data on this plug is no longer available
|
||||
else:
|
||||
assert self.is_source()
|
||||
self._outgoing_wires.remove(wire)
|
||||
|
||||
def get_incoming_plug(self):
|
||||
assert self.is_destination()
|
||||
assert self.is_connected()
|
||||
return self.incoming_wire.input_plug # (scenegraph_group.setter seen as a method, see https://github.com/PyCQA/pylint/issues/870) pylint: disable=no-member
|
||||
|
||||
def get_outgoing_plugs(self):
|
||||
"""
|
||||
:rtype: list(dataflow.Plug)
|
||||
"""
|
||||
assert self.is_source()
|
||||
assert self.is_connected()
|
||||
outgoing_plugs = []
|
||||
for wire in self._outgoing_wires:
|
||||
outgoing_plugs.append(wire.output_plug)
|
||||
return outgoing_plugs
|
|
@ -0,0 +1,33 @@
|
|||
'''
|
||||
Created on Jul 1, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
|
||||
|
||||
class Wire(object):
|
||||
"""
|
||||
a wire connects an input plug to an output plug
|
||||
"""
|
||||
|
||||
def __init__(self, input_plug, output_plug):
|
||||
"""
|
||||
:type input_plug: dataflow.Plug
|
||||
:type output_plug: dataflow.Plug
|
||||
"""
|
||||
self.input_plug = input_plug
|
||||
self.output_plug = output_plug
|
||||
|
||||
@property
|
||||
def data_type(self):
|
||||
"""
|
||||
:return IDataType: the datatype of this wire
|
||||
"""
|
||||
return self.input_plug.data_type
|
||||
|
||||
@property
|
||||
def data_flow(self):
|
||||
"""
|
||||
:return msspecgui.dataflow.DataFlow
|
||||
"""
|
||||
return self.input_plug.operator.data_flow
|
|
@ -0,0 +1,6 @@
|
|||
from .dataflowview import DataflowView
|
||||
from .operatorwidget import OperatorWidget
|
||||
from .plugwidget import PlugWidget
|
||||
from .wirewidget import WireWidget
|
||||
from .operatorgui import OperatorGui
|
||||
from msspecgui.datafloweditor.dotlayoutmanager import DotLayoutManager
|
|
@ -0,0 +1,342 @@
|
|||
"""
|
||||
a graphical editor for dataflow
|
||||
"""
|
||||
from __future__ import print_function
|
||||
# import wx
|
||||
import wx.lib.wxcairo
|
||||
import cairo
|
||||
# from wx.lib.scrolledpanel import ScrolledPanel
|
||||
# import scenegraph2d.xml
|
||||
import msspecgui.scenegraph2d.cairo
|
||||
|
||||
# import dataflow
|
||||
import msspecgui.dataflow as dataflow
|
||||
# import msspecgui.datafloweditor as datafloweditor
|
||||
|
||||
from .plugwidget import PlugWidget
|
||||
from .wirewidget import WireWidget
|
||||
from msspecgui.datafloweditor.dotlayoutmanager import DotLayoutManager
|
||||
# class DataflowView(wx.ScrolledWindow):
|
||||
|
||||
|
||||
class DataflowView(wx.Panel):
|
||||
'''
|
||||
a dataflow Graphical User Interface
|
||||
|
||||
:type self.wire_being_created_widget: WireWidget
|
||||
'''
|
||||
|
||||
# see https://wiki.wxwidgets.org/Scrolling
|
||||
|
||||
class DataflowEventsHandler(dataflow.DataFlow.IDataFlowEventsHandler):
|
||||
"""
|
||||
handles dataflow events
|
||||
"""
|
||||
def __init__(self, data_flow_view):
|
||||
"""
|
||||
:param msspec.datafloweditor.DataflowView data_flow_view:
|
||||
"""
|
||||
super(DataflowView.DataflowEventsHandler, self).__init__()
|
||||
self.data_flow_view = data_flow_view
|
||||
|
||||
def on_added_operator(self, operator):
|
||||
super(DataflowView.DataflowEventsHandler, self).on_added_operator(operator)
|
||||
# a new operator has just been added to the dataflow
|
||||
# create a new widget to graphically manipulate the added operator
|
||||
self.data_flow_view.add_operator_widget(msspecgui.datafloweditor.OperatorWidget(operator, self.data_flow_view))
|
||||
|
||||
def on_deleted_operator(self, operator):
|
||||
super(DataflowView.DataflowEventsHandler, self).on_deleted_operator(operator)
|
||||
self.data_flow_view.delete_widget_for_operator(operator)
|
||||
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
|
||||
|
||||
def on_added_wire(self, wire):
|
||||
super(DataflowView.DataflowEventsHandler, self).on_added_wire(wire)
|
||||
# create a new widget to graphically represent and manipulate the added wire
|
||||
wire_widget = self.data_flow_view.add_wire_widget(wire) # @UnusedVariable
|
||||
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
|
||||
|
||||
def on_deleted_wire(self, wire):
|
||||
super(DataflowView.DataflowEventsHandler, self).on_deleted_wire(wire)
|
||||
self.data_flow_view.delete_widget_for_wire(wire)
|
||||
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
|
||||
|
||||
def __init__(self, parent, data_flow, log):
|
||||
"""
|
||||
:type parent: wx.Window
|
||||
:type data_flow: DataFlow
|
||||
"""
|
||||
wx.Panel.__init__(self, parent, -1)
|
||||
self.scenegraph_group_to_widget = {} # this array associates a datafloweditor.Widget instance to a scenegraph group that represents this widget
|
||||
self.layout_is_enabled = True
|
||||
|
||||
self.log = log
|
||||
self.layout_manager = DotLayoutManager()
|
||||
self.scene = None # the 2d scenegraph that describes the graphical aspect of the dataflow
|
||||
self.scene = msspecgui.scenegraph2d.Group()
|
||||
# with open('/Users/graffy/ownCloud/ipr/msspec/rectangle.svg') as f:
|
||||
# self.scene = scenegraph2d.xml.parse(f.read())
|
||||
self.operator_to_widget = {}
|
||||
self.wire_to_widget = {}
|
||||
|
||||
self.dataflow = data_flow
|
||||
|
||||
# self.selected_widget = None
|
||||
self.hovered_widget = None # the widget hovered on by the mouse pointer
|
||||
self.plug_being_connected = None # when the user creates a wire, memorizes the first selected plug
|
||||
self.wire_being_created_widget = None # while the use creates a wire, the widget that is used to represent it
|
||||
self.is_left_down = False
|
||||
|
||||
self.Bind(wx.EVT_PAINT, self.on_paint)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
|
||||
self.Bind(wx.EVT_MOTION, self.on_move)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu)
|
||||
|
||||
self.data_flow_view_updater = DataflowView.DataflowEventsHandler(self)
|
||||
self.layout_is_enabled = False # temporarily disable the layout manager for efficiency (prevent complete layout computation for each operator in the data_flow)
|
||||
data_flow.add_dataflow_events_handler(self.data_flow_view_updater)
|
||||
# initialize the widgets to reflect the state of the cluster flow
|
||||
for node in self.dataflow.operators:
|
||||
self.data_flow_view_updater.on_added_operator(node)
|
||||
|
||||
for wire in self.dataflow.wires:
|
||||
self.data_flow_view_updater.on_added_wire(wire)
|
||||
self.layout_is_enabled = True
|
||||
self.update_operators_position()
|
||||
|
||||
def on_paint(self, evt): # IGNORE:unused-argument
|
||||
"""handler for the wx.EVT_PAINT event
|
||||
"""
|
||||
if self.IsDoubleBuffered():
|
||||
display_context = wx.PaintDC(self)
|
||||
else:
|
||||
display_context = wx.BufferedPaintDC(self)
|
||||
display_context.SetBackground(wx.Brush('white'))
|
||||
display_context.Clear()
|
||||
|
||||
self.render(display_context)
|
||||
|
||||
def update_appearance(self):
|
||||
for operator_widget in self.operator_to_widget.itervalues():
|
||||
operator_widget.update_appearance()
|
||||
|
||||
def update_operators_position(self):
|
||||
if self.layout_is_enabled:
|
||||
op_pos = self.layout_manager.compute_operators_position(self.dataflow)
|
||||
dodgy_offset = (20.0, 20.0) # TODO: make it better
|
||||
dodgy_scale = 1.5
|
||||
for op, pos in op_pos.iteritems():
|
||||
x, y = pos
|
||||
print("update_operators_position : %f %f" % (x, y))
|
||||
if op in self.operator_to_widget: # while loading the dataflow, there are operators that don't have widgets yet
|
||||
op_widget = self.operator_to_widget[op]
|
||||
op_widget.set_position(x * dodgy_scale + dodgy_offset[0], y * dodgy_scale + dodgy_offset[1])
|
||||
|
||||
def add_operator_widget(self, operator_widget):
|
||||
"""
|
||||
:type operator_widget: an instance of OperatorWidget
|
||||
"""
|
||||
self.operator_to_widget[operator_widget.operator] = operator_widget
|
||||
widget_root = msspecgui.scenegraph2d.Group()
|
||||
self.scene.add_child(widget_root)
|
||||
# widget_root.transform = [msspecgui.scenegraph2d.Translate(operator_widget.get_id() * 100.0 + 50.0, 60.0)]
|
||||
operator_widget.render_to_scene_graph(widget_root)
|
||||
self.update_operators_position()
|
||||
# self.UpdateWindowUI(wx.UPDATE_UI_RECURSE)
|
||||
# print("self.UpdateWindowUI has executed")
|
||||
# self.Refresh()
|
||||
|
||||
def delete_widget_for_operator(self, operator):
|
||||
"""
|
||||
:param Operator operator: the operator for which we want to delete the widget
|
||||
"""
|
||||
op_widget = self.get_operator_widget(operator)
|
||||
op_widget.remove_from_scene_graph()
|
||||
|
||||
self.operator_to_widget.pop(operator)
|
||||
|
||||
def add_wire_widget(self, wire):
|
||||
"""
|
||||
creates a wire widget and adds it to this dataflow view
|
||||
:param wire: the wire represented by the widget to create
|
||||
:type wire: Wire
|
||||
:rtype: WireWidget
|
||||
"""
|
||||
wire_widget = WireWidget(self)
|
||||
wire_widget.source_plug_widget = self.get_plug_widget(wire.input_plug)
|
||||
wire_widget.dest_plug_widget = self.get_plug_widget(wire.output_plug)
|
||||
self.wire_to_widget[wire] = wire_widget
|
||||
widget_root = msspecgui.scenegraph2d.Group()
|
||||
self.scene.add_child(widget_root)
|
||||
wire_widget.render_to_scene_graph(widget_root)
|
||||
self.update_operators_position()
|
||||
return wire_widget
|
||||
|
||||
def delete_widget_for_wire(self, wire):
|
||||
"""
|
||||
:param Wire wire: the wire for which we want to delete the widget
|
||||
"""
|
||||
wire_widget = self.get_wire_widget(wire)
|
||||
wire_widget.remove_from_scene_graph()
|
||||
# widget_root = wire_widget.parent()
|
||||
# self.scene.remove_child(widget_root)
|
||||
|
||||
self.wire_to_widget.pop(wire)
|
||||
self.update_operators_position()
|
||||
|
||||
def get_wire_widget(self, wire):
|
||||
"""
|
||||
Returns the widget associated with the given wire
|
||||
:param Wire wire:
|
||||
:return WireWidget:
|
||||
"""
|
||||
return self.wire_to_widget[wire]
|
||||
|
||||
def get_operator_widget(self, operator):
|
||||
"""
|
||||
Returns the operator widget associated with the given operator
|
||||
:param Operator operator:
|
||||
:return OperatorWidget:
|
||||
"""
|
||||
return self.operator_to_widget[operator]
|
||||
|
||||
def get_plug_widget(self, plug):
|
||||
"""
|
||||
Returns the plug widget associated with the given plug
|
||||
:type plug: Plug
|
||||
:rtype: PlugWidget
|
||||
"""
|
||||
operator_widget = self.get_operator_widget(plug.operator)
|
||||
plug_widget = operator_widget.get_plug_widget(plug)
|
||||
return plug_widget
|
||||
|
||||
def render(self, display_context):
|
||||
"""
|
||||
renders this dataflow view in the given drawing context
|
||||
|
||||
:param display_context: the drawing context in which to render the dataflow
|
||||
:type display_context: a wx context
|
||||
"""
|
||||
#cairo_context = wx.lib.wxcairo.ContextFromDC(display_context)
|
||||
w, h = self.GetClientSize()
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
|
||||
cairo_context = cairo.Context(surface)
|
||||
|
||||
msspecgui.scenegraph2d.cairo.render_scene(self.scene, cairo_context)
|
||||
|
||||
bitmap = wx.lib.wxcairo.BitmapFromImageSurface(surface)
|
||||
display_context.DrawBitmap(bitmap, 0, 0)
|
||||
|
||||
def get_pointed_widget(self, pointer_position):
|
||||
"""
|
||||
returns the widget that is at the position pointer_position
|
||||
"""
|
||||
hits = self.scene.pick(pointer_position[0], pointer_position[1])
|
||||
if len(hits) > 0:
|
||||
svg_path, local_pos = hits[-1] # @UnusedVariable
|
||||
# print("hit svg node stack : %s" % str(svg_path))
|
||||
for svg_node in reversed(svg_path):
|
||||
if svg_node in self.scenegraph_group_to_widget:
|
||||
return self.scenegraph_group_to_widget[svg_node]
|
||||
|
||||
# for widget in self.operator_to_widget.itervalues():
|
||||
# if widget.get_bounding_box().Contains(pointer_position):
|
||||
# return widget
|
||||
return None
|
||||
|
||||
# def select_widget(self, widget):
|
||||
# if self.selected_widget is not None:
|
||||
# self.selected_widget.set_selected_state(False)
|
||||
# self.selected_widget = widget
|
||||
# self.selected_widget.set_selected_state(True)
|
||||
|
||||
def on_left_down(self, event):
|
||||
pos = event.GetPositionTuple()
|
||||
display_context = wx.ClientDC(self)
|
||||
display_context.DrawCircle(pos[0], pos[1], 5)
|
||||
widget = self.get_pointed_widget(pos)
|
||||
if widget is not None:
|
||||
# self.select_widget(widget)
|
||||
if isinstance(widget, PlugWidget):
|
||||
plug_widget = widget
|
||||
if plug_widget.is_connectable():
|
||||
self.plug_being_connected = plug_widget
|
||||
wire_widget = WireWidget(self)
|
||||
if plug_widget.plug.is_source():
|
||||
wire_widget.source_plug_widget = plug_widget
|
||||
else:
|
||||
wire_widget.dest_plug_widget = plug_widget
|
||||
self.wire_being_created_widget = wire_widget
|
||||
self.wire_being_created_widget.render_to_scene_graph(self.scene)
|
||||
self.is_left_down = True
|
||||
self.Refresh()
|
||||
|
||||
def on_left_up(self, event):
|
||||
self.is_left_down = False
|
||||
self.plug_being_connected = None
|
||||
|
||||
if self.wire_being_created_widget is not None:
|
||||
pos = event.GetPositionTuple()
|
||||
pointed_widget = self.get_pointed_widget(pos)
|
||||
if pointed_widget is not None and isinstance(pointed_widget, PlugWidget):
|
||||
if self.wire_being_created_widget.is_valid_final_plug_widget(pointed_widget):
|
||||
self.wire_being_created_widget.set_final_plug_widget(pointed_widget)
|
||||
self.dataflow.create_wire(self.wire_being_created_widget.source_plug_widget.plug, self.wire_being_created_widget.dest_plug_widget.plug)
|
||||
self.wire_being_created_widget.remove_from_scene_graph()
|
||||
self.wire_being_created_widget = None
|
||||
self.Refresh()
|
||||
|
||||
def on_move(self, event):
|
||||
pos = event.GetPositionTuple()
|
||||
if self.wire_being_created_widget is not None:
|
||||
self.wire_being_created_widget.set_pointer_pos(pos)
|
||||
widget = self.get_pointed_widget(pos)
|
||||
# print('widget at (%d,%d) : %s\n' % (pos[0], pos[1], widget))
|
||||
if self.hovered_widget is not None:
|
||||
if self.hovered_widget != widget:
|
||||
self.hovered_widget.on_hover(False)
|
||||
self.hovered_widget = None
|
||||
if widget is not None:
|
||||
if self.hovered_widget is None:
|
||||
widget.on_hover(True)
|
||||
self.hovered_widget = widget
|
||||
|
||||
if self.is_left_down:
|
||||
display_context = wx.ClientDC(self)
|
||||
display_context.DrawCircle(pos[0], pos[1], 3)
|
||||
self.Refresh()
|
||||
|
||||
def on_context_menu(self, event):
|
||||
pos = self.ScreenToClient(event.GetPosition())
|
||||
|
||||
for wire_widget in self.wire_to_widget.itervalues():
|
||||
if wire_widget.get_bounding_box(border=5).Contains(pos):
|
||||
self.on_wire_context_menu(event, wire_widget.wire)
|
||||
return
|
||||
|
||||
for operator_widget in self.operator_to_widget.itervalues():
|
||||
if operator_widget.get_bounding_box().Contains(pos):
|
||||
self.on_operator_context_menu(event, operator_widget.operator)
|
||||
return
|
||||
|
||||
self.on_background_context_menu(event)
|
||||
|
||||
def on_operator_context_menu(self, event, operator):
|
||||
'''
|
||||
called whenever the user right-clicks in an operator of the dataflow
|
||||
'''
|
||||
pass # possibly implemented in derived classes
|
||||
|
||||
def on_wire_context_menu(self, event, wire):
|
||||
'''
|
||||
called whenever the user right-clicks in an operator of the dataflow
|
||||
:param msspec.dataflow.Wire wire: the wire on which the context menu is supposed to act
|
||||
'''
|
||||
pass # possibly implemented in derived classes
|
||||
|
||||
def on_background_context_menu(self, event):
|
||||
'''
|
||||
called whenever the user right-clicks in the background of the dataflow
|
||||
'''
|
||||
pass # possibly implemented in derived classes
|
|
@ -0,0 +1,193 @@
|
|||
from __future__ import print_function
|
||||
import json
|
||||
import tempfile
|
||||
# from pprint import pprint
|
||||
import pydot
|
||||
from .ilayoutmanager import ILayoutManager
|
||||
|
||||
# def pydot_sample():
|
||||
# import pydot # import pydot or you're not going to get anywhere my friend :D
|
||||
#
|
||||
# # first you create a new graph, you do that with pydot.Dot()
|
||||
# graph = pydot.Dot(graph_type='graph')
|
||||
#
|
||||
# # the idea here is not to cover how to represent the hierarchical data
|
||||
# # but rather how to graph it, so I'm not going to work on some fancy
|
||||
# # recursive function to traverse a multidimensional array...
|
||||
# # I'm going to hardcode stuff... sorry if that offends you
|
||||
#
|
||||
# # let's add the relationship between the king and vassals
|
||||
# for i in range(3):
|
||||
# # we can get right into action by "drawing" edges between the nodes in our graph
|
||||
# # we do not need to CREATE nodes, but if you want to give them some custom style
|
||||
# # then I would recomend you to do so... let's cover that later
|
||||
# # the pydot.Edge() constructor receives two parameters, a source node and a destination
|
||||
# # node, they are just strings like you can see
|
||||
# edge = pydot.Edge("king", "lord%d" % i)
|
||||
# # and we obviosuly need to add the edge to our graph
|
||||
# graph.add_edge(edge)
|
||||
#
|
||||
# # now let us add some vassals
|
||||
# vassal_num = 0
|
||||
# for i in range(3):
|
||||
# # we create new edges, now between our previous lords and the new vassals
|
||||
# # let us create two vassals for each lord
|
||||
# for j in range(2):
|
||||
# edge = pydot.Edge("lord%d" % i, "vassal%d" % vassal_num)
|
||||
# graph.add_edge(edge)
|
||||
# vassal_num += 1
|
||||
#
|
||||
# # ok, we are set, let's save our graph into a file
|
||||
# graph.write_png('/tmp/example1.png') # .write_png('example1_graph.png')
|
||||
#
|
||||
# # and we are done!
|
||||
|
||||
# def graphviz_sample():
|
||||
# from graphviz import Digraph
|
||||
#
|
||||
# dot = Digraph(comment='The Round Table')
|
||||
# dot.node('A', 'King Arthur')
|
||||
# dot.node('B', 'Sir Bedevere the Wise')
|
||||
# dot.node('L', 'Sir Lancelot the Brave')
|
||||
#
|
||||
# dot.edges(['AB', 'AL'])
|
||||
# dot.edge('B', 'L', constraint='false')
|
||||
#
|
||||
# # print(dot.source)
|
||||
# # // The Round Table
|
||||
# # digraph {
|
||||
# # A [label="King Arthur"]
|
||||
# # B [label="Sir Bedevere the Wise"]
|
||||
# # L [label="Sir Lancelot the Brave"]
|
||||
# # A -> B
|
||||
# # A -> L
|
||||
# # B -> L [constraint=false]
|
||||
# # }
|
||||
#
|
||||
# dot.render('/tmp/round-table.png', view=True)
|
||||
|
||||
|
||||
class DotLayoutManager(ILayoutManager):
|
||||
"""A layout manager that uses graphviz's dot
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(DotLayoutManager, self).__init__()
|
||||
self._method = 'plain' # at the moment we use the plain method instead of the more robust json method because json method is too recent to be supported on all platforms (on windows, graphviz 2.38 doesn't yet supports json output format)
|
||||
self._tmp_file_path = tempfile.mktemp(suffix='.%s' % self._method, prefix='msspec_layout_')
|
||||
print('DotLayoutManager.__init__ : self._tmp_file_path = %s' % self._tmp_file_path)
|
||||
|
||||
def compute_operators_position(self, data_flow):
|
||||
"""
|
||||
:param DataFlow data_flow:
|
||||
"""
|
||||
# pydot_sample()
|
||||
graph = pydot.Dot(graph_type='graph')
|
||||
|
||||
# node [shape = record,height=.1];
|
||||
|
||||
# attr_to_label = {}
|
||||
|
||||
graph.set_node_defaults(shape='record')
|
||||
for op in data_flow.operators:
|
||||
node = pydot.Node(op.name)
|
||||
label = '"'
|
||||
plug_index = 0
|
||||
for plug in op.get_input_plugs() + op.get_output_plugs():
|
||||
if plug.is_pluggable:
|
||||
if len(label) > 1:
|
||||
label += '|'
|
||||
# label += "<%s> %s" % (plug.name, plug.name)
|
||||
label += "<%s> " % (plug.name)
|
||||
plug_index += 1
|
||||
label += '"'
|
||||
node.set('label', label)
|
||||
graph.add_node(node)
|
||||
|
||||
for wire in data_flow.wires:
|
||||
#: :type wire: Wire
|
||||
src_plug = wire.input_plug
|
||||
dst_plug = wire.output_plug
|
||||
|
||||
src_op = src_plug.operator
|
||||
dst_op = dst_plug.operator
|
||||
|
||||
# print("src_op.name = %s" % src_op.name)
|
||||
# print("dst_op.name = %s" % dst_op.name)
|
||||
edge = pydot.Edge('"%s":%s' % (src_op.name, src_plug.name), '"%s":%s' % (dst_op.name, dst_plug.name))
|
||||
graph.add_edge(edge)
|
||||
|
||||
# print(graph.to_string())
|
||||
# graph.write('/tmp/toto.pdf', format='pdf')
|
||||
func = {'plain': self._graph_to_pos_plain, 'json': self._graph_to_pos_json}[self._method]
|
||||
return func(graph, data_flow)
|
||||
|
||||
def _graph_to_pos_plain(self, graph, data_flow):
|
||||
"""extracts operator positions from the pydot graph using dot's plain output format
|
||||
"""
|
||||
class GraphField(object):
|
||||
X_SIZE = 2
|
||||
Y_SIZE = 3
|
||||
|
||||
class NodeField(object):
|
||||
NAME = 1
|
||||
X = 2
|
||||
Y = 3
|
||||
|
||||
graph.write(self._tmp_file_path, format='plain')
|
||||
|
||||
# example output
|
||||
# graph 1 1.9444 2.5417
|
||||
# node "ase.lattice.bulk_1" 0.375 2.2847 0.75 0.51389 "<output_cluster> " solid record black lightgrey
|
||||
# node "ase.repeat_2" 0.56944 1.2708 0.75 0.51389 "<input_cluster> |<output_cluster> " solid record black lightgrey
|
||||
# node "ase.lattice.bulk_3" 1.5694 1.2708 0.75 0.51389 "<output_cluster> " solid record black lightgrey
|
||||
# node "ase.build.add_adsorbate_4" 1.3056 0.25694 0.83333 0.51389 "<slab> |<adsorbate> |<output_cluster> " solid record black lightgrey
|
||||
# edge "ase.lattice.bulk_1" "ase.repeat_2" 4 0.375 2.0301 0.375 1.8816 0.375 1.6906 0.375 1.5208 solid black
|
||||
# edge "ase.repeat_2" "ase.build.add_adsorbate_4" 4 0.76389 1.0208 0.76389 0.76408 1.0278 0.76369 1.0278 0.50694 solid black
|
||||
# edge "ase.lattice.bulk_3" "ase.build.add_adsorbate_4" 4 1.4344 1.0167 1.3695 0.87155 1.3056 0.68373 1.3056 0.50694 solid black
|
||||
# stop
|
||||
|
||||
positions = {} #: :type positions: dict(str, (float,float))
|
||||
|
||||
scale = 100.0
|
||||
y_max = None
|
||||
with open(self._tmp_file_path) as data_file:
|
||||
for line in data_file.readlines():
|
||||
# print(line)
|
||||
tokens = line.split(' ')
|
||||
if tokens[0] == 'graph':
|
||||
y_max = float(tokens[GraphField.Y_SIZE])
|
||||
elif tokens[0] == 'node':
|
||||
op_name = tokens[NodeField.NAME][1:-1]
|
||||
x = float(tokens[NodeField.X])
|
||||
y = float(tokens[NodeField.Y])
|
||||
# print( op_name, x, y)
|
||||
positions[data_flow.find_operator(op_name)] = ((y_max - y) * scale, x * scale) # graphviz' dot lays th flow of arcs in the down direction by default, and we want it on the right
|
||||
elif tokens[0] == 'edge':
|
||||
break # for efficiency reason, stop parsing as we're not interested in the rest of the file
|
||||
return positions
|
||||
|
||||
def _graph_to_pos_json(self, graph, data_flow):
|
||||
"""extracts operator positions from the pydot graph using dot's json output format
|
||||
"""
|
||||
graph.write('toto.json', format='json')
|
||||
|
||||
with open('toto.json') as data_file:
|
||||
data = json.load(data_file)
|
||||
# pprint(data)
|
||||
|
||||
y_max = float(data["bb"].split(",")[3])
|
||||
|
||||
positions = {} #: :type positions: dict(str, (float,float))
|
||||
if "objects" in data:
|
||||
for op_node in data["objects"]:
|
||||
op_name = op_node["name"]
|
||||
# print(op_node["name"])
|
||||
# print(op_node["pos"])
|
||||
coords = op_node["pos"].split(',')
|
||||
x = float(coords[0])
|
||||
y = float(coords[1])
|
||||
# print( op_name, x, y)
|
||||
positions[data_flow.find_operator(op_name)] = (y_max - y, x) # graphviz' dot lays th flow of arcs in the down direction by default, and we want it on the right
|
||||
|
||||
return positions
|
|
@ -0,0 +1,12 @@
|
|||
import abc
|
||||
|
||||
|
||||
class ILayoutManager(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def compute_operators_position(self, data_flow):
|
||||
"""
|
||||
:param DataFlow data_flow: the data flow that we want to compute the layout for
|
||||
:return dict(msspecgui.dataflow.Operator, (int, int)): a (x, y) position for each operator
|
||||
"""
|
||||
return {}
|
|
@ -0,0 +1,142 @@
|
|||
import wx
|
||||
from wx.lib.agw import floatspin as fs
|
||||
from msspecgui.dataflow import IOperatorCreator
|
||||
|
||||
|
||||
# bug 778 on https://www.brainwy.com/tracker/PyDev/#
|
||||
# autocomplete (code completion) doesn't work for list items inside self members
|
||||
# Hi, The code below shows a case of code completion that doesn't work although it works on very close cases (see below)
|
||||
#
|
||||
# class Toto(object):
|
||||
# def dummy(self):
|
||||
# pass
|
||||
#
|
||||
#
|
||||
# class Titi(object):
|
||||
# def __init__(self, l, s):
|
||||
# """
|
||||
# :param list(Toto) l:
|
||||
# :param str s:
|
||||
# """
|
||||
# self._l = l
|
||||
# self._s = s
|
||||
#
|
||||
# s.capitalize() # <-- autocomplete works on s
|
||||
# self._s.capitalize() # <-- autocomplete works on self._s
|
||||
#
|
||||
# l.pop() # <-- autocomplete works on l
|
||||
# for p1 in l:
|
||||
# p1.dummy() # <-- autocomplete works on p1
|
||||
#
|
||||
# self._l.pop() # <-- autocomplete works on self._l
|
||||
# for p2 in self._l:
|
||||
# p2.dummy() # <-- autocomplete doesn't work on p2
|
||||
|
||||
class PlugsGuiFrame(wx.Dialog):
|
||||
""" The frame containing the graphical user interface for the given plugs
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window_title, plugs):
|
||||
"""
|
||||
:param wx.Widget parent: window that contains this window
|
||||
:param str window_title: window title
|
||||
:param list(msspecgui.dataflow.Plug) plugs: the plugs that this gui allows the user to modify
|
||||
:param msspecgui.dataflow.Plug plug: the plugs that this gui allows the user to modify
|
||||
"""
|
||||
super(PlugsGuiFrame, self).__init__(parent, title=window_title)
|
||||
self._plugs = plugs
|
||||
self._widgets = {} #: :type self._widgets: dict(str, wx.Control)
|
||||
self.initui()
|
||||
self.Centre()
|
||||
self.Show()
|
||||
|
||||
def initui(self):
|
||||
"""
|
||||
the constructor of the interface
|
||||
"""
|
||||
self.vbox = wx.BoxSizer(wx.VERTICAL)
|
||||
self.panel = wx.Panel(parent=self, id=1)
|
||||
main_params_sizer = wx.FlexGridSizer(0, 2, 10, 10)
|
||||
# print("PlugsGuiFrame.initui len(self._plugs) = %d" % len(list(self._plugs)))
|
||||
|
||||
for plug in self._plugs: #: :type plug: msspecgui.dataflow.Plug
|
||||
if not plug.is_pluggable:
|
||||
label = wx.StaticText(self.panel, label=plug.name)
|
||||
main_params_sizer.Add(label)
|
||||
widget = None
|
||||
# print("plug.data_type.get_type_id() = %s" % plug.data_type.get_type_id())
|
||||
if plug.data_type.get_type_id() == 'int':
|
||||
widget = wx.SpinCtrl(self.panel)
|
||||
widget.SetValue(plug.get_value())
|
||||
elif plug.data_type.get_type_id() == 'float':
|
||||
widget = fs.FloatSpin(self.panel,
|
||||
value=plug.get_value(), size=(180, -1),
|
||||
min_val=0.1, max_val=10.00,
|
||||
increment=0.01,
|
||||
style=fs.FS_CENTRE)
|
||||
widget.SetFormat("%f")
|
||||
widget.SetDigits(6)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
main_params_sizer.Add(widget)
|
||||
self._widgets[plug.name] = widget
|
||||
|
||||
ok_cancel_reset_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
# reset_btn = wx.Button(self.panel, 2, label="Reset")
|
||||
# reset_btn.Bind(wx.EVT_BUTTON, self.on_reset)
|
||||
cancel_btn = wx.Button(self.panel, wx.ID_CANCEL, label="Cancel")
|
||||
cancel_btn.Bind(wx.EVT_BUTTON, self.on_close, id=wx.ID_CANCEL)
|
||||
self.ok_btn = wx.Button(self.panel, wx.ID_OK, label="OK")
|
||||
# self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
|
||||
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
|
||||
|
||||
# ok_cancel_reset_sizer.Add(reset_btn, flag=wx.ALL, border=5)
|
||||
ok_cancel_reset_sizer.Add(cancel_btn)
|
||||
ok_cancel_reset_sizer.Add(self.ok_btn, flag=wx.LEFT, border=5)
|
||||
|
||||
self.vbox.Add(main_params_sizer, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
|
||||
|
||||
self.vbox.Add(ok_cancel_reset_sizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
|
||||
self.panel.SetSizer(self.vbox)
|
||||
self.panel.Fit()
|
||||
self.Fit()
|
||||
|
||||
def on_reset(self, event):
|
||||
pass
|
||||
|
||||
def on_close(self, event):
|
||||
event.Skip() # propagate the event so that the dialog closes
|
||||
|
||||
def on_ok(self, event):
|
||||
# print("PlugsGuiFrame.on_ok len(self._plugs) = %d" % len(list(self._plugs)))
|
||||
for plug in self._plugs: #: :type plug: msspecgui.dataflow.Plug
|
||||
# print("plug.data_type.get_type_id() = %s" % plug.data_type.get_type_id())
|
||||
if not plug.is_pluggable:
|
||||
new_value = None
|
||||
widget = self._widgets[plug.name]
|
||||
if plug.data_type.get_type_id() == 'int':
|
||||
new_value = widget.GetValue()
|
||||
# print("PlugsGuiFrame.on_ok : new_value (int) = %d" % new_value)
|
||||
elif plug.data_type.get_type_id() == 'float':
|
||||
new_value = widget.GetValue()
|
||||
# print("PlugsGuiFrame.on_ok : new_value (float) = %f" % new_value)
|
||||
plug.set_value(new_value)
|
||||
event.Skip() # propagate the event so that the dialog closes
|
||||
|
||||
|
||||
class OperatorGui(IOperatorCreator.IAction):
|
||||
"""a graphic user interface to allow the user to modify an operator's parameters
|
||||
|
||||
"""
|
||||
def get_name(self):
|
||||
return 'properties via gui'
|
||||
|
||||
def execute_on_operator(self, operator):
|
||||
"""
|
||||
:param dataflow.operator.Operator operator: the operator related to this action
|
||||
"""
|
||||
dialog = PlugsGuiFrame(None, window_title='operator %s' % operator.name, plugs=operator.get_input_plugs())
|
||||
result = dialog.ShowModal()
|
||||
if result == wx.ID_OK:
|
||||
operator.data_flow.on_modified_operator(operator)
|
||||
dialog.Destroy()
|
|
@ -0,0 +1,119 @@
|
|||
# import wx.lib.wxcairo
|
||||
# import math
|
||||
import msspecgui
|
||||
from msspecgui import scenegraph2d
|
||||
from .widget import Widget
|
||||
# from msspecgui import datafloweditor
|
||||
# from numpy.distutils.misc_util import cxx_ext_match
|
||||
|
||||
|
||||
class OperatorWidget(Widget):
|
||||
'''
|
||||
The widget representing a node in the dataflow
|
||||
'''
|
||||
_g_last_id = 0
|
||||
|
||||
def __init__(self, operator, data_flow_view):
|
||||
"""
|
||||
:param operator: the dataflow operator that this widget graphically represents
|
||||
:type operator: msspecgui.dataflow.Operator
|
||||
:param data_flow_view: the dataflowview to which this operator belongs
|
||||
:type data_flow_view: msspecgui.datafloweditor.DataflowView
|
||||
|
||||
"""
|
||||
super(OperatorWidget, self).__init__(data_flow_view)
|
||||
self._operator = operator
|
||||
self._id = OperatorWidget.get_new_id()
|
||||
self.is_selected = False
|
||||
self.widget_background = None
|
||||
self.main_shape_node = scenegraph2d.Group()
|
||||
self.plug_to_widget = {} # an associative array that gives the widget associated to each plug name
|
||||
|
||||
for side in ['input', 'output']:
|
||||
plugs = {'input': self._operator.get_input_plugs(),
|
||||
'output': self._operator.get_output_plugs()}[side]
|
||||
# plug_index = 0
|
||||
for plug in plugs:
|
||||
if plug.is_pluggable:
|
||||
plug_widget = msspecgui.datafloweditor.PlugWidget(plug, self, data_flow_view)
|
||||
self.plug_to_widget[plug.name] = plug_widget
|
||||
# plug_index += 1
|
||||
|
||||
@classmethod
|
||||
def get_new_id(cls):
|
||||
OperatorWidget._g_last_id += 1
|
||||
return OperatorWidget._g_last_id
|
||||
|
||||
def get_id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
return self._operator
|
||||
|
||||
def update_appearance(self):
|
||||
for plug_widget in self.plug_to_widget.itervalues():
|
||||
plug_widget.update_appearance(mouse_is_above=False) # FIXME : handle the case where the mouse is over the plug widget
|
||||
|
||||
def get_plug_widget(self, plug):
|
||||
"""
|
||||
Returns the plug widget associated with the given plug
|
||||
:type plug: Plug
|
||||
:rtype: PlugWidget
|
||||
"""
|
||||
plug_widget = self.plug_to_widget[plug.name]
|
||||
return plug_widget
|
||||
|
||||
def set_selected_state(self, is_selected):
|
||||
self.is_selected = is_selected
|
||||
self.widget_background.fill = {False: scenegraph2d.Color(128, 128, 128), True: scenegraph2d.Color(192, 192, 128)}[self.is_selected]
|
||||
|
||||
def set_position(self, x, y):
|
||||
self.main_shape_node.parent.transform = [msspecgui.scenegraph2d.Translate(x, y)]
|
||||
assert self.operator is not None
|
||||
for wire in self.operator.wires:
|
||||
if wire in self._data_flow_view.wire_to_widget: # while load the dataflow, wires are not guaranteed to have a widget yet
|
||||
wire_widget = self._data_flow_view.wire_to_widget[wire]
|
||||
wire_widget.update_position()
|
||||
|
||||
def render_to_scene_graph(self, scenegraph_group):
|
||||
"""
|
||||
:param scenegraph_group: the group node that contains the drawing of this element
|
||||
:type scenegraph_group: scenegraph.Group
|
||||
"""
|
||||
rect = scenegraph2d.Rectangle()
|
||||
rect.width = 70.0
|
||||
rect.height = 70.0
|
||||
rect.x = -35.0
|
||||
rect.y = -35.0
|
||||
rect.fill = scenegraph2d.Color(128, 128, 128)
|
||||
self.widget_background = rect
|
||||
self.main_shape_node.add_child(rect)
|
||||
title = "%s (%d)" % (self._operator.creator.get_operator_type_id().split('.')[-1], self.get_id())
|
||||
# import my.source.module
|
||||
# c = my.source.module.Color()
|
||||
title_text = scenegraph2d.Text(title)
|
||||
title_text.fill = scenegraph2d.Color.black
|
||||
title_text.text_anchor = "middle"
|
||||
self.main_shape_node.add_child(title_text)
|
||||
|
||||
for side in ['input', 'output']:
|
||||
(cx, plugs) = {'input': (-25.0, self._operator.get_input_plugs()),
|
||||
'output': (25.0, self._operator.get_output_plugs())}[side]
|
||||
plug_index = 0
|
||||
for p in plugs:
|
||||
if p.is_pluggable:
|
||||
plug_group = scenegraph2d.Group()
|
||||
plug_group.x = cx
|
||||
plug_group.y = -25.0 + 10.0 * plug_index
|
||||
self.main_shape_node.add_child(plug_group)
|
||||
|
||||
plug_widget = self.plug_to_widget[p.name]
|
||||
plug_widget.render_to_scene_graph(plug_group)
|
||||
plug_index += 1
|
||||
self.scenegraph_group = scenegraph_group
|
||||
self.scenegraph_group.add_child(self.main_shape_node)
|
||||
|
||||
def remove_from_scene_graph(self):
|
||||
parent = self.main_shape_node.parent
|
||||
parent.remove_child(self.main_shape_node)
|
|
@ -0,0 +1,83 @@
|
|||
from msspecgui import scenegraph2d
|
||||
from .widget import Widget
|
||||
# import dataflow
|
||||
|
||||
|
||||
class PlugWidget(Widget):
|
||||
'''
|
||||
The widget representing a plug (input or output) of an operator in the dataflow
|
||||
|
||||
'''
|
||||
|
||||
DATA_IS_UNAVAILABLE_COLOR = scenegraph2d.Color(128, 64, 64)
|
||||
DATA_IS_AVAILABLE_COLOR = scenegraph2d.Color(64, 128, 64)
|
||||
HIGHLITED_COLOR = scenegraph2d.Color(128, 255, 128)
|
||||
|
||||
def __init__(self, plug, operator_widget, data_flow_view):
|
||||
"""
|
||||
:param plug: the dataflow plug that this widget graphically represents
|
||||
:type plug: dataflow.Plug
|
||||
@type plug: dataflow.Plug
|
||||
:param operator_widget: the operator widget that this plug widget is attached to
|
||||
:type operator_widget: datafloweditor.OperatorWidget
|
||||
:param data_flow_view: the dataflowview to which this operator belongs
|
||||
:type data_flow_view: datafloweditor.DataflowView
|
||||
|
||||
"""
|
||||
super(PlugWidget, self).__init__(data_flow_view)
|
||||
self.operator_widget = operator_widget
|
||||
self._plug = plug
|
||||
self.main_shape_node = None
|
||||
|
||||
@property
|
||||
def plug(self):
|
||||
"""
|
||||
:rtype: dataflow.plug.Plug
|
||||
"""
|
||||
# plug = dataflow.plug.Plug
|
||||
return self._plug
|
||||
|
||||
@property
|
||||
def centre_pos(self):
|
||||
""" Returns the position of the centre of this plug widget
|
||||
"""
|
||||
# print('PlugWidget.centre_pos : local_to_parent_matrix = %s' % str(self.scenegraph_group.local_to_parent_matrix()))
|
||||
# defs = []
|
||||
# print('PlugWidget.centre_pos : self.scenegraph_group = %s' % str(self.scenegraph_group._xml(defs)))
|
||||
# defs = []
|
||||
# print('PlugWidget.centre_pos : self.scenegraph_group.parent = %s' % str(self.scenegraph_group.parent._xml(defs)))
|
||||
|
||||
return self.scenegraph_group.local_to_world_matrix().project(x=0, y=0)
|
||||
|
||||
def is_connectable(self):
|
||||
if self._plug.is_connected():
|
||||
return False
|
||||
if not self._plug.value_is_available():
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_appearance(self, mouse_is_above):
|
||||
color = {False: self.DATA_IS_UNAVAILABLE_COLOR,
|
||||
True: self.DATA_IS_AVAILABLE_COLOR}[self._plug.value_is_available()]
|
||||
if mouse_is_above and self.is_connectable():
|
||||
color = self.HIGHLITED_COLOR
|
||||
|
||||
self.main_shape_node.fill = color
|
||||
|
||||
def render_to_scene_graph(self, scenegraph_group):
|
||||
"""
|
||||
:param scenegraph_group: the group node that contains the drawing of this element
|
||||
:type scenegraph_group: scenegraph.Group
|
||||
"""
|
||||
|
||||
self.scenegraph_group = scenegraph_group
|
||||
circle_node = scenegraph2d.Circle()
|
||||
circle_node.cx = 0.0
|
||||
circle_node.cy = 0.0
|
||||
circle_node.r = 7.0
|
||||
self.main_shape_node = circle_node
|
||||
scenegraph_group.add_child(circle_node)
|
||||
self.update_appearance(mouse_is_above=False)
|
||||
|
||||
def on_hover(self, is_entering):
|
||||
self.update_appearance(mouse_is_above=is_entering)
|
|
@ -0,0 +1,51 @@
|
|||
'''
|
||||
Created on May 20, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
|
||||
import wx
|
||||
|
||||
|
||||
class Widget(object):
|
||||
'''
|
||||
The widget representing an interactive object in the dataflow (an operator, a plug, etc.)
|
||||
'''
|
||||
|
||||
def __init__(self, data_flow_view):
|
||||
"""
|
||||
:param data_flow_view: the dataflowview to which this operator belongs
|
||||
:type data_flow_view: datafloweditor.DataflowView
|
||||
"""
|
||||
self._container_group = None # the 2d scene graph group node representing this widget
|
||||
self._data_flow_view = data_flow_view
|
||||
|
||||
@property
|
||||
def scenegraph_group(self):
|
||||
"""
|
||||
:rtype: scenegraph.Group
|
||||
"""
|
||||
return self._container_group
|
||||
|
||||
@scenegraph_group.setter
|
||||
def scenegraph_group(self, scenegraph_group):
|
||||
"""
|
||||
:type scenegraph_group: scenegraph.Group
|
||||
"""
|
||||
print('Widget.scenegraph_group.setter : scenegraph_group=%s' % str(scenegraph_group))
|
||||
self._container_group = scenegraph_group
|
||||
self._data_flow_view.scenegraph_group_to_widget[scenegraph_group] = self
|
||||
|
||||
def get_bounding_box(self, border=0):
|
||||
"""
|
||||
:param int border: the width of the border surrounding the box
|
||||
:return wx.Rect: the smallest box containing this widget
|
||||
"""
|
||||
(x_min, y_min), (x_max, y_max) = self.scenegraph_group.aabbox() # (scenegraph_group.setter seen as a method, see https://github.com/PyCQA/pylint/issues/870) pylint: disable=no-member
|
||||
return wx.Rect(x_min - border, y_min - border, x_max - x_min + border * 2, y_max - y_min + border * 2)
|
||||
|
||||
def on_hover(self, is_entering):
|
||||
'''
|
||||
:param is_entering: True is the mouse pointer enters this widget, False if it leaves the widget
|
||||
'''
|
||||
pass
|
|
@ -0,0 +1,111 @@
|
|||
import msspecgui
|
||||
from msspecgui import scenegraph2d
|
||||
# import msspecgui.datafloweditor as datafloweditor
|
||||
import msspecgui.dataflow as dataflow
|
||||
import widget
|
||||
|
||||
|
||||
class WireWidget(widget.Widget):
|
||||
'''
|
||||
The widget representing a wire (a link connectiong two plugs)
|
||||
'''
|
||||
|
||||
def __init__(self, data_flow_view):
|
||||
"""
|
||||
:param data_flow_view: the dataflowview to which this operator belongs
|
||||
:type data_flow_view: datafloweditor.DataflowView
|
||||
|
||||
"""
|
||||
super(WireWidget, self).__init__(data_flow_view)
|
||||
# print('WireWidget constructor')
|
||||
self.main_shape_node = None
|
||||
self._source_plug_widget = None #: :type self._source_plug_widget: msspecgui.datafloweditor.PlugWidget
|
||||
self._dest_plug_widget = None
|
||||
self.main_shape_node = scenegraph2d.Line()
|
||||
self.main_shape_node.fill = scenegraph2d.Color(0, 255, 255)
|
||||
|
||||
@property
|
||||
def wire(self):
|
||||
return self.dest_plug_widget.plug.incoming_wire
|
||||
|
||||
@property
|
||||
def source_plug_widget(self):
|
||||
return self._source_plug_widget
|
||||
|
||||
@source_plug_widget.setter
|
||||
def source_plug_widget(self, plug_widget):
|
||||
self._source_plug_widget = plug_widget
|
||||
self._set_from_pos(plug_widget.centre_pos)
|
||||
|
||||
@property
|
||||
def dest_plug_widget(self):
|
||||
return self._dest_plug_widget
|
||||
|
||||
@dest_plug_widget.setter
|
||||
def dest_plug_widget(self, plug_widget):
|
||||
self._dest_plug_widget = plug_widget
|
||||
self._set_to_pos(plug_widget.centre_pos)
|
||||
|
||||
def _set_from_pos(self, pos):
|
||||
(self.main_shape_node.x1, self.main_shape_node.y1) = pos
|
||||
|
||||
def _set_to_pos(self, pos):
|
||||
(self.main_shape_node.x2, self.main_shape_node.y2) = pos
|
||||
|
||||
def render_to_scene_graph(self, scenegraph_group):
|
||||
"""
|
||||
:param scenegraph_group: the group node that contains the drawing of this element
|
||||
:type scenegraph_group: scenegraph.Group
|
||||
"""
|
||||
self.scenegraph_group = scenegraph_group
|
||||
scenegraph_group.add_child(self.main_shape_node)
|
||||
|
||||
def update_position(self):
|
||||
self._set_from_pos(self.source_plug_widget.centre_pos)
|
||||
self._set_to_pos(self.dest_plug_widget.centre_pos)
|
||||
|
||||
def remove_from_scene_graph(self):
|
||||
parent = self.main_shape_node.parent
|
||||
parent.remove_child(self.main_shape_node)
|
||||
|
||||
def is_construction_wire(self):
|
||||
""" Tells if this widget represents a wire being built interactively by the user
|
||||
"""
|
||||
return self.source_plug_widget is None or self.dest_plug_widget is None
|
||||
|
||||
def set_pointer_pos(self, pos):
|
||||
"""
|
||||
:type pos: a tuple (x,y)
|
||||
"""
|
||||
assert(self.is_construction_wire()) # this call only makes sens if this wire widget represents a wire in construction
|
||||
if self.source_plug_widget is None:
|
||||
self._set_from_pos(pos)
|
||||
else:
|
||||
self._set_to_pos(pos)
|
||||
|
||||
def is_valid_final_plug_widget(self, plug_widget):
|
||||
"""
|
||||
checks whether plug_widget is a valid plug to complete this connection
|
||||
|
||||
:type plug_widget: datafloweditor.PlugWidget
|
||||
"""
|
||||
assert(isinstance(plug_widget, msspecgui.datafloweditor.PlugWidget))
|
||||
assert(self.is_construction_wire())
|
||||
if self.source_plug_widget is None:
|
||||
return plug_widget.plug.is_output_plug()
|
||||
else:
|
||||
return plug_widget.plug.is_input_plug()
|
||||
|
||||
def set_final_plug_widget(self, plug_widget):
|
||||
"""
|
||||
completes this wire with the given plug_widget
|
||||
|
||||
:type plug_widget: datafloweditor.PlugWidget
|
||||
"""
|
||||
assert(isinstance(plug_widget, msspecgui.datafloweditor.PlugWidget))
|
||||
assert(self.is_construction_wire())
|
||||
assert(self.is_valid_final_plug_widget(plug_widget))
|
||||
if self.source_plug_widget is None:
|
||||
self.source_plug_widget = plug_widget
|
||||
else:
|
||||
self.dest_plug_widget = plug_widget
|
|
@ -0,0 +1 @@
|
|||
from dataflowxmlserializer import DataflowSerializer
|
|
@ -0,0 +1,213 @@
|
|||
import abc
|
||||
from xml.dom import minidom
|
||||
|
||||
# from msspecgui.dataflow import Operator
|
||||
from msspecgui.dataflow import IDataflowSerializer
|
||||
from msspecgui.dataflow.datatypes import StringDataType
|
||||
from msspecgui.dataflow.datatypes import FloatDataType
|
||||
from msspecgui.dataflow.datatypes import BoolDataType
|
||||
from msspecgui.dataflow.datatypes import IntDataType
|
||||
|
||||
|
||||
class IDataTypeSerializer(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_data_type(self):
|
||||
"""
|
||||
:return msspecgui.dataflow.IDatatype: the id of the type this seralize deals with
|
||||
"""
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
def value_to_xml(self, value, xml_node):
|
||||
"""
|
||||
:param minidom.Element xml_node: the container node of the serialized value
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def xml_to_value(self, xml_node):
|
||||
"""
|
||||
:param minidom.Element xml_node: the container node of the serialized value
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StringSerializer(IDataTypeSerializer):
|
||||
|
||||
def get_data_type(self):
|
||||
return StringDataType()
|
||||
|
||||
def value_to_xml(self, value, xml_node):
|
||||
xml_node.setAttribute('value', '%s' % value)
|
||||
|
||||
def xml_to_value(self, xml_node):
|
||||
return xml_node.GetAttribute('value')
|
||||
|
||||
|
||||
class FloatSerializer(IDataTypeSerializer):
|
||||
|
||||
def get_data_type(self):
|
||||
return FloatDataType()
|
||||
|
||||
def value_to_xml(self, value, xml_node):
|
||||
xml_node.setAttribute('value', '%f' % value)
|
||||
|
||||
def xml_to_value(self, xml_node):
|
||||
return float(xml_node.GetAttribute('value'))
|
||||
|
||||
|
||||
class BoolSerializer(IDataTypeSerializer):
|
||||
|
||||
def get_data_type(self):
|
||||
return BoolDataType()
|
||||
|
||||
def value_to_xml(self, value, xml_node):
|
||||
xml_node.setAttribute('value', {False: 'false', True: 'true'}[value])
|
||||
|
||||
def xml_to_value(self, xml_node):
|
||||
value_as_str = xml_node.GetAttribute('value')
|
||||
return {'false': False, 'true': True}[value_as_str]
|
||||
|
||||
|
||||
class IntSerializer(IDataTypeSerializer):
|
||||
|
||||
def get_data_type(self):
|
||||
return IntDataType()
|
||||
|
||||
def value_to_xml(self, value, xml_node):
|
||||
xml_node.setAttribute('value', '%d' % value)
|
||||
|
||||
def xml_to_value(self, xml_node):
|
||||
return int(xml_node.GetAttribute('value'))
|
||||
|
||||
|
||||
class DataflowSerializer(IDataflowSerializer):
|
||||
|
||||
FORMAT_VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
super(DataflowSerializer, self).__init__()
|
||||
self._data_type_serializers = {} #: :type self._data_type_serializers: dict[str, IDataTypeSerializer]
|
||||
self._register_data_type_serializer(StringSerializer())
|
||||
self._register_data_type_serializer(FloatSerializer())
|
||||
self._register_data_type_serializer(BoolSerializer())
|
||||
self._register_data_type_serializer(IntSerializer())
|
||||
|
||||
def _register_data_type_serializer(self, data_type_serializer):
|
||||
"""
|
||||
:param IDataTypeSerializer data_type_serializer: the datatype serialize that needs to be registered
|
||||
"""
|
||||
self._data_type_serializers[data_type_serializer.get_data_type().get_type_id()] = data_type_serializer
|
||||
|
||||
def _get_datatype_serializer(self, data_type_id):
|
||||
"""
|
||||
:param str data_type_id:
|
||||
"""
|
||||
return self._data_type_serializers[data_type_id]
|
||||
|
||||
def _operator_as_xml(self, operator, xml_doc):
|
||||
"""creates the xml representation of the given operator
|
||||
|
||||
:param msspecgui.dataflow.Operator operator: the operator
|
||||
:param minidom.Document xml_doc: the xml document that be used to create the xml node
|
||||
:return minidom.Element: the xml representation of the operator
|
||||
"""
|
||||
op_xml_node = xml_doc.createElement('operator')
|
||||
op_xml_node.setAttribute('nodeId', '%d' % operator.id)
|
||||
op_xml_node.setAttribute('operatorTypeId', '%s' % operator.creator.get_operator_type_id())
|
||||
for plug in operator.get_input_plugs():
|
||||
if not plug.is_pluggable:
|
||||
plug_node = xml_doc.createElement(plug.name)
|
||||
data_type_id = plug.data_type.get_type_id()
|
||||
# print('data_type_id = %s' % data_type_id)
|
||||
data_type_serializer = self._get_datatype_serializer(data_type_id)
|
||||
data_type_serializer.value_to_xml(plug.get_value(), plug_node)
|
||||
op_xml_node.appendChild(plug_node)
|
||||
return op_xml_node
|
||||
|
||||
def _create_operator_from_xml(self, op_xml_node, dataflow):
|
||||
"""
|
||||
:param minidom.Element op_xml_node: the xml node describing the operator and its data
|
||||
:param msspec.dataflow.IDataFlow dataflow: the dataflow that contains the resulting operator
|
||||
|
||||
:return msspec.dataflow.Operator: the created operator
|
||||
"""
|
||||
operator_type_id = str(op_xml_node.getAttribute('operatorTypeId'))
|
||||
operator = dataflow.create_operator(operator_type_id)
|
||||
operator.id = int(op_xml_node.getAttribute('nodeId'))
|
||||
dataflow.add_operator(operator)
|
||||
return operator
|
||||
|
||||
def save_dataflow(self, dataflow, file_path):
|
||||
"""
|
||||
:type dataflow: msspecgui.dataflow.DataFlow
|
||||
|
||||
"""
|
||||
xml_doc = minidom.Document()
|
||||
root_xml_node = xml_doc.createElement('dataflow')
|
||||
xml_doc.appendChild(root_xml_node)
|
||||
|
||||
# store a format version so that if the format needs changing, then it will be possible to detect and support old file formats
|
||||
format_version_xml_node = xml_doc.createElement('formatVersion')
|
||||
format_version_xml_node.setAttribute('value', '%d' % self.FORMAT_VERSION)
|
||||
root_xml_node.appendChild(format_version_xml_node)
|
||||
|
||||
last_create_op_id_xml_node = xml_doc.createElement('lastCreatedOperatorId')
|
||||
last_create_op_id_xml_node.setAttribute('value', '%d' % dataflow.last_created_operator_id)
|
||||
root_xml_node.appendChild(last_create_op_id_xml_node)
|
||||
|
||||
operators_xml_node = xml_doc.createElement('operators')
|
||||
root_xml_node.appendChild(operators_xml_node)
|
||||
|
||||
for op in dataflow.operators:
|
||||
op_xml_node = self._operator_as_xml(op, xml_doc)
|
||||
operators_xml_node.appendChild(op_xml_node)
|
||||
|
||||
wires_xml_node = xml_doc.createElement('wires')
|
||||
root_xml_node.appendChild(wires_xml_node)
|
||||
|
||||
for wire in dataflow.wires: #: :type wire: msspec.dataflow.Wire
|
||||
wire_xml_node = xml_doc.createElement('wire')
|
||||
|
||||
wire_xml_node.setAttribute('fromOperator', '%d' % wire.input_plug.operator.id)
|
||||
wire_xml_node.setAttribute('fromAttr', wire.input_plug.name)
|
||||
wire_xml_node.setAttribute('toOperator', '%d' % wire.output_plug.operator.id)
|
||||
wire_xml_node.setAttribute('toAttr', wire.output_plug.name)
|
||||
wires_xml_node.appendChild(wire_xml_node)
|
||||
|
||||
print('save_dataflow : saving to %s\n' % file_path)
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(xml_doc.toprettyxml())
|
||||
|
||||
def load_dataflow(self, file_path, dataflow):
|
||||
"""
|
||||
:param msspecgui.dataflow.DataFlow dataflow: an empty dataflow that will be filled
|
||||
|
||||
"""
|
||||
xml_doc = minidom.parse(file_path)
|
||||
root_xml_node = xml_doc.documentElement
|
||||
|
||||
last_create_op_id_xml_node = root_xml_node.getElementsByTagName('lastCreatedOperatorId')[0]
|
||||
last_created_operator_id = int(last_create_op_id_xml_node.getAttribute('value'))
|
||||
dataflow.last_created_operator_id = last_created_operator_id
|
||||
|
||||
operators_xml_node = root_xml_node.getElementsByTagName('operators')[0]
|
||||
for op_xml_node in operators_xml_node.getElementsByTagName('operator'):
|
||||
# print('load_dataflow : creating operator')
|
||||
self._create_operator_from_xml(op_xml_node, dataflow)
|
||||
|
||||
wires_xml_node = root_xml_node.getElementsByTagName('wires')[0]
|
||||
for wire_xml_node in wires_xml_node.getElementsByTagName('wire'):
|
||||
from_op_id = int(wire_xml_node.getAttribute('fromOperator'))
|
||||
from_attr = wire_xml_node.getAttribute('fromAttr')
|
||||
to_op_id = int(wire_xml_node.getAttribute('toOperator'))
|
||||
to_attr = wire_xml_node.getAttribute('toAttr')
|
||||
|
||||
from_plug = dataflow.get_operator(from_op_id).get_plug(from_attr)
|
||||
to_plug = dataflow.get_operator(to_op_id).get_plug(to_attr)
|
||||
|
||||
# print('load_dataflow : creating wire')
|
||||
dataflow.create_wire(from_plug, to_plug)
|
||||
|
||||
return dataflow
|
|
@ -0,0 +1 @@
|
|||
from .clusterflow import ClusterFlow
|
|
@ -0,0 +1,34 @@
|
|||
'''
|
||||
Created on Jan 18, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
from msspecgui import dataflow
|
||||
from msspecgui.dataflow.datatypes import StringDataType
|
||||
from msspecgui.dataflow.datatypes import FloatDataType
|
||||
from msspecgui.dataflow.datatypes import BoolDataType
|
||||
from msspecgui.dataflow.datatypes import IntDataType
|
||||
from .datatypes import ClusterDataType
|
||||
from .generators import Bulk
|
||||
from .generators import Repeat
|
||||
from .generators import AddAdsorbate
|
||||
|
||||
|
||||
class ClusterFlow(dataflow.DataFlow):
|
||||
'''
|
||||
a dataflow of cluster generator nodes
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
'''
|
||||
Constructor
|
||||
'''
|
||||
super(ClusterFlow, self).__init__()
|
||||
self.register_operator_creator(Bulk.Creator())
|
||||
self.register_operator_creator(Repeat.Creator())
|
||||
self.register_operator_creator(AddAdsorbate.Creator())
|
||||
self.register_data_type(ClusterDataType())
|
||||
self.register_data_type(StringDataType())
|
||||
self.register_data_type(FloatDataType())
|
||||
self.register_data_type(BoolDataType())
|
||||
self.register_data_type(IntDataType())
|
|
@ -0,0 +1,19 @@
|
|||
from msspecgui.dataflow import IDataType
|
||||
from ase import Atoms
|
||||
|
||||
|
||||
class ClusterDataType(IDataType):
|
||||
def __init__(self):
|
||||
IDataType.__init__(self)
|
||||
|
||||
def get_type_id(self):
|
||||
"""
|
||||
Returns a string uniquely identifying this data type
|
||||
"""
|
||||
return 'physics.atomscluster'
|
||||
|
||||
def get_python_class(self): # pylint: disable=no-self-use
|
||||
"""
|
||||
see IDataType.get_python_class
|
||||
"""
|
||||
return Atoms
|
|
@ -0,0 +1,3 @@
|
|||
from msspecgui.msspec.cluster.generators.repeat import Repeat
|
||||
from msspecgui.msspec.cluster.generators.bulk import Bulk
|
||||
from msspecgui.msspec.cluster.generators.addadsorbate import AddAdsorbate
|
|
@ -0,0 +1,43 @@
|
|||
from msspecgui import dataflow
|
||||
from ase.build import add_adsorbate
|
||||
|
||||
|
||||
class AddAdsorbate(dataflow.Operator):
|
||||
"""
|
||||
an operator for ase.build.add_absorbate
|
||||
"""
|
||||
|
||||
class Creator(dataflow.IOperatorCreator):
|
||||
def __init__(self):
|
||||
dataflow.IOperatorCreator.__init__(self)
|
||||
self.add_input_attribute('slab', 'physics.atomscluster', 'the surface onto which the adsorbate should be added')
|
||||
self.add_input_attribute('adsorbate', 'physics.atomscluster', 'the adsorbate')
|
||||
self.add_input_attribute('height', 'float', 'height above the surface.', is_pluggable=False)
|
||||
|
||||
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
|
||||
|
||||
def get_operator_type_id(self):
|
||||
return 'ase.build.add_adsorbate'
|
||||
|
||||
def create_operator(self, dflow):
|
||||
return AddAdsorbate(dflow, self)
|
||||
|
||||
def __init__(self, data_flow, creator):
|
||||
'''
|
||||
Constructor
|
||||
|
||||
:param data_flow: the dataflow that will contain this operator
|
||||
'''
|
||||
dataflow.Operator.__init__(self, data_flow, creator)
|
||||
self.get_plug('height').set_value(0.0)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
see dataflow.Operator.update
|
||||
"""
|
||||
slab = self.get_plug('slab').get_value()
|
||||
adsorbate = self.get_plug('adsorbate').get_value()
|
||||
height = self.get_plug('height').get_value()
|
||||
output_cluster = slab.copy()
|
||||
add_adsorbate(output_cluster, adsorbate, height)
|
||||
self.get_plug('output_cluster').set_value(output_cluster)
|
|
@ -0,0 +1,131 @@
|
|||
from ase.lattice import bulk
|
||||
|
||||
from msspecgui import dataflow
|
||||
|
||||
|
||||
class Bulk(dataflow.Operator):
|
||||
'''
|
||||
a cluster creator that performs what ase.lattice.bulk performs
|
||||
'''
|
||||
|
||||
class Creator(dataflow.IOperatorCreator):
|
||||
def __init__(self):
|
||||
dataflow.IOperatorCreator.__init__(self)
|
||||
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
|
||||
|
||||
# the following attributes are set via a graphical user interface, hence not pluggable
|
||||
self.add_input_attribute('atoms', 'string', 'the atoms that constitute this cluster (eg "MgO")', is_pluggable=False)
|
||||
self.add_input_attribute('structure', 'string', 'the lattice structure (eg "rocksalt")', is_pluggable=False)
|
||||
self.add_input_attribute('a', 'float', 'the lattice constant a', is_pluggable=False)
|
||||
self.add_input_attribute('c', 'float', 'the lattice constant c', is_pluggable=False)
|
||||
self.add_input_attribute('u', 'float', 'the lattice constant u', is_pluggable=False)
|
||||
self.add_input_attribute('is_cubic', 'bool', 'is the lattice cubic', is_pluggable=False)
|
||||
self.add_input_attribute('is_orthorombic', 'bool', 'is the lattice orthorhombic', is_pluggable=False)
|
||||
|
||||
def get_operator_type_id(self):
|
||||
return 'ase.lattice.bulk'
|
||||
|
||||
def create_operator(self, dflow):
|
||||
return Bulk(dflow, self)
|
||||
|
||||
def __init__(self, data_flow, creator):
|
||||
'''
|
||||
Constructor
|
||||
|
||||
:param data_flow: the dataflow that will contain this operator
|
||||
'''
|
||||
dataflow.Operator.__init__(self, data_flow, creator)
|
||||
self.atoms = 'MgO'
|
||||
self.structure = 'rocksalt'
|
||||
self.a = 4.219
|
||||
self.c = 0.0
|
||||
self.u = 0.0
|
||||
self.is_cubic = True
|
||||
self.is_orthorombic = False
|
||||
|
||||
@property
|
||||
def atoms(self):
|
||||
return self.get_plug('atoms').get_value()
|
||||
|
||||
@atoms.setter
|
||||
def atoms(self, atoms):
|
||||
"""
|
||||
:type atoms: str
|
||||
"""
|
||||
self.get_plug('atoms').set_value(atoms)
|
||||
|
||||
@property
|
||||
def structure(self):
|
||||
return self.get_plug('structure').get_value()
|
||||
|
||||
@structure.setter
|
||||
def structure(self, structure):
|
||||
"""
|
||||
:type structure: str
|
||||
"""
|
||||
self.get_plug('structure').set_value(structure)
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
return self.get_plug('a').get_value()
|
||||
|
||||
@a.setter
|
||||
def a(self, a):
|
||||
"""
|
||||
:type a: float
|
||||
"""
|
||||
self.get_plug('a').set_value(a)
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
return self.get_plug('c').get_value()
|
||||
|
||||
@c.setter
|
||||
def c(self, c):
|
||||
"""
|
||||
:type c: float
|
||||
"""
|
||||
self.get_plug('c').set_value(c)
|
||||
|
||||
@property
|
||||
def u(self):
|
||||
return self.get_plug('u').get_value()
|
||||
|
||||
@u.setter
|
||||
def u(self, u):
|
||||
"""
|
||||
:type u: float
|
||||
"""
|
||||
self.get_plug('u').set_value(u)
|
||||
|
||||
@property
|
||||
def is_cubic(self):
|
||||
return self.get_plug('is_cubic').get_value()
|
||||
|
||||
@is_cubic.setter
|
||||
def is_cubic(self, is_cubic):
|
||||
"""
|
||||
:type is_cubic: bool
|
||||
"""
|
||||
self.get_plug('is_cubic').set_value(is_cubic)
|
||||
|
||||
@property
|
||||
def is_orthorombic(self):
|
||||
return self.get_plug('is_orthorombic').get_value()
|
||||
|
||||
@is_orthorombic.setter
|
||||
def is_orthorombic(self, is_orthorombic):
|
||||
"""
|
||||
:type is_orthorombic: bool
|
||||
"""
|
||||
self.get_plug('is_orthorombic').set_value(is_orthorombic)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
see dataflow.Operator.update
|
||||
"""
|
||||
# >>> ase.lattice.bulk('MgO', crystalstructure='rocksalt', a=4.219, c=0.1, u=0.1, cubic=True, orthorhombic=False)
|
||||
# Atoms(symbols='MgOMgOMgOMgO', positions=..., cell=[4.219, 4.219, 4.219], pbc=[True, True, True])
|
||||
print('self.atoms = %s (type = %s)' % (self.atoms, type(self.atoms)))
|
||||
cluster = bulk(self.atoms, crystalstructure=self.structure, a=self.a, c=self.c, u=self.u, cubic=self.is_cubic, orthorhombic=self.is_orthorombic)
|
||||
self.get_plug('output_cluster').set_value(cluster)
|
|
@ -0,0 +1,48 @@
|
|||
from msspecgui import dataflow
|
||||
|
||||
|
||||
class Repeat(dataflow.Operator):
|
||||
"""
|
||||
an operator that repeats the input cluser a given number of times along each axis
|
||||
"""
|
||||
# an operator that removes the atoms of the cluster that are outside the given sphere
|
||||
|
||||
class Creator(dataflow.IOperatorCreator):
|
||||
def __init__(self):
|
||||
dataflow.IOperatorCreator.__init__(self)
|
||||
self.add_input_attribute('input_cluster', 'physics.atomscluster', 'the cluster from which we want to keep the atoms that are inside the given sphere')
|
||||
self.add_input_attribute('repeat_x', 'int', 'the number of repetitions along x axis', is_pluggable=False)
|
||||
self.add_input_attribute('repeat_y', 'int', 'the number of repetitions along y axis', is_pluggable=False)
|
||||
self.add_input_attribute('repeat_z', 'int', 'the number of repetitions along z axis', is_pluggable=False)
|
||||
|
||||
# self.add_input_attribute('inputSphere.center', 'position3', 'the position of the center of the sphere')
|
||||
# self.add_input_attribute('inputSphere.radius', 'float', 'the radius of the center of the sphere')
|
||||
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
|
||||
|
||||
def get_operator_type_id(self):
|
||||
return 'ase.repeat'
|
||||
|
||||
def create_operator(self, dflow):
|
||||
return Repeat(dflow, self)
|
||||
|
||||
def __init__(self, data_flow, creator):
|
||||
'''
|
||||
Constructor
|
||||
|
||||
:param data_flow: the dataflow that will contain this operator
|
||||
'''
|
||||
dataflow.Operator.__init__(self, data_flow, creator)
|
||||
self.get_plug('repeat_x').set_value(2)
|
||||
self.get_plug('repeat_y').set_value(3)
|
||||
self.get_plug('repeat_z').set_value(4)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
see dataflow.Operator.update
|
||||
"""
|
||||
input_cluster = self.get_plug('input_cluster').get_value()
|
||||
rx = self.get_plug('repeat_x').get_value()
|
||||
ry = self.get_plug('repeat_y').get_value()
|
||||
rz = self.get_plug('repeat_z').get_value()
|
||||
output_cluster = input_cluster.repeat((rx, ry, rz))
|
||||
self.get_plug('output_cluster').set_value(output_cluster)
|
|
@ -0,0 +1,435 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
from wx.lib.agw import floatspin as fs
|
||||
"""
|
||||
bulk interface that the user use to create a single cell crystal
|
||||
"""
|
||||
|
||||
|
||||
class StructureDef(object):
|
||||
# enabled widgets
|
||||
class EnabledWidgets(object):
|
||||
covera_flg = 0b00001
|
||||
u_flg = 0b00010
|
||||
c_flg = 0b00100
|
||||
orth_flg = 0b01000
|
||||
cubic_flg = 0b10000
|
||||
|
||||
def __init__(self, ase_name, long_name, min_atoms, max_atoms, default_atoms, enabled_widgets, default_a, default_c=0.1):
|
||||
"""
|
||||
:param ase_name: crystal structure identifier used by ase
|
||||
:param long_name: crystal long name
|
||||
:param min_atoms: minimal number of atoms allowed in crystal
|
||||
:param max_atoms: minimal number of atoms allowed in crystal
|
||||
"""
|
||||
self.long_name = long_name
|
||||
self.ase_name = ase_name
|
||||
self.min_atoms = min_atoms
|
||||
self.max_atoms = max_atoms
|
||||
self.default_atoms = default_atoms
|
||||
self.enabled_widgets = enabled_widgets
|
||||
self.default_a = default_a
|
||||
self.default_c = default_c
|
||||
|
||||
|
||||
class StructureDefs(object):
|
||||
|
||||
def __init__(self):
|
||||
ew = StructureDef.EnabledWidgets
|
||||
self._long_name_to_structure_def = {}
|
||||
self._ase_name_to_structure_def = {}
|
||||
self._add_structure_def(StructureDef('sc', 'Simple Cubic', -1, 1, 'Po', ew.cubic_flg | ew.orth_flg, 3.80))
|
||||
self._add_structure_def(StructureDef('fcc', 'Face Centered Cubic', -1, 1, 'Cu', ew.cubic_flg | ew.orth_flg, 3.615))
|
||||
self._add_structure_def(StructureDef('bcc', 'Body Centered Cubic', -1, 1, 'Fe', ew.cubic_flg | ew.orth_flg, 2.866))
|
||||
self._add_structure_def(StructureDef('hcp', 'Hexagonal Close-Pack', 2, -1, 'Mg', ew.cubic_flg | ew.c_flg | ew.covera_flg, 3.209, 5.210))
|
||||
self._add_structure_def(StructureDef('diamond', 'Diamond', 2, -1, 'C', ew.cubic_flg | ew.orth_flg, 3.5668))
|
||||
self._add_structure_def(StructureDef('zincblende', 'Zincblende', 2, -1, 'ZnS', ew.cubic_flg | ew.orth_flg, 5.420))
|
||||
self._add_structure_def(StructureDef('rocksalt', 'Rocksalt', 2, -1, 'NaCl', ew.cubic_flg | ew.orth_flg, 5.630))
|
||||
self._add_structure_def(StructureDef('cesiumchloride', 'Cesiumchloride', -1, 2, 'CsCl', ew.cubic_flg, 4.110))
|
||||
self._add_structure_def(StructureDef('fluorite', 'Fluorite', -1, 1, 'CaFF', ew.orth_flg | ew.c_flg | ew.covera_flg, 5.463))
|
||||
self._add_structure_def(StructureDef('wurtzite', 'Wurtzite', -1, 1, 'ZnS', ew.cubic_flg | ew.c_flg | ew.u_flg | ew.covera_flg, 3.820, 6.260))
|
||||
|
||||
def _add_structure_def(self, structure_def):
|
||||
"""
|
||||
:type structure_def: StructureDef
|
||||
"""
|
||||
self._long_name_to_structure_def[structure_def.long_name] = structure_def
|
||||
self._ase_name_to_structure_def[structure_def.ase_name] = structure_def
|
||||
|
||||
def get_structure_def(self, long_name=None, ase_name=None):
|
||||
"""
|
||||
:rtype: StructureDef
|
||||
"""
|
||||
assert (long_name is None) ^ (ase_name is None)
|
||||
if long_name is not None:
|
||||
return self._long_name_to_structure_def[long_name]
|
||||
if ase_name is not None:
|
||||
return self._ase_name_to_structure_def[ase_name]
|
||||
|
||||
|
||||
class BulkFrame(wx.Dialog):
|
||||
"""
|
||||
:param structure_defs: list of crystal structures
|
||||
:type structure_defs: dictionary
|
||||
:param selected_crystal_str: the structure that will be
|
||||
used to create the cluster
|
||||
"""
|
||||
|
||||
structure_defs = StructureDefs()
|
||||
|
||||
@classmethod
|
||||
def get_structure_defs(cls):
|
||||
"""
|
||||
"""
|
||||
return cls.structure_defs
|
||||
|
||||
@classmethod
|
||||
def get_structure_id(cls, structure_name):
|
||||
"""
|
||||
:type structure_name: str
|
||||
:rtype: str
|
||||
"""
|
||||
return cls.structure_defs.get_structure_def(long_name=structure_name).ase_name
|
||||
|
||||
@classmethod
|
||||
def get_structure_name(cls, structure_id):
|
||||
"""
|
||||
:type structure_id: str
|
||||
:rtype: str
|
||||
"""
|
||||
return cls.structure_defs.get_structure_def(ase_name=structure_id).long_name
|
||||
|
||||
def get_crystal_def(self, long_name):
|
||||
"""
|
||||
:param long_name : the structure long name (eg 'Face Centered Cubic'
|
||||
:type long_name : str
|
||||
:rtype: StructureDef
|
||||
"""
|
||||
|
||||
return BulkFrame.get_structure_defs().get_structure_def(long_name=long_name)
|
||||
|
||||
selected_crystal_str = ""
|
||||
|
||||
def __init__(self, parent, title, bulk_operator):
|
||||
"""
|
||||
:param parent: window that contains this window
|
||||
:type parent: wx.Widget
|
||||
:param bulk_operator: the bulk_operator that this gui allows the user to modify
|
||||
:type bulk_operator: msspec.cluster.generators.Bulk
|
||||
"""
|
||||
self._bulk_operator = bulk_operator
|
||||
super(BulkFrame, self).__init__(parent, title=title)
|
||||
self.initui()
|
||||
self.Centre()
|
||||
self.Show()
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def initui(self):
|
||||
"""
|
||||
the constructor of the interface
|
||||
"""
|
||||
self.vbox = wx.BoxSizer(wx.VERTICAL)
|
||||
self.panel = wx.Panel(parent=self, id=1)
|
||||
main_params_sizer = wx.GridBagSizer()
|
||||
|
||||
# all crystal structures that can be selected to create the cluster
|
||||
self.crystal_structures = ['Simple Cubic',
|
||||
'Face Centered Cubic',
|
||||
'Body Centered Cubic',
|
||||
'Hexagonal Close-Pack',
|
||||
'Diamond',
|
||||
'Zincblende',
|
||||
'Rocksalt',
|
||||
'Cesiumchloride',
|
||||
'Wurtzite']
|
||||
|
||||
# crystal structure
|
||||
crystalstructure_txt = wx.StaticText(self.panel, label="Crystal structure : ")
|
||||
main_params_sizer.Add(crystalstructure_txt, pos=(1, 0), flag=wx.LEFT | wx.TOP, border=10)
|
||||
# list of crystal structures
|
||||
self.crystr_cbx = wx.ComboBox(self.panel, choices=self.crystal_structures)
|
||||
main_params_sizer.Add(self.crystr_cbx, pos=(1, 1), border=10)
|
||||
crystal_structure_name = BulkFrame.get_structure_name(self._bulk_operator.structure)
|
||||
self.crystr_cbx.Select(self.crystal_structures.index(crystal_structure_name))
|
||||
# a ToolTip associated to the combobox
|
||||
self.crystr_cbx.SetToolTip(wx.ToolTip("choose a crystal structure"))
|
||||
self.crystr_cbx.Bind(wx.EVT_COMBOBOX, self.on_crystal_select)
|
||||
|
||||
# the atoms that constitute the crystal
|
||||
self.atoms_txt = wx.StaticText(self.panel, label="Atoms : ")
|
||||
main_params_sizer.Add(self.atoms_txt, pos=(3, 0), flag=wx.LEFT, border=10)
|
||||
# to insert the name of the cluster
|
||||
self.atoms_txc = wx.TextCtrl(self.panel)
|
||||
main_params_sizer.Add(self.atoms_txc, pos=(3, 1), flag=wx.EXPAND, border=10)
|
||||
# default value is 'MgO'
|
||||
self.atoms_txc.SetLabel(self._bulk_operator.atoms)
|
||||
self.atoms_txc.SetToolTip(wx.ToolTip("the atoms that form the crystal (eg MgO or NaCl)"))
|
||||
self.atoms_txc.Bind(wx.EVT_TEXT, self.alert_crystal_name)
|
||||
self.numatoms_txt = wx.StaticText(self.panel, label="this crystal is composed of two atoms")
|
||||
main_params_sizer.Add(self.numatoms_txt, pos=(4, 1), flag=wx.LEFT, border=10)
|
||||
|
||||
# is cubic
|
||||
self.is_cubic_txt = wx.StaticText(self.panel, label="Cubic : ")
|
||||
main_params_sizer.Add(self.is_cubic_txt, pos=(5, 0), flag=wx.TOP | wx.LEFT, border=10)
|
||||
self.is_cubic_chk = wx.CheckBox(self.panel)
|
||||
main_params_sizer.Add(self.is_cubic_chk, pos=(5, 1), flag=wx.TOP | wx.EXPAND, border=10)
|
||||
self.is_cubic_chk.SetValue(self._bulk_operator.is_cubic)
|
||||
|
||||
# is othorhombic
|
||||
self.is_orth_txt = wx.StaticText(self.panel, label="Orthorhombic : ")
|
||||
main_params_sizer.Add(self.is_orth_txt, pos=(7, 0), flag=wx.TOP | wx.LEFT, border=10)
|
||||
self.is_orth_chk = wx.CheckBox(self.panel)
|
||||
main_params_sizer.Add(self.is_orth_chk, pos=(7, 1), flag=wx.TOP | wx.EXPAND, border=10)
|
||||
self.is_orth_chk.SetValue(self._bulk_operator.is_orthorombic)
|
||||
|
||||
# lattice parameters
|
||||
lattice_params_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.lattice_params_stbox = wx.StaticBox(self.panel, label='Lattice Parameters')
|
||||
lattice_params_subsizer = wx.StaticBoxSizer(self.lattice_params_stbox, wx.VERTICAL)
|
||||
|
||||
lattice_params_subsizer.AddSpacer(3)
|
||||
|
||||
# lattice parameter a
|
||||
a_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
a_txt = wx.StaticText(self.panel, label=" a = ")
|
||||
self.a_fspin = fs.FloatSpin(self.panel,
|
||||
value=self._bulk_operator.a, size=(180, -1),
|
||||
min_val=0.1, max_val=10.00,
|
||||
increment=0.01,
|
||||
style=fs.FS_CENTRE)
|
||||
self.a_fspin.SetFormat("%f")
|
||||
self.a_fspin.SetDigits(6)
|
||||
self.a_fspin.Bind(fs.EVT_FLOATSPIN, self.alert_a)
|
||||
self.a_fspin.Bind(wx.EVT_SPINCTRL, self.on_a_changed)
|
||||
a_sizer.Add(a_txt)
|
||||
a_sizer.Add(self.a_fspin)
|
||||
|
||||
lattice_params_subsizer.AddSpacer(10)
|
||||
|
||||
# lattice parameter c
|
||||
c_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.c_txt = wx.StaticText(self.panel, 6, label=" c = ")
|
||||
self.c_txt.Disable()
|
||||
self.c_fspin = fs.FloatSpin(self.panel,
|
||||
value=self._bulk_operator.c, size=(160, -1),
|
||||
min_val=0.1, max_val=10.00, increment=0.01)
|
||||
self.c_fspin.SetFormat("%f")
|
||||
self.c_fspin.SetDigits(6)
|
||||
self.c_fspin.Bind(wx.EVT_SPINCTRL, self.on_c_changed)
|
||||
self.c_fspin.Disable()
|
||||
c_sizer.Add(self.c_txt)
|
||||
c_sizer.Add(self.c_fspin)
|
||||
|
||||
# lattice parameter u
|
||||
u_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.u_txt = wx.StaticText(self.panel, 7, label=" u = ")
|
||||
self.u_txt.Disable()
|
||||
self.u_fspin = fs.FloatSpin(self.panel,
|
||||
value=self._bulk_operator.u, size=(160, -1),
|
||||
min_val=0.1, max_val=10.00, increment=0.01)
|
||||
self.u_fspin.SetFormat("%f")
|
||||
self.u_fspin.SetDigits(6)
|
||||
self.u_fspin.Disable()
|
||||
u_sizer.Add(self.u_txt)
|
||||
u_sizer.Add(self.u_fspin)
|
||||
|
||||
# lattice parameter c/a
|
||||
covera_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.covera_txt = wx.StaticText(self.panel, label="c/a ratio : ")
|
||||
self.covera_txt.Disable()
|
||||
self.covera_fspin = fs.FloatSpin(self.panel,
|
||||
value='0.0', size=(160, -1),
|
||||
min_val=0.1, max_val=10.00, increment=0.01)
|
||||
self.covera_fspin.SetFormat("%f")
|
||||
self.covera_fspin.SetDigits(6)
|
||||
self.covera_fspin.Bind(wx.EVT_SPINCTRL, self.on_covera_changed)
|
||||
self.covera_fspin.Disable()
|
||||
covera_sizer.Add(self.covera_txt)
|
||||
covera_sizer.Add(self.covera_fspin)
|
||||
|
||||
lattice_params_subsizer.Add(a_sizer)
|
||||
lattice_params_subsizer.AddSpacer(10)
|
||||
lattice_params_subsizer.Add(c_sizer)
|
||||
lattice_params_subsizer.AddSpacer(10)
|
||||
lattice_params_subsizer.Add(u_sizer)
|
||||
lattice_params_subsizer.AddSpacer(10)
|
||||
lattice_params_subsizer.Add(covera_sizer)
|
||||
lattice_params_subsizer.AddSpacer(10)
|
||||
|
||||
lattice_params_sizer.Add(lattice_params_subsizer, flag=wx.ALL | wx.EXPAND, border=5)
|
||||
|
||||
ok_cancel_reset_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
reset_btn = wx.Button(self.panel, 2, label="Reset")
|
||||
reset_btn.Bind(wx.EVT_BUTTON, self.on_reset_crystal_params)
|
||||
cancel_btn = wx.Button(self.panel, wx.ID_CANCEL, label="Cancel")
|
||||
cancel_btn.Bind(wx.EVT_BUTTON, self.on_close, id=wx.ID_CANCEL)
|
||||
self.ok_btn = wx.Button(self.panel, wx.ID_OK, label="OK")
|
||||
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
|
||||
ok_cancel_reset_sizer.Add(reset_btn)
|
||||
ok_cancel_reset_sizer.Add(cancel_btn)
|
||||
ok_cancel_reset_sizer.Add(self.ok_btn)
|
||||
|
||||
self.vbox.Add(main_params_sizer)
|
||||
self.vbox.Add(lattice_params_sizer, proportion=1, flag=wx.LEFT | wx.EXPAND, border=10)
|
||||
self.vbox.Add(ok_cancel_reset_sizer, flag=wx.ALIGN_RIGHT, border=5)
|
||||
self.panel.SetSizer(self.vbox)
|
||||
self.panel.Fit()
|
||||
self.Fit()
|
||||
|
||||
def on_reset_crystal_params(self, event):
|
||||
self.on_crystal_select(event)
|
||||
|
||||
def on_close(self, event):
|
||||
event.Skip() # propagate the event so that the dialog closes
|
||||
|
||||
def on_ok(self, event):
|
||||
a_val = str(self.a_fspin.GetValue())
|
||||
c_val = str(self.c_fspin.GetValue())
|
||||
u_val = str(self.u_fspin.GetValue())
|
||||
covera_val = str(self.covera_fspin.GetValue())
|
||||
|
||||
print('bulk = (%s,' % self.atoms_txc.GetValue() +
|
||||
' %s,' % self.crystr_cbx.GetValue() + ' a= %s,' % a_val +
|
||||
' c= %s,' % c_val +
|
||||
' u= %s,' % u_val +
|
||||
' c/a ratio= %s,' % covera_val +
|
||||
' cubic= %s,' % str(self.is_cubic_chk.GetValue()) +
|
||||
' orthorombic= %s)' % str(self.is_orth_chk.GetValue()))
|
||||
|
||||
self._bulk_operator.atoms = self.atoms_txc.GetValue().encode('ascii', 'ignore')
|
||||
self._bulk_operator.structure = BulkFrame.get_structure_id(self.crystr_cbx.GetValue())
|
||||
self._bulk_operator.a = float(a_val)
|
||||
self._bulk_operator.c = float(c_val)
|
||||
self._bulk_operator.u = float(u_val)
|
||||
self._bulk_operator.is_cubic = self.is_cubic_chk.GetValue()
|
||||
self._bulk_operator.is_orthorombic = self.is_orth_chk.GetValue()
|
||||
event.Skip() # propagate the event so that the dialog closes
|
||||
|
||||
def alert_a(self, event):
|
||||
if self.a_fspin.GetValue() == 0.00:
|
||||
wx.MessageBox("Please enter the parameter a",
|
||||
style=wx.OK | wx.ICON_MASK)
|
||||
|
||||
def alert_crystal_name(self, event):
|
||||
if self.atoms_txc.GetValue() == '':
|
||||
wx.MessageBox("Please enter the Atoms that\ncomposed the crystal ",
|
||||
style=wx.CANCEL | wx.ICON_MASK)
|
||||
|
||||
def on_a_changed(self, event):
|
||||
"""
|
||||
change values of c and covera when the user insert the value of the parameter a
|
||||
"""
|
||||
self.covera_fspin.SetValue(self.c_fspin.GetValue() / self.a_fspin.GetValue())
|
||||
|
||||
def on_c_changed(self, event):
|
||||
"""
|
||||
change values of a and covera when the user insert the value of the parameter c
|
||||
"""
|
||||
self.covera_fspin.SetValue(self.c_fspin.GetValue() / self.a_fspin.GetValue())
|
||||
|
||||
"""
|
||||
def change_covera_val(self):
|
||||
print ("---_cov_---")
|
||||
# if self.c_fspin.GetValue() != 0.00000:
|
||||
self.covera_fspin.SetValue(
|
||||
self.c_fspin.GetValue() / self.a_fspin.GetValue())
|
||||
print ("---***_cov_***---")
|
||||
"""
|
||||
|
||||
def on_covera_changed(self, event):
|
||||
"""
|
||||
change values of c and a when the user
|
||||
insert the value of the parameter covera
|
||||
"""
|
||||
self.change_covera_val()
|
||||
|
||||
def change_covera_val(self):
|
||||
self.c_fspin.SetValue(self.covera_fspin.GetValue() * self.a_fspin.GetValue())
|
||||
|
||||
def get_atoms_constraint_msg(self, num_atoms_min, num_atoms_max):
|
||||
"""
|
||||
return the constraint of atoms number
|
||||
"""
|
||||
message = ''
|
||||
if num_atoms_min != -1:
|
||||
message += 'minimum %d atoms' % num_atoms_min
|
||||
if num_atoms_max != -1:
|
||||
if len(message) > 0:
|
||||
message += ', '
|
||||
message += 'maximum %d atoms' % num_atoms_max
|
||||
return message
|
||||
|
||||
def update_widgets(self, enabled_widgets):
|
||||
"""
|
||||
manage the widgets
|
||||
"""
|
||||
ew = StructureDef.EnabledWidgets
|
||||
|
||||
if enabled_widgets & ew.cubic_flg:
|
||||
self.is_cubic_txt.Enable()
|
||||
self.is_cubic_chk.Enable()
|
||||
self.is_cubic_chk.SetValue(False)
|
||||
else:
|
||||
self.is_cubic_chk.SetValue(False)
|
||||
self.is_cubic_txt.Disable()
|
||||
self.is_cubic_chk.Disable()
|
||||
|
||||
if enabled_widgets & ew.orth_flg:
|
||||
self.is_orth_txt.Enable()
|
||||
self.is_orth_chk.Enable()
|
||||
self.is_orth_chk.SetValue(False)
|
||||
else:
|
||||
self.is_orth_chk.SetValue(False)
|
||||
self.is_orth_txt.Disable()
|
||||
self.is_orth_chk.Disable()
|
||||
|
||||
if enabled_widgets & ew.c_flg:
|
||||
self.c_txt.Enable()
|
||||
self.c_fspin.Enable()
|
||||
else:
|
||||
self.c_txt.Disable()
|
||||
self.c_fspin.Disable()
|
||||
|
||||
if enabled_widgets & ew.u_flg:
|
||||
self.u_txt.Enable()
|
||||
self.u_fspin.Enable()
|
||||
else:
|
||||
self.u_txt.Disable()
|
||||
self.u_fspin.Disable()
|
||||
|
||||
if enabled_widgets & ew.covera_flg:
|
||||
self.covera_txt.Enable()
|
||||
self.covera_fspin.Enable()
|
||||
else:
|
||||
self.covera_txt.Disable()
|
||||
self.covera_fspin.Disable()
|
||||
|
||||
def on_crystal_select(self, event):
|
||||
"""
|
||||
when the user choose one structure this function will be triggered
|
||||
"""
|
||||
|
||||
self.selected_crystal_str = self.crystr_cbx.GetValue()
|
||||
# print self.selected_crystal_str
|
||||
|
||||
crystal_def = self.get_crystal_def(self.selected_crystal_str)
|
||||
# print ('values : %s' % crystal_def)
|
||||
self.numatoms_txt.SetLabel(str(self.get_atoms_constraint_msg(crystal_def.min_atoms, crystal_def.max_atoms)))
|
||||
self.update_widgets(crystal_def.enabled_widgets)
|
||||
self.atoms_txc.SetLabel(crystal_def.default_atoms)
|
||||
self.a_fspin.SetValue(crystal_def.default_a)
|
||||
self.c_fspin.SetValue(crystal_def.default_c)
|
||||
self.covera_fspin.SetValue(crystal_def.default_c / crystal_def.default_a)
|
||||
|
||||
self.a_fspin.Bind(fs.EVT_FLOATSPIN, self.alert_a)
|
||||
self.lattice_params_stbox.Refresh()
|
||||
self.vbox.RecalcSizes()
|
||||
self.panel.SetSizer(self.vbox)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = wx.App()
|
||||
BulkFrame(None, title="create a single cell", bulk_operator=None)
|
||||
app.MainLoop()
|
|
@ -0,0 +1,294 @@
|
|||
'''
|
||||
Created on Jan 18, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
|
||||
# standard libraries
|
||||
import os
|
||||
import functools
|
||||
# import math
|
||||
# import sys
|
||||
# from fileinput import close
|
||||
|
||||
import ase.io
|
||||
# 3rd party libraries
|
||||
import wx
|
||||
# import cairo
|
||||
# import wx.lib.wxcairo
|
||||
|
||||
# local libraries
|
||||
import msspecgui.datafloweditor as datafloweditor
|
||||
import msspecgui.dataflow as dataflow
|
||||
from msspecgui.dataflow import IDataType
|
||||
from msspecgui.msspec.gui.bulk_interface import BulkFrame
|
||||
from msspecgui.msspec.gui.viewmanager import IViewCreator
|
||||
from msspecgui.datafloweditor import OperatorGui
|
||||
|
||||
# based on example https://github.com/wxWidgets/wxPython/blob/master/demo/Cairo.py
|
||||
|
||||
|
||||
class BackgroundContextMenu(wx.Menu):
|
||||
'''
|
||||
the context menu that the user sees when right-clicking on the background of the dataflow
|
||||
|
||||
'''
|
||||
def __init__(self, panel):
|
||||
"""
|
||||
:type panel: msspecgui.msspec.gui.ClusterEditor
|
||||
"""
|
||||
wx.Menu.__init__(self)
|
||||
self.m_panel = panel
|
||||
|
||||
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
|
||||
# self.add_cluster_modifier_menu_item = self.Append(wx.ID_ANY,"Add Cluster Modifier"," adds a cluster modifier in the cluster editor")
|
||||
|
||||
self.add_cluster_modifier_menu = wx.Menu()
|
||||
self.add_cluster_modifier_menu_item = self.AppendMenu(wx.ID_ANY, "Add Cluster Modifier...", self.add_cluster_modifier_menu)
|
||||
|
||||
# self.m_panel.Bind(wx.EVT_MENU, self.on_add_cluster_modifier, self.add_cluster_modifier_menu_item)
|
||||
|
||||
data_flow = self.m_panel.get_cluster_flow()
|
||||
|
||||
for creator in data_flow.get_operator_creators():
|
||||
operator_type_id = creator.get_operator_type_id()
|
||||
|
||||
menu_item = self.add_cluster_modifier_menu.Append(wx.ID_ANY, operator_type_id, " adds a cluster modifier in the cluster editor")
|
||||
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
|
||||
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_add_cluster_modifier, operator_type_id=operator_type_id), menu_item)
|
||||
|
||||
def on_add_cluster_modifier(self, event, operator_type_id):
|
||||
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
|
||||
|
||||
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
|
||||
"""
|
||||
self.m_panel.create_operator(operator_type_id)
|
||||
|
||||
|
||||
class OperatorContextMenu(wx.Menu):
|
||||
'''
|
||||
the context menu that the user sees when right-clicking on an operator of the dataflow
|
||||
|
||||
'''
|
||||
def __init__(self, panel, operator):
|
||||
"""
|
||||
:type panel: msspecgui.msspec.gui.ClusterEditor
|
||||
:type operator: dataflow.operator.Operator
|
||||
"""
|
||||
wx.Menu.__init__(self)
|
||||
self.m_panel = panel
|
||||
|
||||
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this operator")
|
||||
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_operator, operator=operator), menu_item)
|
||||
|
||||
for action in operator.creator.get_actions():
|
||||
|
||||
menu_item = self.Append(wx.ID_ANY, action.get_name(), " performs this action on the operator")
|
||||
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
|
||||
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_operator_action, action=action, operator=operator), menu_item)
|
||||
|
||||
def on_perform_operator_action(self, event, action, operator):
|
||||
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
|
||||
|
||||
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
|
||||
:type action: dataflow.ioperatorcreator.IOperatorCreator.IAction
|
||||
:type operator: dataflow.operator.Operator
|
||||
"""
|
||||
action.execute_on_operator(operator)
|
||||
|
||||
def on_delete_operator(self, event, operator):
|
||||
"""Callback that is invoked when the user chooses to delete an operator
|
||||
|
||||
:param dataflow.Operator operator: the operator that needs to be deleted
|
||||
"""
|
||||
data_flow = operator.data_flow
|
||||
data_flow.delete_operator(operator)
|
||||
|
||||
|
||||
class WireContextMenu(wx.Menu):
|
||||
'''
|
||||
the context menu that the user sees when right-clicking on a wire of the dataflow
|
||||
|
||||
'''
|
||||
def __init__(self, panel, wire):
|
||||
"""
|
||||
:param msspecgui.msspec.gui.ClusterEditor panel:
|
||||
:param dataflow.Wire wire: the wire for which this context menu is created
|
||||
"""
|
||||
wx.Menu.__init__(self)
|
||||
self.m_panel = panel
|
||||
|
||||
# # create a menu item for each wire action
|
||||
# for wire_action in self.m_panel.get_cluster_flow().wire_actions:
|
||||
# menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
|
||||
# self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
|
||||
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this wire")
|
||||
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_wire, wire=wire), menu_item)
|
||||
|
||||
for data_action in wire.data_type.get_actions():
|
||||
menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
|
||||
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
|
||||
|
||||
# def on_perform_wire_action(self, event, wire_action, wire):
|
||||
# """Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
|
||||
#
|
||||
# :param dataflow.Wire.IAction wire_action: the action to perform
|
||||
# :param dataflow.Wire wire: the wire on which to perform the action
|
||||
# """
|
||||
# wire_action.execute_on_wire(wire)
|
||||
|
||||
def on_perform_data_action(self, event, data_action, data):
|
||||
"""Callback that is invoked when the user chooses to add a specific data action using the context menu
|
||||
|
||||
:param dataflow.IDataType.IAction data_action: the action to perform on the given data
|
||||
:param data: the data on which to perform the action
|
||||
"""
|
||||
assert isinstance(data, data_action.datatype.get_python_class())
|
||||
data_action.execute_on_data(data)
|
||||
|
||||
def on_delete_wire(self, event, wire):
|
||||
"""Callback that is invoked when the user chooses to delete a wire using the context menu
|
||||
|
||||
:param dataflow.Wire wire: the wire that needs to be deleted
|
||||
"""
|
||||
data_flow = wire.data_flow
|
||||
data_flow.delete_wire(wire)
|
||||
|
||||
|
||||
def opj(path):
|
||||
"""Convert paths to the platform-specific separator"""
|
||||
platform_path = apply(os.path.join, tuple(path.split('/')))
|
||||
# HACK: on Linux, a leading / gets lost...
|
||||
if path.startswith('/'):
|
||||
platform_path = '/' + platform_path
|
||||
return platform_path
|
||||
|
||||
|
||||
class BulkGui(dataflow.IOperatorCreator.IAction):
|
||||
|
||||
def get_name(self):
|
||||
return 'properties via gui'
|
||||
|
||||
def execute_on_operator(self, operator):
|
||||
"""
|
||||
:type operator: dataflow.operator.Operator
|
||||
"""
|
||||
dialog = BulkFrame(None, title="create a single cell", bulk_operator=operator)
|
||||
print("execute_on_operator : before MainLoop")
|
||||
result = dialog.ShowModal()
|
||||
if result == wx.ID_OK:
|
||||
print("execute_on_operator : signaling operator %d as modified" % operator.id)
|
||||
operator.data_flow.on_modified_operator(operator)
|
||||
print "OK"
|
||||
else:
|
||||
print "Cancel"
|
||||
dialog.Destroy()
|
||||
|
||||
|
||||
class ExportCluster(IDataType.IAction):
|
||||
|
||||
def __init__(self, cluster_flow):
|
||||
"""
|
||||
:param msspecgui.cluster.ClusterFlow cluster_flow:
|
||||
"""
|
||||
super(ExportCluster, self).__init__(cluster_flow.get_data_type('physics.atomscluster'))
|
||||
|
||||
def get_name(self):
|
||||
return 'export cluster'
|
||||
|
||||
def execute_on_data(self, data):
|
||||
"""
|
||||
:param data: the data on which this action needs to be performed
|
||||
"""
|
||||
assert isinstance(data, self.datatype.get_python_class())
|
||||
dlg = wx.FileDialog(None, message="Choose a file to store this cluster (the format of the file is defined by the file's extension)", defaultDir='', defaultFile='', style=wx.FD_SAVE)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
cluster_file_path = dlg.GetPath()
|
||||
ase.io.write(cluster_file_path, data)
|
||||
dlg.Destroy()
|
||||
|
||||
|
||||
class ClusterEditor(datafloweditor.DataflowView):
|
||||
|
||||
class Creator(IViewCreator):
|
||||
VIEW_TYPE_NAME = 'cluster editor'
|
||||
|
||||
def __init__(self, workspace):
|
||||
"""
|
||||
:param msspecgui.msspec.Workspace workspace: the workspace that is associated with the cluster editors created by this creator
|
||||
"""
|
||||
self._workspace = workspace
|
||||
|
||||
@property
|
||||
def view_type_name(self):
|
||||
"""
|
||||
:return str:
|
||||
"""
|
||||
return self.VIEW_TYPE_NAME
|
||||
|
||||
def create_view(self, parent):
|
||||
"""
|
||||
:param wx.Window parent: the wx.Window that owns the view
|
||||
:return wx.Panel:
|
||||
"""
|
||||
return ClusterEditor(parent, self._workspace.get_cluster_flow())
|
||||
|
||||
def __init__(self, parent, cluster_flow):
|
||||
"""
|
||||
:param cluster_flow: the dataflow that this editor manipulates
|
||||
:type cluster_flow: msspec.cluster.clusterflow.ClusterFlow
|
||||
|
||||
"""
|
||||
super(ClusterEditor, self).__init__(parent, cluster_flow, -1)
|
||||
|
||||
for operator_creator in cluster_flow.get_operator_creators():
|
||||
if operator_creator.get_operator_type_id() == 'ase.lattice.bulk':
|
||||
operator_creator.register_operator_action(BulkGui())
|
||||
else:
|
||||
operator_creator.register_operator_action(OperatorGui())
|
||||
|
||||
datatype = cluster_flow.get_data_type('physics.atomscluster')
|
||||
datatype.register_datatype_action(ExportCluster(cluster_flow))
|
||||
|
||||
def create_operator(self, operator_type_id):
|
||||
operator = self.get_cluster_flow().create_operator(operator_type_id)
|
||||
self.get_cluster_flow().add_operator(operator)
|
||||
|
||||
def get_cluster_flow(self):
|
||||
"""
|
||||
:rtype: msspec.cluster.clusterflow.ClusterFlow
|
||||
"""
|
||||
return self.dataflow
|
||||
|
||||
def on_background_context_menu(self, event):
|
||||
'''
|
||||
called whenever the user right-clicks in the background of the dataflow
|
||||
'''
|
||||
pos = event.GetPosition()
|
||||
# print(pos)
|
||||
pos = self.ScreenToClient(pos)
|
||||
self.PopupMenu(BackgroundContextMenu(self), pos)
|
||||
|
||||
def on_operator_context_menu(self, event, operator):
|
||||
'''
|
||||
called whenever the user right-clicks in an operator of the dataflow
|
||||
|
||||
:type event: wx.Event
|
||||
:type operator: dataflow.operator.Operator
|
||||
'''
|
||||
pos = event.GetPosition()
|
||||
# print(pos)
|
||||
pos = self.ScreenToClient(pos)
|
||||
self.PopupMenu(OperatorContextMenu(self, operator), pos)
|
||||
|
||||
def on_wire_context_menu(self, event, wire):
|
||||
'''
|
||||
called whenever the user right-clicks in a wire of the dataflow
|
||||
|
||||
:type event: wx.Event
|
||||
:param dataflow.Wire wire:
|
||||
'''
|
||||
pos = event.GetPosition()
|
||||
# print(pos)
|
||||
pos = self.ScreenToClient(pos)
|
||||
self.PopupMenu(WireContextMenu(self, wire), pos)
|
|
@ -0,0 +1,201 @@
|
|||
|
||||
import wx
|
||||
from msspecgui.msspec.gui.clusterviewer import ClusterViewer
|
||||
from msspecgui.dataflow import DataFlow
|
||||
from msspecgui.msspec.gui.viewmanager import IViewCreator
|
||||
|
||||
|
||||
class ClusterView(wx.Window):
|
||||
"""
|
||||
a view that allows the user to view a cluster and also to select which cluster is viewed
|
||||
"""
|
||||
|
||||
class Creator(IViewCreator):
|
||||
|
||||
VIEW_TYPE_NAME = '3d cluster viewer'
|
||||
|
||||
def __init__(self, cluster_flow):
|
||||
"""
|
||||
:param msspecgui.msspec.cluster.clusterflow.ClusterFlow cluster_flow: the cluster flow that is associated with the cluster viewers created with this creator
|
||||
"""
|
||||
self._cluster_flow = cluster_flow
|
||||
|
||||
@property
|
||||
def view_type_name(self):
|
||||
"""
|
||||
:return str:
|
||||
"""
|
||||
return self.VIEW_TYPE_NAME
|
||||
|
||||
def create_view(self, parent):
|
||||
"""
|
||||
:param wx.Window parent: the wx.Window that owns the view
|
||||
:return wx.Panel:
|
||||
"""
|
||||
return ClusterView(self._cluster_flow, parent)
|
||||
|
||||
class ClusterFlowEventsHandler(DataFlow.IDataFlowEventsHandler):
|
||||
|
||||
def __init__(self, cluster_view):
|
||||
"""
|
||||
:type cluster_view: ClusterView
|
||||
"""
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).__init__()
|
||||
self._cluster_view = cluster_view
|
||||
# self._selected_cluster_chc = selected_cluster_chc
|
||||
# self.__dataflow = dataflow
|
||||
|
||||
def on_added_operator(self, operator):
|
||||
"""
|
||||
:type operator: Operator
|
||||
"""
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).on_added_operator(operator)
|
||||
self._cluster_view.update_cluster_cbx()
|
||||
|
||||
def on_deleted_operator(self, operator):
|
||||
"""
|
||||
:param Operator operator:
|
||||
"""
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).on_deleted_operator(operator)
|
||||
# the available clusters might have changed
|
||||
self._cluster_view.update_cluster_cbx()
|
||||
# force an update of the displayed atoms
|
||||
if self._cluster_view.selected_cluster is not None:
|
||||
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
|
||||
|
||||
def on_modified_operator(self, operator):
|
||||
"""
|
||||
:type operator: Operator
|
||||
"""
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).on_modified_operator(operator)
|
||||
self._cluster_view.update_cluster_cbx()
|
||||
# force an update of the displayed atoms
|
||||
if self._cluster_view.selected_cluster is not None:
|
||||
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
|
||||
|
||||
def on_added_wire(self, wire):
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).on_added_wire(wire)
|
||||
self._cluster_view.update_cluster_cbx()
|
||||
|
||||
def on_deleted_wire(self, wire):
|
||||
super(ClusterView.ClusterFlowEventsHandler, self).on_deleted_wire(wire)
|
||||
self._cluster_view.update_cluster_cbx()
|
||||
# force an update of the displayed atoms
|
||||
if self._cluster_view.selected_cluster is not None:
|
||||
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
|
||||
|
||||
def __init__(self, cluster_dataflow, *args, **kwargs):
|
||||
"""
|
||||
:param msspecgui.msspec.cluster.clusterflow.ClusterFlow cluster_dataflow: the cluster flow to which this view is attached
|
||||
"""
|
||||
super(ClusterView, self).__init__(*args, **kwargs)
|
||||
self._cluster_dataflow = cluster_dataflow
|
||||
self._available_clusters = None
|
||||
self._selected_cluster = None
|
||||
|
||||
main_box = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(main_box)
|
||||
|
||||
# add the choice widget that allows the user to select the cluster he wants to view
|
||||
widgets_border = 1 # number of pixels used as a border between widgets
|
||||
widgets_spacing = 3 # size of the spacings separating widgets, in pixels
|
||||
selected_cluster_box = wx.BoxSizer(wx.HORIZONTAL)
|
||||
selected_cluster_lbl = wx.StaticText(self, id=-1, label=u'Viewed cluster')
|
||||
selected_cluster_box.Add(selected_cluster_lbl)
|
||||
selected_cluster_box.AddSpacer(widgets_spacing)
|
||||
self._selected_cluster_chc = wx.Choice(self, size=wx.Size(400, -1))
|
||||
selected_cluster_box.Add(self._selected_cluster_chc)
|
||||
main_box.Add(selected_cluster_box, proportion=0, flag=wx.TOP | wx.BOTTOM, border=widgets_border)
|
||||
self._selected_cluster_chc.Bind(wx.EVT_CHOICE, self.on_cluster_selection_changed)
|
||||
|
||||
main_box.AddSpacer(widgets_spacing)
|
||||
|
||||
self._cluster_viewer = ClusterViewer(self)
|
||||
#self._cluster_viewer.light_mode_threshold = 2
|
||||
main_box.Add(self._cluster_viewer, proportion=1, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=widgets_border)
|
||||
|
||||
self._clusterflow_events_handler = ClusterView.ClusterFlowEventsHandler(self)
|
||||
self._cluster_dataflow.add_dataflow_events_handler(self._clusterflow_events_handler)
|
||||
self._clusterflow_events_handler.on_added_operator(operator=None)
|
||||
self.Bind(wx.EVT_CLOSE, self.on_close)
|
||||
|
||||
def on_close(self, event):
|
||||
print("ClusterView.on_close")
|
||||
self._cluster_dataflow.remove_dataflow_events_handler(self._clusterflow_events_handler)
|
||||
self.Close(True)
|
||||
|
||||
@property
|
||||
def cluster_viewer(self):
|
||||
return self._cluster_viewer
|
||||
|
||||
@classmethod
|
||||
def _get_cluster_id_string(cls, plug):
|
||||
"""
|
||||
:type plug: Plug
|
||||
:rtype: str
|
||||
"""
|
||||
op = plug.operator
|
||||
cluster_id = '%s(%d).%s' % (op.creator.get_operator_type_id(), op.id, plug.name)
|
||||
return cluster_id
|
||||
|
||||
def update_cluster_cbx(self):
|
||||
"""update the widget that allows the user to choose the viewed cluster
|
||||
"""
|
||||
print('update_cluster_cbx')
|
||||
available_clusters = []
|
||||
|
||||
for op in self._cluster_dataflow.operators:
|
||||
# cluster_id = '%s (%d)' % (op.creator.get_operator_type_id(), op.id)
|
||||
for plug in op.get_output_plugs():
|
||||
# cluster_id += '.' + plug.name
|
||||
# available_clusters.append(self._get_cluster_id_string(plug))
|
||||
if plug.value_is_available(): # only propose the clusters that can be computed
|
||||
available_clusters.append(plug)
|
||||
# if len(available_clusters) == 0:
|
||||
# available_clusters.append('no cluster available')
|
||||
self._selected_cluster_chc.SetItems([self._get_cluster_id_string(plug) for plug in available_clusters])
|
||||
self._available_clusters = dict(enumerate(available_clusters))
|
||||
|
||||
# keep the selected cluster in the choices, if possible
|
||||
if self._selected_cluster is not None:
|
||||
selected_cluster_indices = [k for k, plug in self._available_clusters.items() if plug == self._selected_cluster]
|
||||
if len(selected_cluster_indices) != 0:
|
||||
selected_cluster_index = selected_cluster_indices[0]
|
||||
self._selected_cluster_chc.SetSelection(selected_cluster_index)
|
||||
else:
|
||||
# the selected cluster is no longer available (for example because its operator has been deleted)
|
||||
self._selected_cluster_chc.SetSelection(wx.NOT_FOUND)
|
||||
self.selected_cluster = None
|
||||
|
||||
@property
|
||||
def selected_cluster(self):
|
||||
"""
|
||||
:rtype: dataflow.Plug
|
||||
"""
|
||||
return self._selected_cluster
|
||||
|
||||
@selected_cluster.setter
|
||||
def selected_cluster(self, selected_cluster):
|
||||
"""
|
||||
:param dataflow.Plug selected_cluster:
|
||||
"""
|
||||
self._selected_cluster = selected_cluster
|
||||
if selected_cluster is not None:
|
||||
# print('ClusterView.selected_cluster : setting self._selected_cluster to %s' % selected_cluster.name)
|
||||
|
||||
# print("ClusterView.selected_cluster : updating viewed atoms")
|
||||
self._cluster_viewer.set_atoms(selected_cluster.get_value(), rescale=True)
|
||||
else:
|
||||
self._cluster_viewer.set_atoms(None, rescale=True)
|
||||
|
||||
def on_cluster_selection_changed(self, event):
|
||||
"""
|
||||
callback when the user changes the selected cluster in the choice widget
|
||||
|
||||
:type event: wx.CommandEvent
|
||||
"""
|
||||
if len(self._available_clusters) != 0:
|
||||
new_selected_cluster = self._available_clusters[event.GetInt()]
|
||||
else:
|
||||
new_selected_cluster = None
|
||||
self.selected_cluster = new_selected_cluster
|
|
@ -0,0 +1,897 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# vim: set fdm=indent ts=2 sw=2 sts=2 et tw=80 cc=+1 mouse=a nu : #
|
||||
# import wx
|
||||
|
||||
import numpy as np
|
||||
# from time import clock
|
||||
# import copy
|
||||
|
||||
import cairo
|
||||
import wx.lib.wxcairo
|
||||
|
||||
# import ase
|
||||
from ase.data import covalent_radii
|
||||
from ase.data.colors import jmol_colors
|
||||
|
||||
|
||||
class ClusterViewer(wx.Window):
|
||||
"""
|
||||
:param mx: last mouse position in x
|
||||
:param my: last mouse position in y
|
||||
"""
|
||||
MODE_NONE = 0b0000000
|
||||
MODE_SELECTION = 0b0000001
|
||||
MODE_SELECTION_BOX = 0b0000010
|
||||
MODE_SELECTION_APPEND = 0b0000100
|
||||
MODE_SELECTION_TOGGLE = 0b0001000
|
||||
MODE_TRANSLATION = 0b0010000
|
||||
MODE_ROTATION = 0b0100000
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_CHILDREN
|
||||
wx.Window.__init__(self, *args, **kwargs)
|
||||
|
||||
self.ox = self.oy = 0 # offset in x and y
|
||||
self.im_ox = self.im_oy = 0 # image offset in x and y
|
||||
self.last_mouse_move_x = self.last_mouse_move_y = 0 # last mouse move
|
||||
self.mx = self.my = 0 # last mouse position
|
||||
self.theta = self.phi = 0
|
||||
self.scale = self.scale0 = 100
|
||||
self.im_factor = self.im_scale = 1
|
||||
self.atoms = None
|
||||
# self.do_rescale = False
|
||||
# self.do_center = False
|
||||
self.atoms_center_of_mass = np.zeros(3)
|
||||
self.atoms_largest_dimension = 1.0 # float, in angstrom
|
||||
self.selection = []
|
||||
self.selection_box = None
|
||||
self.__outer_margin = 0
|
||||
self.surface = None
|
||||
self.busy = False
|
||||
self.refresh_delay = 200
|
||||
self.back_buffer = None
|
||||
self.screenshot = None
|
||||
self.atom_numbers = None
|
||||
self.atom_surfaces = None
|
||||
self.atoms_sprite = None
|
||||
self.background_sprite = None
|
||||
|
||||
self.mode = self.MODE_NONE
|
||||
|
||||
self.colors = {
|
||||
'selection_box': (0.0, 0.4, 1.0),
|
||||
'boulding_box_line': (0.0, 0.4, 1.0, 1.0),
|
||||
'boulding_box_fill': (0.0, 0.4, 1.0, 0.3),
|
||||
}
|
||||
self.sprites_opts = {'alpha': 1, 'glow': True}
|
||||
|
||||
self.light_mode = False
|
||||
self.light_mode_threshold = 2000
|
||||
|
||||
self.rotation_matrix = np.identity(4)
|
||||
self.scale_matrix = np.identity(4)
|
||||
self.translation_matrix = np.identity(4)
|
||||
self.model_matrix = np.identity(4)
|
||||
self.projection_matrix = np.identity(4)
|
||||
# model to world matrix
|
||||
self.m2w_matrix = np.identity(4)
|
||||
# world to view matrix
|
||||
self.w2v_matrix = np.identity(4)
|
||||
# view to projection matrix
|
||||
viewport = (-1., 1., -1., 1., -1., 1.)
|
||||
self.v2p_matrix = self.create_v2p_matrix(*viewport)
|
||||
|
||||
self.projections = None
|
||||
|
||||
self.timer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_PAINT, self.__evt_paint_cb)
|
||||
self.Bind(wx.EVT_SIZE, self.__evt_size_cb)
|
||||
self.Bind(wx.EVT_MOUSEWHEEL, self.__evt_mousewheel_cb)
|
||||
self.Bind(wx.EVT_MOTION, self.__evt_motion_cb)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.__evt_left_down_cb)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.__evt_left_up_cb)
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.__evt_right_up_cb)
|
||||
self.Bind(wx.EVT_TIMER, self.__evt_timer_cb, self.timer)
|
||||
|
||||
def show_emitter(self, show=True):
|
||||
_opts = self.sprites_opts.copy()
|
||||
if show:
|
||||
self.sprites_opts['alpha'] = 0.25
|
||||
self.sprites_opts['glow'] = False
|
||||
else:
|
||||
self.sprites_opts = _opts.copy()
|
||||
|
||||
def set_atoms(self, atoms, rescale=False, center=True):
|
||||
"""
|
||||
Attach an Atoms object to the view.
|
||||
|
||||
This will translate the model to the center of mass, move the model center
|
||||
to the center of screen and adjust the scale to the largest dimension of the
|
||||
model
|
||||
|
||||
:param rescale: if True, the zoom is computed to view the atoms; if False, a fixed zoom value is used
|
||||
"""
|
||||
if atoms is None:
|
||||
self.light_mode = False
|
||||
self.atoms_center_of_mass = np.zeros(3)
|
||||
self.atoms_largest_dimension = 1.0
|
||||
self.atom_numbers = None
|
||||
self.atom_surfaces = None
|
||||
self.atoms_sprite = None
|
||||
self.projections = None
|
||||
else:
|
||||
# Set the light mode according to the number of atoms
|
||||
if len(atoms) > self.light_mode_threshold: # pylint: disable=simplifiable-if-statement
|
||||
self.light_mode = True
|
||||
else:
|
||||
self.light_mode = False
|
||||
|
||||
# get the center of mass
|
||||
self.atoms_center_of_mass = atoms.get_center_of_mass()
|
||||
# get the largest dimension
|
||||
p = atoms.get_positions()
|
||||
self.atoms_largest_dimension = np.max(np.amax(p, axis=0) - np.amin(p, axis=0))
|
||||
if self.atoms_largest_dimension == 0:
|
||||
self.atoms_largest_dimension = 1.0
|
||||
|
||||
# make atoms a class attribute
|
||||
self.atoms = atoms
|
||||
# self.do_rescale = rescale
|
||||
# self.do_center = center
|
||||
self.update_camera(center=center, rescale=rescale)
|
||||
# create the textures
|
||||
self.create_atom_sprites()
|
||||
# finally update the view
|
||||
self.update_drawing()
|
||||
|
||||
def rotate_atoms(self, dtheta, dphi):
|
||||
self.theta += dtheta
|
||||
self.phi += dphi
|
||||
|
||||
tx, ty = (self.theta, self.phi)
|
||||
m_mat = np.zeros((4, 4))
|
||||
m_mat[0, 0] = m_mat[3, 3] = 1
|
||||
m_mat[1, 1] = m_mat[2, 2] = np.cos(np.radians(tx))
|
||||
m_mat[2, 1] = -np.sin(np.radians(tx))
|
||||
m_mat[1, 2] = np.sin(np.radians(tx))
|
||||
|
||||
n_mat = np.zeros((4, 4))
|
||||
n_mat[1, 1] = n_mat[3, 3] = 1
|
||||
n_mat[0, 0] = n_mat[2, 2] = np.cos(np.radians(ty))
|
||||
n_mat[0, 2] = -np.sin(np.radians(ty))
|
||||
n_mat[2, 0] = np.sin(np.radians(ty))
|
||||
|
||||
self.rotation_matrix = np.dot(m_mat, n_mat)
|
||||
self.update_model_matrix()
|
||||
self.scale_atoms(self.scale)
|
||||
|
||||
def scale_atoms(self, factor):
|
||||
self.scale = factor
|
||||
self.scale_matrix[(0, 1, 2), (0, 1, 2)] = factor
|
||||
self.create_atom_sprites()
|
||||
self.update_projection_matrix()
|
||||
|
||||
def translate_atoms(self, x, y):
|
||||
"""
|
||||
sets the translation of the atoms
|
||||
"""
|
||||
# print('translate_atoms : x=%f, y=%f' % (x, y))
|
||||
self.ox = x
|
||||
self.oy = y
|
||||
self.im_ox += self.last_mouse_move_x
|
||||
self.im_oy += self.last_mouse_move_y
|
||||
self.last_mouse_move_x = self.last_mouse_move_y = 0
|
||||
self.translation_matrix[-1, (0, 1)] = (x, y)
|
||||
self.update_projection_matrix()
|
||||
# print('translate_atoms : self.projection_matrix=%s' % str(self.projection_matrix))
|
||||
|
||||
def select_atoms(self, x, y, w=None, h=None, append=False,
|
||||
toggle=False):
|
||||
selection = np.array([])
|
||||
if w is None and h is None:
|
||||
# get the projections
|
||||
p = self.projections.copy()
|
||||
# translate to the event point
|
||||
p[:, :2] -= (x, y)
|
||||
# compute the norm and the radius for each projected atom
|
||||
norm = np.linalg.norm(p[:, :2], axis=1)
|
||||
radii = covalent_radii[p[:, 4].astype(int)] * self.scale
|
||||
# search where the norm is inside an atom
|
||||
i = np.where(norm < radii)
|
||||
# pick up the atom index of the one with the z min
|
||||
try:
|
||||
selection = np.array([int(p[i][np.argmin(p[i, 2]), 5])])
|
||||
# self.selection = np.array([selection])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if w < 0:
|
||||
x += w
|
||||
w = abs(w)
|
||||
if h < 0:
|
||||
y += h
|
||||
h = abs(h)
|
||||
p = self.projections.copy()
|
||||
p = p[np.where(p[:, 0] > x)]
|
||||
p = p[np.where(p[:, 0] < x + w)]
|
||||
p = p[np.where(p[:, 1] > y)]
|
||||
p = p[np.where(p[:, 1] < y + h)]
|
||||
selection = p[:, -1].astype(int)
|
||||
|
||||
if toggle:
|
||||
print(self.selection)
|
||||
# whether atoms in the current selection were previously selected
|
||||
i = np.in1d(self.selection, selection)
|
||||
print(i)
|
||||
self.selection = self.selection[np.invert(i)]
|
||||
|
||||
if append:
|
||||
self.selection = np.append(self.selection, selection)
|
||||
self.selection = np.unique(self.selection)
|
||||
else:
|
||||
self.selection = selection
|
||||
|
||||
def __evt_paint_cb(self, event):
|
||||
self.swap_buffers()
|
||||
|
||||
def __evt_size_cb(self, event):
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
size = self.GetClientSize()
|
||||
self.back_buffer = cairo.ImageSurface(cairo.FORMAT_RGB24, *size)
|
||||
self.create_background_sprite(*size)
|
||||
self.update_drawing()
|
||||
|
||||
def __evt_timer_cb(self, event):
|
||||
self.update_drawing(light=False)
|
||||
self.timer.Stop()
|
||||
|
||||
def __evt_left_down_cb(self, event):
|
||||
self.mx = event.GetX()
|
||||
self.my = event.GetY()
|
||||
self.capture_screen()
|
||||
if event.ControlDown():
|
||||
self.mode |= self.MODE_SELECTION
|
||||
if event.ShiftDown():
|
||||
self.mode |= self.MODE_SELECTION_APPEND
|
||||
if event.AltDown():
|
||||
self.mode |= self.MODE_SELECTION_TOGGLE
|
||||
|
||||
def __evt_left_up_cb(self, event):
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.mode ^= self.MODE_SELECTION
|
||||
# search for atoms in the selection box
|
||||
x, y = event.GetPosition()
|
||||
w = h = None
|
||||
if self.mode & self.MODE_SELECTION_BOX:
|
||||
self.mode ^= self.MODE_SELECTION_BOX
|
||||
x, y, w, h = self.selection_box
|
||||
|
||||
append = False
|
||||
if self.mode & self.MODE_SELECTION_APPEND:
|
||||
self.mode ^= self.MODE_SELECTION_APPEND
|
||||
append = True
|
||||
|
||||
toggle = False
|
||||
if self.mode & self.MODE_SELECTION_TOGGLE:
|
||||
self.mode ^= self.MODE_SELECTION_TOGGLE
|
||||
toggle = True
|
||||
|
||||
self.select_atoms(x, y, w, h, append=append, toggle=toggle)
|
||||
|
||||
if self.mode == self.MODE_TRANSLATION:
|
||||
self.mode ^= self.MODE_TRANSLATION
|
||||
|
||||
self.update_drawing(light=False)
|
||||
|
||||
def __evt_right_up_cb(self, event):
|
||||
if self.mode & self.MODE_ROTATION:
|
||||
self.mode ^= self.MODE_ROTATION
|
||||
self.update_drawing(light=False)
|
||||
|
||||
def __evt_motion_cb(self, event):
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
if event.LeftIsDown():
|
||||
mx, my = event.GetPosition()
|
||||
dx, dy = (mx - self.mx, my - self.my)
|
||||
# if event.ControlDown():
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.mode |= self.MODE_SELECTION_BOX
|
||||
# if event.ShiftDown():
|
||||
# self.mode |= self.MODE_SELECTION_APPEND
|
||||
self.selection_box = [self.mx, self.my, dx, dy]
|
||||
else:
|
||||
self.mode = self.MODE_TRANSLATION
|
||||
self.mx, self.my = (mx, my)
|
||||
self.last_mouse_move_x = int(dx)
|
||||
self.last_mouse_move_y = int(dy)
|
||||
self.ox = int(self.ox + dx)
|
||||
self.oy = int(self.oy + dy)
|
||||
self.translate_atoms(self.ox, self.oy)
|
||||
self.update_drawing()
|
||||
elif event.RightIsDown():
|
||||
self.mode = self.MODE_ROTATION
|
||||
theta = 2. * (float(self.scale0) / self.scale)
|
||||
theta = max(1., theta)
|
||||
mx, my = event.GetPosition()
|
||||
dx, dy = (mx - self.mx, my - self.my)
|
||||
self.mx, self.my = (mx, my)
|
||||
|
||||
tx = theta * np.sign(dy)
|
||||
ty = theta * np.sign(dx)
|
||||
self.rotate_atoms(tx, ty)
|
||||
|
||||
self.update_drawing()
|
||||
|
||||
def __evt_mousewheel_cb(self, event):
|
||||
rot = event.GetWheelRotation()
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
if rot > 0:
|
||||
factor = self.scale * 1.1
|
||||
im_factor = 1 * 1.1
|
||||
elif rot < 0:
|
||||
factor = self.scale / 1.1
|
||||
im_factor = 1 / 1.1
|
||||
self.im_factor = im_factor
|
||||
self.scale_atoms(factor)
|
||||
self.update_drawing()
|
||||
|
||||
def capture_screen(self):
|
||||
# get size of screen
|
||||
w, h = self.GetClientSize()
|
||||
# create a cairo surface and context
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
|
||||
ctx = cairo.Context(surface)
|
||||
# trick here: blit the last back_buffer onto the newly created surface
|
||||
ctx.set_source_surface(self.back_buffer)
|
||||
ctx.paint()
|
||||
# store it as an attribute
|
||||
self.screenshot = surface
|
||||
|
||||
def create_atom_sprites(self):
|
||||
"""
|
||||
This function creates a list of cairo surfaces for each kind
|
||||
of atoms
|
||||
"""
|
||||
|
||||
# Get out if there are no atoms
|
||||
if not self.atoms:
|
||||
return
|
||||
|
||||
# First get an array of all atoms numbers
|
||||
atom_numbers = np.unique(self.atoms.numbers)
|
||||
|
||||
# Now, for each kind of atoms create a surface in memory
|
||||
atom_surfaces = np.empty((2, len(atom_numbers)), dtype=object)
|
||||
self.__outer_margin = 0
|
||||
def create_surface(atom_number, alpha=1, glow=True):
|
||||
#global margin
|
||||
# get the radius, and the color
|
||||
radius = int(covalent_radii[atom_number] * 1. * self.scale)
|
||||
r, g, b = jmol_colors[atom_number]
|
||||
# actually create the surface
|
||||
size = 2 * radius #+ 4
|
||||
self.__outer_margin = np.maximum(self.__outer_margin, size / 2.)
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the ball
|
||||
ctx = cairo.Context(surface)
|
||||
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_source_rgba(r, g, b, alpha)
|
||||
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
|
||||
ctx.fill_preserve()
|
||||
if glow:
|
||||
gradient = cairo.RadialGradient(radius, radius, radius / 2,
|
||||
radius, radius, radius)
|
||||
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
|
||||
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
|
||||
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
|
||||
ctx.set_source(gradient)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(0., 0., 0., alpha)
|
||||
ctx.stroke()
|
||||
|
||||
# Create the overlay for selection
|
||||
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the circle
|
||||
ctx = cairo.Context(overlay)
|
||||
ctx.set_source_surface(surface)
|
||||
ctx.paint()
|
||||
ctx.set_line_width(2.)
|
||||
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
|
||||
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
|
||||
ctx.stroke()
|
||||
|
||||
return surface, overlay
|
||||
|
||||
for i, a in enumerate(atom_numbers):
|
||||
surface, overlay = create_surface(a, alpha=self.sprites_opts['alpha'],
|
||||
glow=self.sprites_opts['glow'])
|
||||
atom_surfaces[0, i] = surface
|
||||
atom_surfaces[1, i] = overlay
|
||||
"""
|
||||
# get the radius, and the color
|
||||
radius = int(covalent_radii[a] * 1. * self.scale)
|
||||
# b, g, r = jmol_colors[a]
|
||||
r, g, b = jmol_colors[a]
|
||||
# actually create the surface
|
||||
size = 2 * radius + 4
|
||||
margin = np.maximum(margin, size / 2.)
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the ball
|
||||
ctx = cairo.Context(surface)
|
||||
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_source_rgba(r, g, b, self.sprites_opts['alpha'])
|
||||
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
|
||||
ctx.fill_preserve()
|
||||
if self.sprites_opts['glow']:
|
||||
gradient = cairo.RadialGradient(radius, radius, radius / 2,
|
||||
radius, radius, radius)
|
||||
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
|
||||
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
|
||||
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
|
||||
ctx.set_source(gradient)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(0., 0., 0., self.sprites_opts['alpha'])
|
||||
ctx.stroke()
|
||||
# store it
|
||||
atom_surfaces[0, i] = surface
|
||||
|
||||
# Create the overlay for selection
|
||||
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the circle
|
||||
ctx = cairo.Context(overlay)
|
||||
ctx.set_source_surface(surface)
|
||||
ctx.paint()
|
||||
ctx.set_line_width(2.)
|
||||
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
|
||||
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
|
||||
ctx.stroke()
|
||||
atom_surfaces[1, i] = overlay
|
||||
"""
|
||||
|
||||
self.atom_numbers = atom_numbers
|
||||
self.atom_surfaces = atom_surfaces
|
||||
try:
|
||||
absorber_number = self.atoms[self.atoms.info['absorber']].number
|
||||
self.absorber_surface = create_surface(absorber_number, alpha=1, glow=True)
|
||||
except:
|
||||
self.atoms.info['absorber'] = -1
|
||||
self.__outer_margin *= 1.1
|
||||
|
||||
def create_background_sprite(self, w, h):
|
||||
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
|
||||
ctx = cairo.Context(surface)
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
g = cairo.LinearGradient(0, 0, 0, h)
|
||||
g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
|
||||
g.add_color_stop_rgba(0.7, 1.0, 1.0, 1.0, 1.0)
|
||||
g.add_color_stop_rgba(1.0, 0.5, 0.5, 0.5, 1.0)
|
||||
ctx.set_source(g)
|
||||
ctx.rectangle(0, 0, w, h)
|
||||
ctx.fill()
|
||||
|
||||
g = cairo.LinearGradient(0, 0, 0, h)
|
||||
#g.add_color_stop_rgba(0., 1., 1., 1., 1.)
|
||||
#g.add_color_stop_rgba(2 / 3., 0.5, 0.5, 0.5, 1)
|
||||
#g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
|
||||
#g.add_color_stop_rgba(0.9, 0.8, 0.8, 0.8, 1.0)
|
||||
#g.add_color_stop_rgba(1.0, 0.2, 0.2, 0.2, 1.0)
|
||||
#ctx.set_source(g)
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.rectangle(0, 0, w, h)
|
||||
ctx.fill()
|
||||
|
||||
ctx.save()
|
||||
|
||||
if False:
|
||||
ctx.set_source_rgb(0.8, 0.8, 0.8)
|
||||
rect = (0, 2 * h / 3, w, h / 3)
|
||||
ctx.rectangle(*rect)
|
||||
ctx.clip()
|
||||
ctx.paint()
|
||||
ctx.set_line_width(1.)
|
||||
for i in np.arange(0, 2 * np.pi, np.pi / 30):
|
||||
ctx.move_to(w / 2, 2 * h / 3)
|
||||
x1 = w * np.cos(i)
|
||||
y1 = w * np.sin(i)
|
||||
ctx.rel_line_to(x1, y1)
|
||||
for i in np.arange(2 * h / 3, h, 10):
|
||||
ctx.move_to(0, i)
|
||||
ctx.line_to(w, i)
|
||||
|
||||
ctx.set_source_rgb(0.7, 0.7, 0.7)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
self.background_sprite = surface
|
||||
|
||||
@classmethod
|
||||
def create_v2p_matrix(cls, left, right, bottom, top, near, far):
|
||||
"""
|
||||
creates the matrix that transforms coordinates from view space (space defined by the bounding box passed as argument) to projection space
|
||||
|
||||
this transformation is a scale and offset that maps [left; right], [bottom; top], [near; far] to [-1;1], [-1;1], [0;1]
|
||||
"""
|
||||
v2p_matrix = np.eye(4)
|
||||
v2p_matrix[0, 0] = 2. / (right - left)
|
||||
v2p_matrix[1, 1] = 2. / (right - left)
|
||||
v2p_matrix[2, 2] = 1. / (near - far)
|
||||
v2p_matrix[3, 0] = (left + right) / (left - right)
|
||||
v2p_matrix[3, 1] = (top + bottom) / (bottom - top)
|
||||
v2p_matrix[3, 2] = near / (near - far)
|
||||
return v2p_matrix
|
||||
|
||||
def update_projection_matrix(self):
|
||||
# print('update_projection_matrix : self.v2p_matrix=%s' % str(self.v2p_matrix))
|
||||
# print('update_projection_matrix : self.translation_matrix=%s' % str(self.translation_matrix))
|
||||
m_matrix = np.dot(self.v2p_matrix, self.scale_matrix)
|
||||
m_matrix = np.dot(m_matrix, self.translation_matrix)
|
||||
self.projection_matrix = m_matrix
|
||||
# print('update_projection_matrix : self.projection_matrix=%s' % str(self.projection_matrix))
|
||||
|
||||
def update_model_matrix(self):
|
||||
m_matrix = np.dot(self.m2w_matrix, self.rotation_matrix)
|
||||
self.model_matrix = m_matrix
|
||||
|
||||
def get_projections(self, points, save=False):
|
||||
m_matrix = np.dot(self.model_matrix, self.projection_matrix)
|
||||
# print('get_projections : self.model_matrix = %s' % str(self.model_matrix))
|
||||
# print('get_projections : self.projection_matrix = %s' % str(self.projection_matrix))
|
||||
# print('get_projections : m_matrix = %s' % str(m_matrix))
|
||||
|
||||
p = points[:, :4]
|
||||
v = np.dot(p, m_matrix)
|
||||
v = v / v[:, -1, None]
|
||||
|
||||
# add the other columns
|
||||
v = np.c_[v, points[:, 4:]]
|
||||
# and sort by Z
|
||||
# v = v[v[:,2].argsort()[::-1]]
|
||||
|
||||
if save:
|
||||
self.projections = v
|
||||
return v
|
||||
|
||||
def filter_projections(self, projections, w, h):
|
||||
try:
|
||||
# filtering
|
||||
margin = self.__outer_margin
|
||||
projections = projections[projections[:, 0] >= -1 * margin, :]
|
||||
projections = projections[projections[:, 0] <= w + margin, :]
|
||||
projections = projections[projections[:, 1] >= -1 * margin, :]
|
||||
projections = projections[projections[:, 1] <= h + margin, :]
|
||||
return projections
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
def render_background(self, ctx):
|
||||
surface = self.background_sprite
|
||||
ctx.set_source_surface(surface, 0, 0)
|
||||
ctx.paint()
|
||||
|
||||
def render_scalebar(self, ctx):
|
||||
x, y, w, h = ctx.clip_extents() # @UnusedVariable
|
||||
scalebar_bb_width = 200
|
||||
scalebar_bb_height = 20
|
||||
ctx.set_source_rgba(0., 0., 0., 0.7)
|
||||
ctx.rectangle(x + w - scalebar_bb_width - 6, h - scalebar_bb_height - 6, scalebar_bb_width, scalebar_bb_height)
|
||||
ctx.fill()
|
||||
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.rectangle(x + w - scalebar_bb_width, h - scalebar_bb_height, 100, scalebar_bb_height - 12)
|
||||
ctx.fill()
|
||||
|
||||
ctx.move_to(x + w - scalebar_bb_width / 2 + 6, h - 9)
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.set_font_size(16)
|
||||
ctx.show_text("%.2f \xc5" % (100. / self.scale))
|
||||
|
||||
def render_axes(self, ctx):
|
||||
_, _, w, h = ctx.clip_extents() # @UnusedVariable
|
||||
m_matrix = np.dot(self.rotation_matrix, self.v2p_matrix)
|
||||
|
||||
d = 20
|
||||
offset = 12
|
||||
|
||||
origin = np.array([0, 0, 0, 1]) # @UnusedVariable
|
||||
x_axis = np.array([d, 0, 0, 1])
|
||||
y_axis = np.array([0, d, 0, 1])
|
||||
z_axis = np.array([0, 0, d, 1])
|
||||
|
||||
# translation = np.array([[1, 0, 0, d + offset], # @UnusedVariable
|
||||
# [0, 1, 0, h - offset],
|
||||
# [0, 0, 1, 1 ],
|
||||
# [0, 0, 0, 1 ]])
|
||||
|
||||
red = (1, 0, 0)
|
||||
green = (0, 0.7, 0)
|
||||
blue = (0, 0, 1)
|
||||
|
||||
# draw a white circle
|
||||
so = np.array([d + offset, h - d - offset, 0])
|
||||
ctx.move_to(d + offset, h - d - offset)
|
||||
ctx.arc(d + offset, h - d - offset, d + offset - 2, 0, 2 * np.pi)
|
||||
#ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.set_source_rgba(0., 0., 0., 0.7)
|
||||
ctx.set_line_width(1)
|
||||
#ctx.stroke_preserve()
|
||||
ctx.set_source_rgba(0.95, 0.95, 0.95, 1)
|
||||
ctx.fill()
|
||||
|
||||
for axis, color, label in ((x_axis, red, 'X'),
|
||||
(y_axis, green, 'Y'),
|
||||
(z_axis, blue, 'Z')):
|
||||
axis = np.dot(axis, m_matrix)
|
||||
axis /= axis[-1]
|
||||
ctx.move_to(*so[:2])
|
||||
ctx.rel_line_to(*axis[:2])
|
||||
ctx.set_source_rgb(*color)
|
||||
ctx.set_line_width(2)
|
||||
ctx.set_font_size(10)
|
||||
ctx.show_text(label)
|
||||
ctx.stroke()
|
||||
|
||||
def render_atoms(self, ctx):
|
||||
try:
|
||||
atoms = self.atoms
|
||||
except:
|
||||
return
|
||||
|
||||
# create a points matrix with homogeneous coordinates
|
||||
# x,y,z,w and the atom number and index
|
||||
points = atoms.get_positions()
|
||||
points = np.c_[points, np.ones(len(points))]
|
||||
points = np.c_[points, atoms.numbers]
|
||||
points = np.c_[points, np.arange(len(atoms))]
|
||||
|
||||
# Get the points array projected to the screen display
|
||||
projections = self.get_projections(points, save=True)
|
||||
# print('render_atoms : len(projections) = %d' % len(projections))
|
||||
# print('render_atoms : projections = %s' % str(projections))
|
||||
|
||||
# Reduce the number of atoms to be drawn if outside the viewport
|
||||
w, h = self.GetClientSize()
|
||||
# print('render_atoms : w = %f, h = %f' % (w, h))
|
||||
projections = self.filter_projections(projections, w, h)
|
||||
# self.projections = projections
|
||||
|
||||
if len(projections) == 0:
|
||||
print('render_atoms : no atom is visible')
|
||||
return # no atom is visible from the camera
|
||||
|
||||
# sort by z
|
||||
projections = projections[projections[:, 2].argsort()[::-1]]
|
||||
|
||||
margin = self.__outer_margin
|
||||
xmin, ymin = np.min(projections[:, :2], axis=0)
|
||||
xmax, ymax = np.max(projections[:, :2], axis=0)
|
||||
sw = xmax - xmin + 2 * margin
|
||||
sh = ymax - ymin + 2 * margin
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(sw), int(sh))
|
||||
surface_ctx = cairo.Context(surface)
|
||||
|
||||
# set the local references
|
||||
set_source_surface = surface_ctx.set_source_surface
|
||||
paint = surface_ctx.paint
|
||||
selection = self.selection
|
||||
atom_numbers = self.atom_numbers
|
||||
atom_surfaces = self.atom_surfaces
|
||||
|
||||
self.im_ox = int(xmin - margin)
|
||||
self.im_oy = int(ymin - margin)
|
||||
surface_ctx.translate(-self.im_ox, -self.im_oy)
|
||||
# surface_ctx.set_source_rgb(1,1,0)
|
||||
# surface_ctx.set_line_width(2.)
|
||||
# surface_ctx.rectangle(self.im_ox, self.im_oy, int(sw), int(sh))
|
||||
# surface_ctx.stroke()
|
||||
for i, p in enumerate(projections): # @UnusedVariable
|
||||
x, y, z, w, n, index = p # @UnusedVariable
|
||||
# load the surface
|
||||
if index in selection:
|
||||
if index == self.atoms.info['absorber']:
|
||||
sprite = self.absorber_surface[1]
|
||||
else:
|
||||
sprite = atom_surfaces[1, np.where(atom_numbers == n)[0][0]]
|
||||
else:
|
||||
if index == self.atoms.info['absorber']:
|
||||
sprite = self.absorber_surface[0]
|
||||
else:
|
||||
sprite = atom_surfaces[0, np.where(atom_numbers == n)[0][0]]
|
||||
|
||||
sx = x - sprite.get_width() / 2.
|
||||
sy = y - sprite.get_height() / 2.
|
||||
set_source_surface(sprite, int(sx), int(sy))
|
||||
paint()
|
||||
if False: # pylint: disable=using-constant-test
|
||||
ctx.set_source_rgb(0., 0., 0.)
|
||||
r = sprite.get_width() / 2
|
||||
ctx.arc(x, y, r, 0, 2 * np.pi)
|
||||
ctx.stroke_preserve()
|
||||
ctx.set_source_rgb(0.8, 0.8, 0.8)
|
||||
ctx.fill()
|
||||
ctx.move_to(x, y)
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.set_font_size(14)
|
||||
ctx.show_text("%d" % index)
|
||||
|
||||
# save the rendering
|
||||
self.atoms_sprite = surface
|
||||
ctx.set_source_surface(surface, self.im_ox, self.im_oy)
|
||||
ctx.paint()
|
||||
|
||||
def render_selection_box(self, ctx):
|
||||
r, g, b = self.colors['selection_box']
|
||||
ctx.set_source_surface(self.screenshot, 0, 0)
|
||||
ctx.paint()
|
||||
ctx.set_source_rgba(r, g, b, 0.3)
|
||||
ctx.rectangle(*self.selection_box)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgb(r, g, b)
|
||||
ctx.stroke()
|
||||
|
||||
def render_boundingbox(self, ctx):
|
||||
# print('render_boundingbox : start')
|
||||
try:
|
||||
atoms = self.atoms
|
||||
except:
|
||||
return
|
||||
|
||||
# create a points matrix with homogeneous coordinates
|
||||
# x,y,z,w for atoms extrema
|
||||
points = atoms.get_positions()
|
||||
margin = self.__outer_margin / float(self.scale)
|
||||
xmin, ymin, zmin = np.min(points, axis=0) - margin
|
||||
xmax, ymax, zmax = np.max(points, axis=0) + margin
|
||||
points = np.array([
|
||||
[xmax, ymax, zmax, 1],
|
||||
[xmax, ymin, zmax, 1],
|
||||
[xmin, ymax, zmax, 1],
|
||||
[xmin, ymin, zmax, 1],
|
||||
[xmax, ymax, zmin, 1],
|
||||
[xmax, ymin, zmin, 1],
|
||||
[xmin, ymax, zmin, 1],
|
||||
[xmin, ymin, zmin, 1]])
|
||||
|
||||
# Get the points array projected to the screen display
|
||||
projections = self.get_projections(points)
|
||||
x0, y0 = (np.min(projections[:, :2], axis=0)).astype(int)
|
||||
x1, y1 = (np.max(projections[:, :2], axis=0)).astype(int)
|
||||
|
||||
# Declare the 6 faces with their vertex index
|
||||
# the order of the numbers define if the normal plane points outward or not
|
||||
faces = np.array([
|
||||
[6, 7, 5, 4],
|
||||
[2, 3, 7, 6],
|
||||
[3, 1, 5, 7],
|
||||
[2, 6, 4, 0],
|
||||
[0, 4, 5, 1],
|
||||
[2, 0, 1, 3]])
|
||||
|
||||
# kind of backface culling
|
||||
ind = []
|
||||
for i, f in enumerate(faces):
|
||||
# Get 2 vectors of the plane
|
||||
v1 = projections[f[1], :3] - projections[f[0], :3]
|
||||
v2 = projections[f[3], :3] - projections[f[0], :3]
|
||||
# cross multiply them to get the normal
|
||||
n = np.cross(v2, v1)
|
||||
# If the normal z coordinate is <0, the plane is not visible, so, draw it
|
||||
# first, otherwise draw it last
|
||||
if n[-1] > 0:
|
||||
ind.append(i)
|
||||
else:
|
||||
ind.insert(0, i)
|
||||
# faces are now re-ordered
|
||||
faces = faces[ind]
|
||||
|
||||
# plane transparency and color
|
||||
color_plane = self.colors['boulding_box_fill']
|
||||
color_line = self.colors['boulding_box_line']
|
||||
|
||||
ctx.save()
|
||||
#ctx.set_source_rgb(1, 0, 0)
|
||||
#ctx.rectangle(x0, y0, x1 - x0, y1 - y0)
|
||||
#ctx.stroke()
|
||||
ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
|
||||
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_dash([8., 8.])
|
||||
for i, p in enumerate(faces):
|
||||
# remove dash for front faces
|
||||
if i > 2:
|
||||
ctx.set_dash([])
|
||||
if i == 3 and not(self.mode & self.MODE_ROTATION): # pylint: disable=superfluous-parens
|
||||
try:
|
||||
f = self.scale / self.scale0
|
||||
# sprite_w = self.atoms_sprite.get_width()
|
||||
# sprite_h = self.atoms_sprite.get_height()
|
||||
# m = self.__outer_margin
|
||||
# sw = float(sprite_w) / (x1 - x0)
|
||||
# sh = float(sprite_h) / (y1 - y0)
|
||||
ctx.save()
|
||||
# ctx.translate((x0 + x1 - sprite_w)/2. ,
|
||||
# (y0 + y1 - sprite_h)/2.)
|
||||
ctx.set_source_surface(self.atoms_sprite, self.im_ox, self.im_oy)
|
||||
ctx.paint()
|
||||
ctx.restore()
|
||||
except:
|
||||
pass
|
||||
ctx.set_source_rgba(*color_plane)
|
||||
# get the projected points
|
||||
p0, p1, p2, p3 = projections[p, :2]
|
||||
# move to the first
|
||||
ctx.move_to(*p0)
|
||||
# line to the others
|
||||
list(map(lambda _: ctx.line_to(*_), (p1, p2, p3)))
|
||||
# close the polygon
|
||||
ctx.close_path()
|
||||
# fill it and stroke the path
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(*color_line)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
def update_camera(self, rescale=False, center=False):
|
||||
# update the scale
|
||||
w, h = self.GetClientSize()
|
||||
l = min(w, h)
|
||||
# print('set_atoms : w=%d h = %d l = %f' % (w, h, l))
|
||||
self.scale0 = int(.5 * l / self.atoms_largest_dimension) + 1
|
||||
|
||||
# move the model to the center of mass
|
||||
t_matrix = np.eye(4)
|
||||
t_matrix[-1, :-1] = -1 * self.atoms_center_of_mass
|
||||
self.m2w_matrix = t_matrix # self.m2w_matrix.dot(t_matrix)
|
||||
self.update_model_matrix()
|
||||
|
||||
if rescale:
|
||||
assert self.scale0 > 0.0
|
||||
self.scale = self.scale0
|
||||
|
||||
#print "scale = ", self.scale, "scale0 = ", self.scale0
|
||||
#self.scale_atoms(1.)
|
||||
|
||||
if center:
|
||||
self.translate_atoms(w / 2, h / 2)
|
||||
|
||||
def update_drawing(self, light=None):
|
||||
# print('update_drawing : light=%s' % str(light))
|
||||
|
||||
try:
|
||||
ctx = cairo.Context(self.back_buffer)
|
||||
except:
|
||||
#self.scale = self.scale0 = 10
|
||||
return
|
||||
|
||||
light_mode = self.light_mode if light is None else light
|
||||
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.render_selection_box(ctx)
|
||||
else:
|
||||
self.render_background(ctx)
|
||||
if self.atoms:
|
||||
if light_mode:
|
||||
self.render_boundingbox(ctx)
|
||||
else:
|
||||
self.render_atoms(ctx)
|
||||
self.render_scalebar(ctx)
|
||||
self.render_axes(ctx)
|
||||
|
||||
self.Refresh(eraseBackground=False)
|
||||
|
||||
def swap_buffers(self):
|
||||
if self.back_buffer:
|
||||
back_buffer = self.back_buffer
|
||||
# w = back_buffer.get_width()
|
||||
# h = back_buffer.get_height()
|
||||
|
||||
bitmap = wx.lib.wxcairo.BitmapFromImageSurface(back_buffer)
|
||||
dc = wx.PaintDC(self)
|
||||
dc.DrawBitmap(bitmap, 0, 0)
|
|
@ -0,0 +1,897 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# vim: set fdm=indent ts=2 sw=2 sts=2 et tw=80 cc=+1 mouse=a nu : #
|
||||
# import wx
|
||||
|
||||
import numpy as np
|
||||
# from time import clock
|
||||
# import copy
|
||||
|
||||
import cairo
|
||||
import wx.lib.wxcairo
|
||||
|
||||
# import ase
|
||||
from ase.data import covalent_radii
|
||||
from ase.data.colors import jmol_colors
|
||||
|
||||
|
||||
class ClusterViewer(wx.Window):
|
||||
"""
|
||||
:param mx: last mouse position in x
|
||||
:param my: last mouse position in y
|
||||
"""
|
||||
MODE_NONE = 0b0000000
|
||||
MODE_SELECTION = 0b0000001
|
||||
MODE_SELECTION_BOX = 0b0000010
|
||||
MODE_SELECTION_APPEND = 0b0000100
|
||||
MODE_SELECTION_TOGGLE = 0b0001000
|
||||
MODE_TRANSLATION = 0b0010000
|
||||
MODE_ROTATION = 0b0100000
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_CHILDREN
|
||||
wx.Window.__init__(self, *args, **kwargs)
|
||||
|
||||
self.ox = self.oy = 0 # offset in x and y
|
||||
self.im_ox = self.im_oy = 0 # image offset in x and y
|
||||
self.last_mouse_move_x = self.last_mouse_move_y = 0 # last mouse move
|
||||
self.mx = self.my = 0 # last mouse position
|
||||
self.theta = self.phi = 0
|
||||
self.scale = self.scale0 = 100
|
||||
self.im_factor = self.im_scale = 1
|
||||
self.atoms = None
|
||||
# self.do_rescale = False
|
||||
# self.do_center = False
|
||||
self.atoms_center_of_mass = np.zeros(3)
|
||||
self.atoms_largest_dimension = 1.0 # float, in angstrom
|
||||
self.selection = []
|
||||
self.selection_box = None
|
||||
self.__outer_margin = 0
|
||||
self.surface = None
|
||||
self.busy = False
|
||||
self.refresh_delay = 200
|
||||
self.back_buffer = None
|
||||
self.screenshot = None
|
||||
self.atom_numbers = None
|
||||
self.atom_surfaces = None
|
||||
self.atoms_sprite = None
|
||||
self.background_sprite = None
|
||||
|
||||
self.mode = self.MODE_NONE
|
||||
|
||||
self.colors = {
|
||||
'selection_box': (0.0, 0.4, 1.0),
|
||||
'boulding_box_line': (0.0, 0.4, 1.0, 1.0),
|
||||
'boulding_box_fill': (0.0, 0.4, 1.0, 0.3),
|
||||
}
|
||||
self.sprites_opts = {'alpha': 1, 'glow': True}
|
||||
|
||||
self.light_mode = False
|
||||
self.light_mode_threshold = 2000
|
||||
|
||||
self.rotation_matrix = np.identity(4)
|
||||
self.scale_matrix = np.identity(4)
|
||||
self.translation_matrix = np.identity(4)
|
||||
self.model_matrix = np.identity(4)
|
||||
self.projection_matrix = np.identity(4)
|
||||
# model to world matrix
|
||||
self.m2w_matrix = np.identity(4)
|
||||
# world to view matrix
|
||||
self.w2v_matrix = np.identity(4)
|
||||
# view to projection matrix
|
||||
viewport = (-1., 1., -1., 1., -1., 1.)
|
||||
self.v2p_matrix = self.create_v2p_matrix(*viewport)
|
||||
|
||||
self.projections = None
|
||||
|
||||
self.timer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_PAINT, self.__evt_paint_cb)
|
||||
self.Bind(wx.EVT_SIZE, self.__evt_size_cb)
|
||||
self.Bind(wx.EVT_MOUSEWHEEL, self.__evt_mousewheel_cb)
|
||||
self.Bind(wx.EVT_MOTION, self.__evt_motion_cb)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.__evt_left_down_cb)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.__evt_left_up_cb)
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.__evt_right_up_cb)
|
||||
self.Bind(wx.EVT_TIMER, self.__evt_timer_cb, self.timer)
|
||||
|
||||
def show_emitter(self, show=True):
|
||||
_opts = self.sprites_opts.copy()
|
||||
if show:
|
||||
self.sprites_opts['alpha'] = 0.25
|
||||
self.sprites_opts['glow'] = False
|
||||
else:
|
||||
self.sprites_opts = _opts.copy()
|
||||
|
||||
def set_atoms(self, atoms, rescale=False, center=True):
|
||||
"""
|
||||
Attach an Atoms object to the view.
|
||||
|
||||
This will translate the model to the center of mass, move the model center
|
||||
to the center of screen and adjust the scale to the largest dimension of the
|
||||
model
|
||||
|
||||
:param rescale: if True, the zoom is computed to view the atoms; if False, a fixed zoom value is used
|
||||
"""
|
||||
if atoms is None:
|
||||
self.light_mode = False
|
||||
self.atoms_center_of_mass = np.zeros(3)
|
||||
self.atoms_largest_dimension = 1.0
|
||||
self.atom_numbers = None
|
||||
self.atom_surfaces = None
|
||||
self.atoms_sprite = None
|
||||
self.projections = None
|
||||
else:
|
||||
# Set the light mode according to the number of atoms
|
||||
if len(atoms) > self.light_mode_threshold: # pylint: disable=simplifiable-if-statement
|
||||
self.light_mode = True
|
||||
else:
|
||||
self.light_mode = False
|
||||
|
||||
# get the center of mass
|
||||
self.atoms_center_of_mass = atoms.get_center_of_mass()
|
||||
# get the largest dimension
|
||||
p = atoms.get_positions()
|
||||
self.atoms_largest_dimension = np.max(np.amax(p, axis=0) - np.amin(p, axis=0))
|
||||
if self.atoms_largest_dimension == 0:
|
||||
self.atoms_largest_dimension = 1.0
|
||||
|
||||
# make atoms a class attribute
|
||||
self.atoms = atoms
|
||||
# self.do_rescale = rescale
|
||||
# self.do_center = center
|
||||
self.update_camera(center=center, rescale=rescale)
|
||||
# create the textures
|
||||
self.create_atom_sprites()
|
||||
# finally update the view
|
||||
self.update_drawing()
|
||||
|
||||
def rotate_atoms(self, dtheta, dphi):
|
||||
self.theta += dtheta
|
||||
self.phi += dphi
|
||||
|
||||
tx, ty = (self.theta, self.phi)
|
||||
m_mat = np.zeros((4, 4))
|
||||
m_mat[0, 0] = m_mat[3, 3] = 1
|
||||
m_mat[1, 1] = m_mat[2, 2] = np.cos(np.radians(tx))
|
||||
m_mat[2, 1] = -np.sin(np.radians(tx))
|
||||
m_mat[1, 2] = np.sin(np.radians(tx))
|
||||
|
||||
n_mat = np.zeros((4, 4))
|
||||
n_mat[1, 1] = n_mat[3, 3] = 1
|
||||
n_mat[0, 0] = n_mat[2, 2] = np.cos(np.radians(ty))
|
||||
n_mat[0, 2] = -np.sin(np.radians(ty))
|
||||
n_mat[2, 0] = np.sin(np.radians(ty))
|
||||
|
||||
self.rotation_matrix = np.dot(m_mat, n_mat)
|
||||
self.update_model_matrix()
|
||||
self.scale_atoms(self.scale)
|
||||
|
||||
def scale_atoms(self, factor):
|
||||
self.scale = factor
|
||||
self.scale_matrix[(0, 1, 2), (0, 1, 2)] = factor
|
||||
self.create_atom_sprites()
|
||||
self.update_projection_matrix()
|
||||
|
||||
def translate_atoms(self, x, y):
|
||||
"""
|
||||
sets the translation of the atoms
|
||||
"""
|
||||
# print('translate_atoms : x=%f, y=%f' % (x, y))
|
||||
self.ox = x
|
||||
self.oy = y
|
||||
self.im_ox += self.last_mouse_move_x
|
||||
self.im_oy += self.last_mouse_move_y
|
||||
self.last_mouse_move_x = self.last_mouse_move_y = 0
|
||||
self.translation_matrix[-1, (0, 1)] = (x, y)
|
||||
self.update_projection_matrix()
|
||||
# print('translate_atoms : self.projection_matrix=%s' % str(self.projection_matrix))
|
||||
|
||||
def select_atoms(self, x, y, w=None, h=None, append=False,
|
||||
toggle=False):
|
||||
selection = np.array([])
|
||||
if w is None and h is None:
|
||||
# get the projections
|
||||
p = self.projections.copy()
|
||||
# translate to the event point
|
||||
p[:, :2] -= (x, y)
|
||||
# compute the norm and the radius for each projected atom
|
||||
norm = np.linalg.norm(p[:, :2], axis=1)
|
||||
radii = covalent_radii[p[:, 4].astype(int)] * self.scale
|
||||
# search where the norm is inside an atom
|
||||
i = np.where(norm < radii)
|
||||
# pick up the atom index of the one with the z min
|
||||
try:
|
||||
selection = np.array([int(p[i][np.argmin(p[i, 2]), 5])])
|
||||
# self.selection = np.array([selection])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if w < 0:
|
||||
x += w
|
||||
w = abs(w)
|
||||
if h < 0:
|
||||
y += h
|
||||
h = abs(h)
|
||||
p = self.projections.copy()
|
||||
p = p[np.where(p[:, 0] > x)]
|
||||
p = p[np.where(p[:, 0] < x + w)]
|
||||
p = p[np.where(p[:, 1] > y)]
|
||||
p = p[np.where(p[:, 1] < y + h)]
|
||||
selection = p[:, -1].astype(int)
|
||||
|
||||
if toggle:
|
||||
print self.selection
|
||||
# whether atoms in the current selection were previously selected
|
||||
i = np.in1d(self.selection, selection)
|
||||
print i
|
||||
self.selection = self.selection[np.invert(i)]
|
||||
|
||||
if append:
|
||||
self.selection = np.append(self.selection, selection)
|
||||
self.selection = np.unique(self.selection)
|
||||
else:
|
||||
self.selection = selection
|
||||
|
||||
def __evt_paint_cb(self, event):
|
||||
self.swap_buffers()
|
||||
|
||||
def __evt_size_cb(self, event):
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
size = self.GetClientSize()
|
||||
self.back_buffer = cairo.ImageSurface(cairo.FORMAT_RGB24, *size)
|
||||
self.create_background_sprite(*size)
|
||||
self.update_drawing()
|
||||
|
||||
def __evt_timer_cb(self, event):
|
||||
self.update_drawing(light=False)
|
||||
self.timer.Stop()
|
||||
|
||||
def __evt_left_down_cb(self, event):
|
||||
self.mx = event.GetX()
|
||||
self.my = event.GetY()
|
||||
self.capture_screen()
|
||||
if event.ControlDown():
|
||||
self.mode |= self.MODE_SELECTION
|
||||
if event.ShiftDown():
|
||||
self.mode |= self.MODE_SELECTION_APPEND
|
||||
if event.AltDown():
|
||||
self.mode |= self.MODE_SELECTION_TOGGLE
|
||||
|
||||
def __evt_left_up_cb(self, event):
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.mode ^= self.MODE_SELECTION
|
||||
# search for atoms in the selection box
|
||||
x, y = event.GetPosition()
|
||||
w = h = None
|
||||
if self.mode & self.MODE_SELECTION_BOX:
|
||||
self.mode ^= self.MODE_SELECTION_BOX
|
||||
x, y, w, h = self.selection_box
|
||||
|
||||
append = False
|
||||
if self.mode & self.MODE_SELECTION_APPEND:
|
||||
self.mode ^= self.MODE_SELECTION_APPEND
|
||||
append = True
|
||||
|
||||
toggle = False
|
||||
if self.mode & self.MODE_SELECTION_TOGGLE:
|
||||
self.mode ^= self.MODE_SELECTION_TOGGLE
|
||||
toggle = True
|
||||
|
||||
self.select_atoms(x, y, w, h, append=append, toggle=toggle)
|
||||
|
||||
if self.mode == self.MODE_TRANSLATION:
|
||||
self.mode ^= self.MODE_TRANSLATION
|
||||
|
||||
self.update_drawing(light=False)
|
||||
|
||||
def __evt_right_up_cb(self, event):
|
||||
if self.mode & self.MODE_ROTATION:
|
||||
self.mode ^= self.MODE_ROTATION
|
||||
self.update_drawing(light=False)
|
||||
|
||||
def __evt_motion_cb(self, event):
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
if event.LeftIsDown():
|
||||
mx, my = event.GetPosition()
|
||||
dx, dy = (mx - self.mx, my - self.my)
|
||||
# if event.ControlDown():
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.mode |= self.MODE_SELECTION_BOX
|
||||
# if event.ShiftDown():
|
||||
# self.mode |= self.MODE_SELECTION_APPEND
|
||||
self.selection_box = [self.mx, self.my, dx, dy]
|
||||
else:
|
||||
self.mode = self.MODE_TRANSLATION
|
||||
self.mx, self.my = (mx, my)
|
||||
self.last_mouse_move_x = int(dx)
|
||||
self.last_mouse_move_y = int(dy)
|
||||
self.ox = int(self.ox + dx)
|
||||
self.oy = int(self.oy + dy)
|
||||
self.translate_atoms(self.ox, self.oy)
|
||||
self.update_drawing()
|
||||
elif event.RightIsDown():
|
||||
self.mode = self.MODE_ROTATION
|
||||
theta = 2. * (float(self.scale0) / self.scale)
|
||||
theta = max(1., theta)
|
||||
mx, my = event.GetPosition()
|
||||
dx, dy = (mx - self.mx, my - self.my)
|
||||
self.mx, self.my = (mx, my)
|
||||
|
||||
tx = theta * np.sign(dy)
|
||||
ty = theta * np.sign(dx)
|
||||
self.rotate_atoms(tx, ty)
|
||||
|
||||
self.update_drawing()
|
||||
|
||||
def __evt_mousewheel_cb(self, event):
|
||||
rot = event.GetWheelRotation()
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.refresh_delay)
|
||||
if rot > 0:
|
||||
factor = self.scale * 1.1
|
||||
im_factor = 1 * 1.1
|
||||
elif rot < 0:
|
||||
factor = self.scale / 1.1
|
||||
im_factor = 1 / 1.1
|
||||
self.im_factor = im_factor
|
||||
self.scale_atoms(factor)
|
||||
self.update_drawing()
|
||||
|
||||
def capture_screen(self):
|
||||
# get size of screen
|
||||
w, h = self.GetClientSize()
|
||||
# create a cairo surface and context
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
|
||||
ctx = cairo.Context(surface)
|
||||
# trick here: blit the last back_buffer onto the newly created surface
|
||||
ctx.set_source_surface(self.back_buffer)
|
||||
ctx.paint()
|
||||
# store it as an attribute
|
||||
self.screenshot = surface
|
||||
|
||||
def create_atom_sprites(self):
|
||||
"""
|
||||
This function creates a list of cairo surfaces for each kind
|
||||
of atoms
|
||||
"""
|
||||
|
||||
# Get out if there are no atoms
|
||||
if not self.atoms:
|
||||
return
|
||||
|
||||
# First get an array of all atoms numbers
|
||||
atom_numbers = np.unique(self.atoms.numbers)
|
||||
|
||||
# Now, for each kind of atoms create a surface in memory
|
||||
atom_surfaces = np.empty((2, len(atom_numbers)), dtype=object)
|
||||
self.__outer_margin = 0
|
||||
def create_surface(atom_number, alpha=1, glow=True):
|
||||
#global margin
|
||||
# get the radius, and the color
|
||||
radius = int(covalent_radii[atom_number] * 1. * self.scale)
|
||||
r, g, b = jmol_colors[atom_number]
|
||||
# actually create the surface
|
||||
size = 2 * radius #+ 4
|
||||
self.__outer_margin = np.maximum(self.__outer_margin, size / 2.)
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the ball
|
||||
ctx = cairo.Context(surface)
|
||||
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_source_rgba(r, g, b, alpha)
|
||||
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
|
||||
ctx.fill_preserve()
|
||||
if glow:
|
||||
gradient = cairo.RadialGradient(radius, radius, radius / 2,
|
||||
radius, radius, radius)
|
||||
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
|
||||
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
|
||||
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
|
||||
ctx.set_source(gradient)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(0., 0., 0., alpha)
|
||||
ctx.stroke()
|
||||
|
||||
# Create the overlay for selection
|
||||
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the circle
|
||||
ctx = cairo.Context(overlay)
|
||||
ctx.set_source_surface(surface)
|
||||
ctx.paint()
|
||||
ctx.set_line_width(2.)
|
||||
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
|
||||
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
|
||||
ctx.stroke()
|
||||
|
||||
return surface, overlay
|
||||
|
||||
for i, a in enumerate(atom_numbers):
|
||||
surface, overlay = create_surface(a, alpha=self.sprites_opts['alpha'],
|
||||
glow=self.sprites_opts['glow'])
|
||||
atom_surfaces[0, i] = surface
|
||||
atom_surfaces[1, i] = overlay
|
||||
"""
|
||||
# get the radius, and the color
|
||||
radius = int(covalent_radii[a] * 1. * self.scale)
|
||||
# b, g, r = jmol_colors[a]
|
||||
r, g, b = jmol_colors[a]
|
||||
# actually create the surface
|
||||
size = 2 * radius + 4
|
||||
margin = np.maximum(margin, size / 2.)
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the ball
|
||||
ctx = cairo.Context(surface)
|
||||
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_source_rgba(r, g, b, self.sprites_opts['alpha'])
|
||||
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
|
||||
ctx.fill_preserve()
|
||||
if self.sprites_opts['glow']:
|
||||
gradient = cairo.RadialGradient(radius, radius, radius / 2,
|
||||
radius, radius, radius)
|
||||
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
|
||||
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
|
||||
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
|
||||
ctx.set_source(gradient)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(0., 0., 0., self.sprites_opts['alpha'])
|
||||
ctx.stroke()
|
||||
# store it
|
||||
atom_surfaces[0, i] = surface
|
||||
|
||||
# Create the overlay for selection
|
||||
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
# draw the circle
|
||||
ctx = cairo.Context(overlay)
|
||||
ctx.set_source_surface(surface)
|
||||
ctx.paint()
|
||||
ctx.set_line_width(2.)
|
||||
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
|
||||
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
|
||||
ctx.stroke()
|
||||
atom_surfaces[1, i] = overlay
|
||||
"""
|
||||
|
||||
self.atom_numbers = atom_numbers
|
||||
self.atom_surfaces = atom_surfaces
|
||||
try:
|
||||
absorber_number = self.atoms[self.atoms.info['absorber']].number
|
||||
self.absorber_surface = create_surface(absorber_number, alpha=1, glow=True)
|
||||
except:
|
||||
self.atoms.info['absorber'] = -1
|
||||
self.__outer_margin *= 1.1
|
||||
|
||||
def create_background_sprite(self, w, h):
|
||||
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
|
||||
ctx = cairo.Context(surface)
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
g = cairo.LinearGradient(0, 0, 0, h)
|
||||
g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
|
||||
g.add_color_stop_rgba(0.7, 1.0, 1.0, 1.0, 1.0)
|
||||
g.add_color_stop_rgba(1.0, 0.5, 0.5, 0.5, 1.0)
|
||||
ctx.set_source(g)
|
||||
ctx.rectangle(0, 0, w, h)
|
||||
ctx.fill()
|
||||
|
||||
g = cairo.LinearGradient(0, 0, 0, h)
|
||||
#g.add_color_stop_rgba(0., 1., 1., 1., 1.)
|
||||
#g.add_color_stop_rgba(2 / 3., 0.5, 0.5, 0.5, 1)
|
||||
#g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
|
||||
#g.add_color_stop_rgba(0.9, 0.8, 0.8, 0.8, 1.0)
|
||||
#g.add_color_stop_rgba(1.0, 0.2, 0.2, 0.2, 1.0)
|
||||
#ctx.set_source(g)
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.rectangle(0, 0, w, h)
|
||||
ctx.fill()
|
||||
|
||||
ctx.save()
|
||||
|
||||
if False:
|
||||
ctx.set_source_rgb(0.8, 0.8, 0.8)
|
||||
rect = (0, 2 * h / 3, w, h / 3)
|
||||
ctx.rectangle(*rect)
|
||||
ctx.clip()
|
||||
ctx.paint()
|
||||
ctx.set_line_width(1.)
|
||||
for i in np.arange(0, 2 * np.pi, np.pi / 30):
|
||||
ctx.move_to(w / 2, 2 * h / 3)
|
||||
x1 = w * np.cos(i)
|
||||
y1 = w * np.sin(i)
|
||||
ctx.rel_line_to(x1, y1)
|
||||
for i in np.arange(2 * h / 3, h, 10):
|
||||
ctx.move_to(0, i)
|
||||
ctx.line_to(w, i)
|
||||
|
||||
ctx.set_source_rgb(0.7, 0.7, 0.7)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
self.background_sprite = surface
|
||||
|
||||
@classmethod
|
||||
def create_v2p_matrix(cls, left, right, bottom, top, near, far):
|
||||
"""
|
||||
creates the matrix that transforms coordinates from view space (space defined by the bounding box passed as argument) to projection space
|
||||
|
||||
this transformation is a scale and offset that maps [left; right], [bottom; top], [near; far] to [-1;1], [-1;1], [0;1]
|
||||
"""
|
||||
v2p_matrix = np.eye(4)
|
||||
v2p_matrix[0, 0] = 2. / (right - left)
|
||||
v2p_matrix[1, 1] = 2. / (right - left)
|
||||
v2p_matrix[2, 2] = 1. / (near - far)
|
||||
v2p_matrix[3, 0] = (left + right) / (left - right)
|
||||
v2p_matrix[3, 1] = (top + bottom) / (bottom - top)
|
||||
v2p_matrix[3, 2] = near / (near - far)
|
||||
return v2p_matrix
|
||||
|
||||
def update_projection_matrix(self):
|
||||
# print('update_projection_matrix : self.v2p_matrix=%s' % str(self.v2p_matrix))
|
||||
# print('update_projection_matrix : self.translation_matrix=%s' % str(self.translation_matrix))
|
||||
m_matrix = np.dot(self.v2p_matrix, self.scale_matrix)
|
||||
m_matrix = np.dot(m_matrix, self.translation_matrix)
|
||||
self.projection_matrix = m_matrix
|
||||
# print('update_projection_matrix : self.projection_matrix=%s' % str(self.projection_matrix))
|
||||
|
||||
def update_model_matrix(self):
|
||||
m_matrix = np.dot(self.m2w_matrix, self.rotation_matrix)
|
||||
self.model_matrix = m_matrix
|
||||
|
||||
def get_projections(self, points, save=False):
|
||||
m_matrix = np.dot(self.model_matrix, self.projection_matrix)
|
||||
# print('get_projections : self.model_matrix = %s' % str(self.model_matrix))
|
||||
# print('get_projections : self.projection_matrix = %s' % str(self.projection_matrix))
|
||||
# print('get_projections : m_matrix = %s' % str(m_matrix))
|
||||
|
||||
p = points[:, :4]
|
||||
v = np.dot(p, m_matrix)
|
||||
v = v / v[:, -1, None]
|
||||
|
||||
# add the other columns
|
||||
v = np.c_[v, points[:, 4:]]
|
||||
# and sort by Z
|
||||
# v = v[v[:,2].argsort()[::-1]]
|
||||
|
||||
if save:
|
||||
self.projections = v
|
||||
return v
|
||||
|
||||
def filter_projections(self, projections, w, h):
|
||||
try:
|
||||
# filtering
|
||||
margin = self.__outer_margin
|
||||
projections = projections[projections[:, 0] >= -1 * margin, :]
|
||||
projections = projections[projections[:, 0] <= w + margin, :]
|
||||
projections = projections[projections[:, 1] >= -1 * margin, :]
|
||||
projections = projections[projections[:, 1] <= h + margin, :]
|
||||
return projections
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
def render_background(self, ctx):
|
||||
surface = self.background_sprite
|
||||
ctx.set_source_surface(surface, 0, 0)
|
||||
ctx.paint()
|
||||
|
||||
def render_scalebar(self, ctx):
|
||||
x, y, w, h = ctx.clip_extents() # @UnusedVariable
|
||||
scalebar_bb_width = 200
|
||||
scalebar_bb_height = 20
|
||||
ctx.set_source_rgba(0., 0., 0., 0.7)
|
||||
ctx.rectangle(x + w - scalebar_bb_width - 6, h - scalebar_bb_height - 6, scalebar_bb_width, scalebar_bb_height)
|
||||
ctx.fill()
|
||||
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.rectangle(x + w - scalebar_bb_width, h - scalebar_bb_height, 100, scalebar_bb_height - 12)
|
||||
ctx.fill()
|
||||
|
||||
ctx.move_to(x + w - scalebar_bb_width / 2 + 6, h - 9)
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.set_font_size(16)
|
||||
ctx.show_text(u"%.2f \xc5" % (100. / self.scale))
|
||||
|
||||
def render_axes(self, ctx):
|
||||
_, _, w, h = ctx.clip_extents() # @UnusedVariable
|
||||
m_matrix = np.dot(self.rotation_matrix, self.v2p_matrix)
|
||||
|
||||
d = 20
|
||||
offset = 12
|
||||
|
||||
origin = np.array([0, 0, 0, 1]) # @UnusedVariable
|
||||
x_axis = np.array([d, 0, 0, 1])
|
||||
y_axis = np.array([0, d, 0, 1])
|
||||
z_axis = np.array([0, 0, d, 1])
|
||||
|
||||
# translation = np.array([[1, 0, 0, d + offset], # @UnusedVariable
|
||||
# [0, 1, 0, h - offset],
|
||||
# [0, 0, 1, 1 ],
|
||||
# [0, 0, 0, 1 ]])
|
||||
|
||||
red = (1, 0, 0)
|
||||
green = (0, 0.7, 0)
|
||||
blue = (0, 0, 1)
|
||||
|
||||
# draw a white circle
|
||||
so = np.array([d + offset, h - d - offset, 0])
|
||||
ctx.move_to(d + offset, h - d - offset)
|
||||
ctx.arc(d + offset, h - d - offset, d + offset - 2, 0, 2 * np.pi)
|
||||
#ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.set_source_rgba(0., 0., 0., 0.7)
|
||||
ctx.set_line_width(1)
|
||||
#ctx.stroke_preserve()
|
||||
ctx.set_source_rgba(0.95, 0.95, 0.95, 1)
|
||||
ctx.fill()
|
||||
|
||||
for axis, color, label in ((x_axis, red, 'X'),
|
||||
(y_axis, green, 'Y'),
|
||||
(z_axis, blue, 'Z')):
|
||||
axis = np.dot(axis, m_matrix)
|
||||
axis /= axis[-1]
|
||||
ctx.move_to(*so[:2])
|
||||
ctx.rel_line_to(*axis[:2])
|
||||
ctx.set_source_rgb(*color)
|
||||
ctx.set_line_width(2)
|
||||
ctx.set_font_size(10)
|
||||
ctx.show_text(label)
|
||||
ctx.stroke()
|
||||
|
||||
def render_atoms(self, ctx):
|
||||
try:
|
||||
atoms = self.atoms
|
||||
except:
|
||||
return
|
||||
|
||||
# create a points matrix with homogeneous coordinates
|
||||
# x,y,z,w and the atom number and index
|
||||
points = atoms.get_positions()
|
||||
points = np.c_[points, np.ones(len(points))]
|
||||
points = np.c_[points, atoms.numbers]
|
||||
points = np.c_[points, np.arange(len(atoms))]
|
||||
|
||||
# Get the points array projected to the screen display
|
||||
projections = self.get_projections(points, save=True)
|
||||
# print('render_atoms : len(projections) = %d' % len(projections))
|
||||
# print('render_atoms : projections = %s' % str(projections))
|
||||
|
||||
# Reduce the number of atoms to be drawn if outside the viewport
|
||||
w, h = self.GetClientSize()
|
||||
# print('render_atoms : w = %f, h = %f' % (w, h))
|
||||
projections = self.filter_projections(projections, w, h)
|
||||
# self.projections = projections
|
||||
|
||||
if len(projections) == 0:
|
||||
print('render_atoms : no atom is visible')
|
||||
return # no atom is visible from the camera
|
||||
|
||||
# sort by z
|
||||
projections = projections[projections[:, 2].argsort()[::-1]]
|
||||
|
||||
margin = self.__outer_margin
|
||||
xmin, ymin = np.min(projections[:, :2], axis=0)
|
||||
xmax, ymax = np.max(projections[:, :2], axis=0)
|
||||
sw = xmax - xmin + 2 * margin
|
||||
sh = ymax - ymin + 2 * margin
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(sw), int(sh))
|
||||
surface_ctx = cairo.Context(surface)
|
||||
|
||||
# set the local references
|
||||
set_source_surface = surface_ctx.set_source_surface
|
||||
paint = surface_ctx.paint
|
||||
selection = self.selection
|
||||
atom_numbers = self.atom_numbers
|
||||
atom_surfaces = self.atom_surfaces
|
||||
|
||||
self.im_ox = int(xmin - margin)
|
||||
self.im_oy = int(ymin - margin)
|
||||
surface_ctx.translate(-self.im_ox, -self.im_oy)
|
||||
# surface_ctx.set_source_rgb(1,1,0)
|
||||
# surface_ctx.set_line_width(2.)
|
||||
# surface_ctx.rectangle(self.im_ox, self.im_oy, int(sw), int(sh))
|
||||
# surface_ctx.stroke()
|
||||
for i, p in enumerate(projections): # @UnusedVariable
|
||||
x, y, z, w, n, index = p # @UnusedVariable
|
||||
# load the surface
|
||||
if index in selection:
|
||||
if index == self.atoms.info['absorber']:
|
||||
sprite = self.absorber_surface[1]
|
||||
else:
|
||||
sprite = atom_surfaces[1, np.where(atom_numbers == n)[0][0]]
|
||||
else:
|
||||
if index == self.atoms.info['absorber']:
|
||||
sprite = self.absorber_surface[0]
|
||||
else:
|
||||
sprite = atom_surfaces[0, np.where(atom_numbers == n)[0][0]]
|
||||
|
||||
sx = x - sprite.get_width() / 2.
|
||||
sy = y - sprite.get_height() / 2.
|
||||
set_source_surface(sprite, int(sx), int(sy))
|
||||
paint()
|
||||
if False: # pylint: disable=using-constant-test
|
||||
ctx.set_source_rgb(0., 0., 0.)
|
||||
r = sprite.get_width() / 2
|
||||
ctx.arc(x, y, r, 0, 2 * np.pi)
|
||||
ctx.stroke_preserve()
|
||||
ctx.set_source_rgb(0.8, 0.8, 0.8)
|
||||
ctx.fill()
|
||||
ctx.move_to(x, y)
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.set_font_size(14)
|
||||
ctx.show_text("%d" % index)
|
||||
|
||||
# save the rendering
|
||||
self.atoms_sprite = surface
|
||||
ctx.set_source_surface(surface, self.im_ox, self.im_oy)
|
||||
ctx.paint()
|
||||
|
||||
def render_selection_box(self, ctx):
|
||||
r, g, b = self.colors['selection_box']
|
||||
ctx.set_source_surface(self.screenshot, 0, 0)
|
||||
ctx.paint()
|
||||
ctx.set_source_rgba(r, g, b, 0.3)
|
||||
ctx.rectangle(*self.selection_box)
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgb(r, g, b)
|
||||
ctx.stroke()
|
||||
|
||||
def render_boundingbox(self, ctx):
|
||||
# print('render_boundingbox : start')
|
||||
try:
|
||||
atoms = self.atoms
|
||||
except:
|
||||
return
|
||||
|
||||
# create a points matrix with homogeneous coordinates
|
||||
# x,y,z,w for atoms extrema
|
||||
points = atoms.get_positions()
|
||||
margin = self.__outer_margin / float(self.scale)
|
||||
xmin, ymin, zmin = np.min(points, axis=0) - margin
|
||||
xmax, ymax, zmax = np.max(points, axis=0) + margin
|
||||
points = np.array([
|
||||
[xmax, ymax, zmax, 1],
|
||||
[xmax, ymin, zmax, 1],
|
||||
[xmin, ymax, zmax, 1],
|
||||
[xmin, ymin, zmax, 1],
|
||||
[xmax, ymax, zmin, 1],
|
||||
[xmax, ymin, zmin, 1],
|
||||
[xmin, ymax, zmin, 1],
|
||||
[xmin, ymin, zmin, 1]])
|
||||
|
||||
# Get the points array projected to the screen display
|
||||
projections = self.get_projections(points)
|
||||
x0, y0 = (np.min(projections[:, :2], axis=0)).astype(int)
|
||||
x1, y1 = (np.max(projections[:, :2], axis=0)).astype(int)
|
||||
|
||||
# Declare the 6 faces with their vertex index
|
||||
# the order of the numbers define if the normal plane points outward or not
|
||||
faces = np.array([
|
||||
[6, 7, 5, 4],
|
||||
[2, 3, 7, 6],
|
||||
[3, 1, 5, 7],
|
||||
[2, 6, 4, 0],
|
||||
[0, 4, 5, 1],
|
||||
[2, 0, 1, 3]])
|
||||
|
||||
# kind of backface culling
|
||||
ind = []
|
||||
for i, f in enumerate(faces):
|
||||
# Get 2 vectors of the plane
|
||||
v1 = projections[f[1], :3] - projections[f[0], :3]
|
||||
v2 = projections[f[3], :3] - projections[f[0], :3]
|
||||
# cross multiply them to get the normal
|
||||
n = np.cross(v2, v1)
|
||||
# If the normal z coordinate is <0, the plane is not visible, so, draw it
|
||||
# first, otherwise draw it last
|
||||
if n[-1] > 0:
|
||||
ind.append(i)
|
||||
else:
|
||||
ind.insert(0, i)
|
||||
# faces are now re-ordered
|
||||
faces = faces[ind]
|
||||
|
||||
# plane transparency and color
|
||||
color_plane = self.colors['boulding_box_fill']
|
||||
color_line = self.colors['boulding_box_line']
|
||||
|
||||
ctx.save()
|
||||
#ctx.set_source_rgb(1, 0, 0)
|
||||
#ctx.rectangle(x0, y0, x1 - x0, y1 - y0)
|
||||
#ctx.stroke()
|
||||
ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
|
||||
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
|
||||
ctx.set_line_width(1.)
|
||||
ctx.set_dash([8., 8.])
|
||||
for i, p in enumerate(faces):
|
||||
# remove dash for front faces
|
||||
if i > 2:
|
||||
ctx.set_dash([])
|
||||
if i == 3 and not(self.mode & self.MODE_ROTATION): # pylint: disable=superfluous-parens
|
||||
try:
|
||||
f = self.scale / self.scale0
|
||||
# sprite_w = self.atoms_sprite.get_width()
|
||||
# sprite_h = self.atoms_sprite.get_height()
|
||||
# m = self.__outer_margin
|
||||
# sw = float(sprite_w) / (x1 - x0)
|
||||
# sh = float(sprite_h) / (y1 - y0)
|
||||
ctx.save()
|
||||
# ctx.translate((x0 + x1 - sprite_w)/2. ,
|
||||
# (y0 + y1 - sprite_h)/2.)
|
||||
ctx.set_source_surface(self.atoms_sprite, self.im_ox, self.im_oy)
|
||||
ctx.paint()
|
||||
ctx.restore()
|
||||
except:
|
||||
pass
|
||||
ctx.set_source_rgba(*color_plane)
|
||||
# get the projected points
|
||||
p0, p1, p2, p3 = projections[p, :2]
|
||||
# move to the first
|
||||
ctx.move_to(*p0)
|
||||
# line to the others
|
||||
map(lambda _: ctx.line_to(*_), (p1, p2, p3))
|
||||
# close the polygon
|
||||
ctx.close_path()
|
||||
# fill it and stroke the path
|
||||
ctx.fill_preserve()
|
||||
ctx.set_source_rgba(*color_line)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
def update_camera(self, rescale=False, center=False):
|
||||
# update the scale
|
||||
w, h = self.GetClientSize()
|
||||
l = min(w, h)
|
||||
# print('set_atoms : w=%d h = %d l = %f' % (w, h, l))
|
||||
self.scale0 = int(.5 * l / self.atoms_largest_dimension) + 1
|
||||
|
||||
# move the model to the center of mass
|
||||
t_matrix = np.eye(4)
|
||||
t_matrix[-1, :-1] = -1 * self.atoms_center_of_mass
|
||||
self.m2w_matrix = t_matrix # self.m2w_matrix.dot(t_matrix)
|
||||
self.update_model_matrix()
|
||||
|
||||
if rescale:
|
||||
assert self.scale0 > 0.0
|
||||
self.scale = self.scale0
|
||||
|
||||
#print "scale = ", self.scale, "scale0 = ", self.scale0
|
||||
#self.scale_atoms(1.)
|
||||
|
||||
if center:
|
||||
self.translate_atoms(w / 2, h / 2)
|
||||
|
||||
def update_drawing(self, light=None):
|
||||
# print('update_drawing : light=%s' % str(light))
|
||||
|
||||
try:
|
||||
ctx = cairo.Context(self.back_buffer)
|
||||
except:
|
||||
#self.scale = self.scale0 = 10
|
||||
return
|
||||
|
||||
light_mode = self.light_mode if light is None else light
|
||||
|
||||
if self.mode & self.MODE_SELECTION:
|
||||
self.render_selection_box(ctx)
|
||||
else:
|
||||
self.render_background(ctx)
|
||||
if self.atoms:
|
||||
if light_mode:
|
||||
self.render_boundingbox(ctx)
|
||||
else:
|
||||
self.render_atoms(ctx)
|
||||
self.render_scalebar(ctx)
|
||||
self.render_axes(ctx)
|
||||
|
||||
self.Refresh(eraseBackground=False)
|
||||
|
||||
def swap_buffers(self):
|
||||
if self.back_buffer:
|
||||
back_buffer = self.back_buffer
|
||||
# w = back_buffer.get_width()
|
||||
# h = back_buffer.get_height()
|
||||
|
||||
bitmap = wx.lib.wxcairo.BitmapFromImageSurface(back_buffer)
|
||||
dc = wx.PaintDC(self)
|
||||
dc.DrawBitmap(bitmap, 0, 0)
|
|
@ -0,0 +1,286 @@
|
|||
import wx
|
||||
from wx import glcanvas
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GLU import *
|
||||
from OpenGL.GLUT import *
|
||||
import sys
|
||||
|
||||
from ase import Atom, Atoms
|
||||
from ase.data import covalent_radii
|
||||
from ase.data.colors import jmol_colors
|
||||
|
||||
from ase.build import molecule
|
||||
|
||||
import numpy as np
|
||||
|
||||
class GLPanel(wx.Panel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
#style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
|
||||
wx.Panel.__init__(self, *args, **kwargs)
|
||||
|
||||
self.GLinitialized = False
|
||||
attribList = (glcanvas.WX_GL_RGBA, # RGBA
|
||||
glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
|
||||
glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit
|
||||
# Create the canvas
|
||||
self.canvas = glcanvas.GLCanvas(self, attribList=attribList)
|
||||
self.glcontext = glcanvas.GLContext(self.canvas)
|
||||
# Set the event handlers.
|
||||
self.canvas.Bind(wx.EVT_SIZE, self.onSizeEvent)
|
||||
self.canvas.Bind(wx.EVT_PAINT, self.onPaintEvent)
|
||||
|
||||
szr = wx.BoxSizer(wx.HORIZONTAL)
|
||||
szr.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0)
|
||||
self.SetSizer(szr)
|
||||
|
||||
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheelEvent)
|
||||
self.canvas.Bind(wx.EVT_MOTION, self.onMotionEvent)
|
||||
self.canvas.Bind(wx.EVT_LEFT_DOWN, self.onLeftDownEvent)
|
||||
#self.Bind(wx.EVT_LEFT_UP, self.__evt_left_up_cb)
|
||||
#self.Bind(wx.EVT_RIGHT_UP, self.__evt_right_up_cb)
|
||||
self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.onRightDownEvent)
|
||||
|
||||
self.atoms = None
|
||||
self.scale = 1.
|
||||
self.theta = self.phi = 0.
|
||||
self.tx = self.ty = 0
|
||||
self.tx0 = self.ty0 = 0.
|
||||
self.znear = 1.
|
||||
self.zfar = -1.
|
||||
self.coords = [0., 0., 0.]
|
||||
self.translation = [0., 0., 0]
|
||||
|
||||
def set_atoms(self, atoms):
|
||||
self.atoms = atoms
|
||||
self.com = atoms.get_center_of_mass()
|
||||
self.sorted_indices = np.argsort(atoms.numbers)
|
||||
self.theta = 45
|
||||
self.phi = -45
|
||||
|
||||
|
||||
def onMouseWheelEvent(self, event):
|
||||
rotation = event.GetWheelRotation()
|
||||
if rotation > 0:
|
||||
self.scale *= 1.1
|
||||
else:
|
||||
self.scale /= 1.1
|
||||
#self._gl_scale()
|
||||
print self.scale
|
||||
w, h = self.canvas.GetClientSize()
|
||||
self._gl_init_view(w, h)
|
||||
self._gl_draw()
|
||||
|
||||
def onRightDownEvent(self, event):
|
||||
self.mx0, self.my0 = event.GetPosition()
|
||||
|
||||
def onLeftDownEvent(self, event):
|
||||
mvmatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
|
||||
projmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
|
||||
viewport = glGetIntegerv(GL_VIEWPORT)
|
||||
winx, winy = event.GetPosition()
|
||||
winy = viewport[3] - winy
|
||||
winz = glReadPixels(winx, winy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
|
||||
coords = gluUnProject(winx, winy, winz, mvmatrix, projmatrix, viewport)
|
||||
self.coords0 = coords
|
||||
print "coords0 = ", coords
|
||||
|
||||
|
||||
def onMotionEvent(self, event):
|
||||
w, h = self.canvas.GetClientSize()
|
||||
if event.RightIsDown():
|
||||
mx, my = event.GetPosition()
|
||||
dy = my - self.my0
|
||||
dx = mx - self.mx0
|
||||
self.my0 = my
|
||||
self.mx0 = mx
|
||||
self.theta += dx
|
||||
self.phi += dy
|
||||
elif event.LeftIsDown():
|
||||
mvmatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
|
||||
projmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
|
||||
viewport = glGetIntegerv(GL_VIEWPORT)
|
||||
|
||||
# get x0, y0 and z0
|
||||
#x0, y0, z0 = gluUnProject(self.tx0, self.ty0, self.tz0, mvmatrix, projmatrix, viewport)
|
||||
|
||||
winx, winy = event.GetPosition()
|
||||
winy = viewport[3] - winy
|
||||
winz = glReadPixels(winx, winy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
|
||||
x, y, z = gluUnProject(winx, winy, winz, mvmatrix, projmatrix, viewport)
|
||||
|
||||
#translation = [x - x0, y - y0, 0]
|
||||
translation = [x - self.coords0[0], y - self.coords0[1], 0]
|
||||
#print x, y, z#translation
|
||||
if winx < w and winx > 0 and winy < h and winy > 0:
|
||||
print 'here'
|
||||
glTranslatef(*translation)
|
||||
#self.translation = translation
|
||||
print translation
|
||||
|
||||
self._gl_draw()
|
||||
|
||||
def onSizeEvent(self, event):
|
||||
"""Process the resize event."""
|
||||
width, height = self.canvas.GetClientSize()
|
||||
self._gl_resize(width, height)
|
||||
self.canvas.Refresh(False)
|
||||
event.Skip()
|
||||
|
||||
def onPaintEvent(self, event):
|
||||
"""Process the drawing event."""
|
||||
if not self.GLinitialized:
|
||||
w, h = self.canvas.GetClientSize()
|
||||
self._gl_init(w, h)
|
||||
|
||||
self._gl_draw()
|
||||
event.Skip()
|
||||
|
||||
# GLFrame OpenGL Event Handlers
|
||||
def _gl_init(self, width, height):
|
||||
"""Initialize OpenGL for use in the window."""
|
||||
self.canvas.SetCurrent(self.glcontext)
|
||||
|
||||
self.quadric = gluNewQuadric()
|
||||
gluQuadricNormals(self.quadric, GLU_SMOOTH)
|
||||
|
||||
glClearColor(1., 1., 1., 1.)
|
||||
glClearDepth(1.)
|
||||
glDepthFunc(GL_LESS)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glEnable(GL_MULTISAMPLE)
|
||||
glShadeModel(GL_SMOOTH)
|
||||
|
||||
#glEnable(GL_BLEND)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
|
||||
|
||||
#glLightfv(GL_LIGHT0, GL_AMBIENT, (0.5, 0.5, 0.5, 1.0))
|
||||
a = 0.4
|
||||
d = 0.4
|
||||
s = 1.
|
||||
glLightfv(GL_LIGHT0, GL_AMBIENT, (a, a, a, 1.0))
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, (d, d, d, 1.0))
|
||||
glLightfv(GL_LIGHT0, GL_SPECULAR, (s, s, s, 1.0))
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, (0.0, 100.0, 10., 1.0))
|
||||
glEnable(GL_LIGHT0)
|
||||
|
||||
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||
#glLightfv(GL_LIGHT1, GL_AMBIENT, (0.3, 0.3, 0.3, 1.0))
|
||||
#glLightfv(GL_LIGHT1, GL_DIFFUSE, (.2, .2, .2, 1.0))
|
||||
#glLightfv(GL_LIGHT1, GL_POSITION, (0.0, -10.0, 0., 1.0))
|
||||
#glEnable(GL_LIGHT1)
|
||||
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_COLOR_MATERIAL)
|
||||
|
||||
self._gl_init_view(width, height)
|
||||
self._gl_resize(width, height)
|
||||
self.GLinitialized = True
|
||||
|
||||
def _gl_init_view(self, width, height):
|
||||
glViewport(0, 0, width, height)
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
gluPerspective(45., float(width) / float(height), self.znear, self.zfar)
|
||||
# find the largest cluster dimension
|
||||
max_d = np.max(np.linalg.norm(self.atoms.get_positions(), axis=1))
|
||||
# set the scale accordingly
|
||||
gluLookAt(0, 0, 5*max_d*self.scale, 0, 0, 0, 0, 1, 0)
|
||||
#gluLookAt(0, 0, self.scale, 0, 0, 0, 0, 1, 0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
|
||||
def _gl_resize(self, width, height):
|
||||
"""Reshape the OpenGL viewport based on the dimensions of the window."""
|
||||
if height == 0:
|
||||
height = 1
|
||||
self._gl_init_view(width, height)
|
||||
|
||||
def _gl_draw(self, *args, **kwargs):
|
||||
"Draw the window."
|
||||
#glClear(GL_COLOR_BUFFER_BIT)
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
# Drawing an example triangle in the middle of the screen
|
||||
|
||||
#glBegin(GL_TRIANGLES)
|
||||
#glColor(0, 0, 0)
|
||||
#color = [1.0, 0.0, 0.0, 1.0]
|
||||
#glMaterialfv(GL_FRONT, GL_DIFFUSE, color)
|
||||
#glVertex3f(-.25, -.25, 0.)
|
||||
#glVertex3f(.25, -.25, 0.)
|
||||
#glVertex3f(0., .25, 0.)
|
||||
##glVertex(-.25, -.25)
|
||||
##glVertex(.25, -.25)
|
||||
##glVertex(0., .25)
|
||||
#glEnd()
|
||||
|
||||
def draw_circle(radius, n):
|
||||
theta = 2*np.pi/float(n)
|
||||
c = np.cos(theta)
|
||||
s = np.sin(theta)
|
||||
|
||||
x = radius
|
||||
y = 0
|
||||
|
||||
glBegin(GL_LINE_LOOP)
|
||||
for i in range(n):
|
||||
glVertex2f(x , y )
|
||||
t = x
|
||||
x = c * x - s * y
|
||||
y = s * t + c * y
|
||||
glEnd()
|
||||
|
||||
def render_atom(atom):
|
||||
glPushMatrix()
|
||||
|
||||
glRotate(self.theta, 0, 1, 0)
|
||||
glRotate(self.phi, 1, 0, 0)
|
||||
glTranslatef(-self.com[0], -self.com[1], -self.com[2])
|
||||
|
||||
glTranslatef(*atom.position)
|
||||
#r, g, b = jmol_colors[atom.number]
|
||||
radius = covalent_radii[atom.number]
|
||||
#color = [r, g, b, 1.0]
|
||||
#glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (.1, .1, .1, 1))
|
||||
#glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, (0, 0, 0, 1))
|
||||
#glColor3f(r, g, b)
|
||||
resolution = 16
|
||||
gluSphere(self.quadric, radius, resolution, resolution)
|
||||
#draw_circle(radius, 32)
|
||||
glPopMatrix()
|
||||
|
||||
def set_material(number):
|
||||
r, g, b = jmol_colors[number]
|
||||
color = [r, g, b, 1.0]
|
||||
s = 0.7
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (s, s, s, 1))
|
||||
glMaterialfv(GL_FRONT, GL_EMISSION, (0., 0., 0., 1))
|
||||
glMateriali(GL_FRONT, GL_SHININESS, 30)
|
||||
glColor4f(r, g, b, 0.5)
|
||||
|
||||
last_number = -1
|
||||
#glPushMatrix()
|
||||
#glLoadIdentity()
|
||||
#glTranslatef(*self.translation)
|
||||
#glRotate(self.theta, 0, 1, 0)
|
||||
#glRotate(self.phi, 1, 0, 0)
|
||||
#glTranslatef(-self.com[0], -self.com[1], -self.com[2])
|
||||
|
||||
#glPopMatrix()
|
||||
for i in self.sorted_indices:
|
||||
atom = self.atoms[i]
|
||||
number = atom.number
|
||||
if number != last_number:
|
||||
set_material(number)
|
||||
render_atom(atom)
|
||||
|
||||
self.canvas.SwapBuffers()
|
||||
|
||||
def _gl_scale(self):
|
||||
scale = self.scale
|
||||
glScale(scale, scale, scale)
|
||||
self._gl_draw()
|
||||
|
||||
|
Binary file not shown.
|
@ -0,0 +1,30 @@
|
|||
# coding: utf-8
|
||||
|
||||
import wx
|
||||
from GLPanel import GLPanel
|
||||
from msspec.utils import hemispherical_cluster
|
||||
from ase.build import bulk
|
||||
|
||||
|
||||
class GLFrame(wx.Frame):
|
||||
def __init__(self, *args, **kwargs):
|
||||
wx.Frame.__init__(self, *args, **kwargs)
|
||||
self.pnl = GLPanel(self)
|
||||
|
||||
def set_atoms(self, atoms):
|
||||
self.pnl.set_atoms(atoms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
lattice = bulk('MgO', crystalstructure='rocksalt', a=4.21, cubic=True)
|
||||
cluster = hemispherical_cluster(lattice, 0, 0, diameter=20.)
|
||||
|
||||
|
||||
app = wx.App(False)
|
||||
frame = GLFrame(None, -1, 'GL Window', size=(640, 480))
|
||||
frame.set_atoms(cluster)
|
||||
frame.Show()
|
||||
|
||||
app.MainLoop()
|
||||
app.Destroy()
|
|
@ -0,0 +1,38 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# vim: set fdm=indent ts=2 sw=2 sts=2 et tw=80 cc=+1 mouse=a nu : #
|
||||
|
||||
# import sys
|
||||
# sys.path.append('/home/sylvain/dev/msspec/trunk/src/python/MsSpecGui/')
|
||||
|
||||
import wx
|
||||
from clusterviewer import ClusterViewer
|
||||
|
||||
from ase.lattice import bulk
|
||||
|
||||
|
||||
class MyFrame(wx.Frame):
|
||||
def __init__(self, **kwargs):
|
||||
p = {}
|
||||
p.setdefault('parent', None)
|
||||
p.setdefault('title', 'Test Frame')
|
||||
p.setdefault('size', (640, 480))
|
||||
p.update(kwargs)
|
||||
|
||||
wx.Frame.__init__(self, **p)
|
||||
|
||||
self.Window = ClusterViewer(self)
|
||||
self.Show()
|
||||
|
||||
# display an MgO cell
|
||||
MgO = bulk('MgO', crystalstructure='rocksalt', orthorhombic=True, a=4.21)
|
||||
MgO = MgO.repeat((20, 20, 30))
|
||||
|
||||
app = wx.App(False)
|
||||
frame = MyFrame(title='MsSpec Viewer')
|
||||
frame.Window.light_mode_threshold = 2
|
||||
frame.Window.set_atoms(MgO, rescale=True)
|
||||
|
||||
frame.Show()
|
||||
|
||||
app.MainLoop()
|
||||
app.Destroy()
|
|
@ -0,0 +1,88 @@
|
|||
'''
|
||||
Created on Jan 4, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
|
||||
import wx
|
||||
import msspecgui.msspec.workspace
|
||||
|
||||
|
||||
class FileMenu(wx.Menu):
|
||||
def __init__(self, main_frame):
|
||||
wx.Menu.__init__(self)
|
||||
self._main_frame = main_frame
|
||||
|
||||
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
|
||||
self._new_workspace_menu_item = self.Append(wx.ID_ANY, "N&ew workspace", " create a new workspace")
|
||||
self._open_workspace_menu_item = self.Append(wx.ID_ANY, "O&pen workspace", " load an existing workspace")
|
||||
self.AppendSeparator()
|
||||
self._save_workspace_menu_item = self.Append(wx.ID_ANY, "S&ave workspace", " save the current workspace")
|
||||
self._close_workspace_menu_item = self.Append(wx.ID_ANY, "C&lose workspace", " close the current workspace")
|
||||
self.AppendSeparator()
|
||||
self._about_menu_item = self.Append(wx.ID_ABOUT, "&About", " Information about this program")
|
||||
self.AppendSeparator()
|
||||
self._exit_menu_item = self.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
|
||||
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_about, self._about_menu_item)
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_exit, self._exit_menu_item)
|
||||
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_new_workspace, self._new_workspace_menu_item)
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_open_workspace, self._open_workspace_menu_item)
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_save_workspace, self._save_workspace_menu_item)
|
||||
self._main_frame.Bind(wx.EVT_MENU, self.on_close_workspace, self._close_workspace_menu_item)
|
||||
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self._close_workspace_menu_item.Enable(self._main_frame.m_workspace is not None)
|
||||
self._save_workspace_menu_item.Enable(self._main_frame.m_workspace is not None)
|
||||
|
||||
def on_new_workspace(self, event):
|
||||
if self.on_close_workspace(event=None):
|
||||
default_path = ''
|
||||
dlg = wx.DirDialog(self._main_frame, "Choose location to store this new workspace", default_path)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
workspace_path = dlg.GetPath()
|
||||
self._main_frame.set_workspace(msspecgui.msspec.workspace.Workspace(workspace_path, is_new_workspace=True))
|
||||
|
||||
# f = open(, 'r')
|
||||
# self.control.SetValue(f.read())
|
||||
# f.close()
|
||||
dlg.Destroy()
|
||||
self.update()
|
||||
|
||||
def on_open_workspace(self, event):
|
||||
if self.on_close_workspace(event=None):
|
||||
default_path = ''
|
||||
dlg = wx.DirDialog(self._main_frame, "Choose the workspace location", default_path)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
workspace_path = dlg.GetPath()
|
||||
workspace = msspecgui.msspec.workspace.Workspace(workspace_path, is_new_workspace=False)
|
||||
self._main_frame.set_workspace(workspace)
|
||||
|
||||
# f = open(, 'r')
|
||||
# self.control.SetValue(f.read())
|
||||
# f.close()
|
||||
dlg.Destroy()
|
||||
self.update()
|
||||
|
||||
def on_save_workspace(self, event):
|
||||
print('FileMenu.on_save_workspace')
|
||||
self._main_frame.save_workspace()
|
||||
self.update()
|
||||
|
||||
def on_close_workspace(self, event):
|
||||
self._main_frame.close_workspace()
|
||||
self.update()
|
||||
return True
|
||||
|
||||
def on_about(self, event):
|
||||
# A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
|
||||
dlg = wx.MessageDialog(self._main_frame, "A multiple-scattering package for spectroscopies using electrons to probe materials", "MsSpec 1.1", wx.OK)
|
||||
dlg.ShowModal() # Show it
|
||||
dlg.Destroy() # finally destroy it when finished.
|
||||
|
||||
def on_exit(self, event):
|
||||
self.on_close_workspace(event=event)
|
||||
self._main_frame.Close(True) # Close the frame.
|
|
@ -0,0 +1,70 @@
|
|||
'''
|
||||
Created on Dec 23, 2015
|
||||
|
||||
@author: graffy
|
||||
|
||||
how to install ase on osx macports
|
||||
[OK]graffy@pr079234:~/ownCloud/ipr/msspec/toto[12:50:42]>export ASE_TAGS=https://svn.fysik.dtu.dk/projects/ase/tags/
|
||||
[OK]graffy@pr079234:~/ownCloud/ipr/msspec/toto[12:50:57]>svn co -r 4567 $ASE_TAGS/3.9.1 ase-3.9.1
|
||||
[OK]graffy@pr079234:~[12:52:41]>cd ownCloud/ipr/msspec/toto/ase-3.9.1
|
||||
sudo /opt/local/bin/python setup.py install
|
||||
'''
|
||||
import sys
|
||||
# import re
|
||||
# sudo port install py27-wxpython-3.0
|
||||
# import os
|
||||
|
||||
sys.path.append('../../')
|
||||
import wx # @IgnorePep8
|
||||
import ase # @IgnorePep8
|
||||
# import msspec
|
||||
import msspecgui.msspec.gui.mainwindow as mainwindow # @IgnorePep8
|
||||
# import cairosvg
|
||||
|
||||
|
||||
def view_atoms(atoms):
|
||||
'''Displays the atoms.
|
||||
|
||||
This is the equivalent of ase.visualize.view(molecule), but this function doesn't make use of the external python program ase-3.9.1/tools/ase-gui. It was built by looking at what ase.visualize.view does.
|
||||
|
||||
'''
|
||||
import ase.gui.gui # IGNORE:redefined-outer-name
|
||||
# import ase.gui.ag
|
||||
import ase.io
|
||||
# import ase.gui.gtkexcepthook
|
||||
# from ase.visualize import view
|
||||
# from ase.io import write
|
||||
# from ase.gui.ag import main
|
||||
# http://wiki.wxpython.org/Getting%20Started
|
||||
|
||||
import ase.gui.images
|
||||
file_format = 'traj'
|
||||
file_path = '/tmp/toto.%s' % file_format
|
||||
# ase.io.write(file_path, molecule, file_format)
|
||||
ase.io.write(file_path, images=atoms, format=file_format)
|
||||
|
||||
images = ase.gui.images.Images()
|
||||
images.read([file_path], ase.io.string2index(':'))
|
||||
gui = ase.gui.gui.GUI(images, '', 1, False)
|
||||
gui.run(None)
|
||||
|
||||
|
||||
def test_ase():
|
||||
d = 1.10
|
||||
molecule = ase.Atoms('2N', positions=[(0., 0., 0.), (0., 0., d)])
|
||||
view_atoms(molecule)
|
||||
|
||||
|
||||
def main():
|
||||
print('hello, world')
|
||||
app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window.
|
||||
app.SetAppName('MsSpec')
|
||||
# app.SetAppDisplayName('MsSpec')
|
||||
frame = mainwindow.MainWindow(None, 'MsSpec') # A Frame is a top-level window. @UnusedVariable
|
||||
|
||||
# test_ase()
|
||||
app.MainLoop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,83 @@
|
|||
'''
|
||||
Created on Jan 4, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
import wx
|
||||
from .filemenu import FileMenu
|
||||
# import clustermenu
|
||||
from msspecgui.msspec.gui.viewmanager import ViewFactory
|
||||
from msspecgui.msspec.gui.viewmanager import ViewManager
|
||||
from msspecgui.msspec.gui.clustereditor import ClusterEditor
|
||||
from msspecgui.msspec.gui.clusterview import ClusterView
|
||||
|
||||
|
||||
class MainWindow(wx.Frame):
|
||||
def __init__(self, parent, title):
|
||||
wx.Frame.__init__(self, parent, title=title, size=(300, 300))
|
||||
# self.c ontrol = wx.TextCtrl(self, style=wx.TE_MULTILINE)
|
||||
self.CreateStatusBar() # A Statusbar in the bottom of the window
|
||||
|
||||
self.view_manager = None
|
||||
self.set_workspace(None)
|
||||
|
||||
self._file_menu = FileMenu(self)
|
||||
# self.m_clusterMenu = clustermenu.ClusterMenu(self)
|
||||
|
||||
# Creating the menubar.
|
||||
menu_bar = wx.MenuBar()
|
||||
menu_bar.Append(self._file_menu, "&File") # Adding the "fileMenu" to the MenuBar
|
||||
# menu_bar.Append(self.m_clusterMenu,"&Cluster") # Adding the "fileMenu" to the MenuBar
|
||||
self.SetMenuBar(menu_bar) # Adding the MenuBar to the Frame content.
|
||||
|
||||
self.Bind(wx.EVT_CLOSE, self.on_close)
|
||||
self.Show(True) # Show the frame.
|
||||
|
||||
def set_workspace(self, workspace):
|
||||
"""
|
||||
:type workspace: Workspace
|
||||
"""
|
||||
self.m_workspace = workspace
|
||||
window_title = 'MsSpecGui'
|
||||
if workspace is not None:
|
||||
window_title += ' - ' + workspace.workspace_path
|
||||
self.SetTitle(window_title)
|
||||
|
||||
if workspace is None:
|
||||
if self.view_manager is not None:
|
||||
self.view_manager.Destroy()
|
||||
self.view_manager = None
|
||||
else:
|
||||
view_factory = ViewFactory()
|
||||
view_factory.register_view_creator(ClusterEditor.Creator(self.get_workspace()))
|
||||
view_factory.register_view_creator(ClusterView.Creator(self.get_workspace().get_cluster_flow()))
|
||||
self.view_manager = ViewManager(self, view_factory)
|
||||
self.view_manager.views[0].set_view_type(ClusterEditor.Creator.VIEW_TYPE_NAME)
|
||||
view2 = self.view_manager.add_view()
|
||||
view2.set_view_type(ClusterView.Creator.VIEW_TYPE_NAME)
|
||||
self.Hide() # without this call, I couldn't make the workspace window updated (at least on osx); as a result, I had to drag the corner of the window to force a refresh
|
||||
self.Show()
|
||||
|
||||
def get_workspace(self):
|
||||
return self.m_workspace
|
||||
|
||||
def close_workspace(self, ask_confirmation=True):
|
||||
if self.m_workspace is not None:
|
||||
dlg = wx.MessageDialog(self, message="Do you want to save your workspace before closing it ?",
|
||||
caption="Save current workspace ?",
|
||||
style=wx.YES_NO | wx.CANCEL | wx.YES_DEFAULT | wx.ICON_QUESTION)
|
||||
clicked_button_id = dlg.ShowModal() # Show it
|
||||
dlg.Destroy() # finally destroy it when finished.
|
||||
if clicked_button_id == wx.ID_YES:
|
||||
self.save_workspace()
|
||||
elif clicked_button_id == wx.ID_CANCEL:
|
||||
return False
|
||||
self.set_workspace(None)
|
||||
|
||||
def save_workspace(self):
|
||||
self.m_workspace.save()
|
||||
|
||||
def on_close(self, event):
|
||||
print("MainWindow.on_close")
|
||||
self.close_workspace(ask_confirmation=True)
|
||||
event.Skip() # propagate the event so that the dialog closes
|
|
@ -0,0 +1,51 @@
|
|||
import wx
|
||||
import wx.lib.scrolledpanel as scrolled
|
||||
|
||||
########################################################################
|
||||
class MyForm(wx.Frame):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self):
|
||||
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(200,500))
|
||||
|
||||
# Add a panel so it looks the correct on all platforms
|
||||
self.panel = wx.Panel(self, wx.ID_ANY)
|
||||
|
||||
# --------------------
|
||||
# Scrolled panel stuff
|
||||
self.scrolled_panel = scrolled.ScrolledPanel(self.panel, -1,
|
||||
style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="panel1")
|
||||
self.scrolled_panel.SetAutoLayout(1)
|
||||
self.scrolled_panel.SetupScrolling()
|
||||
|
||||
words = "A Quick Brown Insane Fox Jumped Over the Fence and Ziplined to Cover".split()
|
||||
self.spSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
for word in words:
|
||||
text = wx.TextCtrl(self.scrolled_panel, value=word)
|
||||
self.spSizer.Add(text)
|
||||
self.scrolled_panel.SetSizer(self.spSizer)
|
||||
# --------------------
|
||||
|
||||
btn = wx.Button(self.panel, label="Add Widget")
|
||||
btn.Bind(wx.EVT_BUTTON, self.onAdd)
|
||||
|
||||
panelSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
panelSizer.AddSpacer(50)
|
||||
panelSizer.Add(self.scrolled_panel, 1, wx.EXPAND)
|
||||
panelSizer.Add(btn)
|
||||
self.panel.SetSizer(panelSizer)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onAdd(self, event):
|
||||
""""""
|
||||
print "in onAdd"
|
||||
new_text = wx.TextCtrl(self.scrolled_panel, value="New Text")
|
||||
self.spSizer.Add(new_text)
|
||||
self.scrolled_panel.Layout()
|
||||
self.scrolled_panel.SetupScrolling()
|
||||
|
||||
# Run the program
|
||||
if __name__ == "__main__":
|
||||
app = wx.App(False)
|
||||
frame = MyForm().Show()
|
||||
app.MainLoop()
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
import wx
|
||||
|
||||
class ScrolledImageComponent(wx.ScrolledWindow):
|
||||
|
||||
def __init__(self, parent, id, image_path):
|
||||
wx.ScrolledWindow.__init__(self, parent, id)
|
||||
|
||||
image = wx.Image(image_path)
|
||||
if not image.IsOk():
|
||||
wx.MessageBox("there was an error loading the image")
|
||||
return
|
||||
|
||||
self.w = image.GetWidth()
|
||||
self.h = image.GetHeight()
|
||||
|
||||
# init scrolled area size, scrolling speed, etc. */
|
||||
self.SetScrollbars(1,1, self.w, self.h, 0, 0)
|
||||
|
||||
self.bitmap = wx.Bitmap(image_path)
|
||||
|
||||
def OnDraw(self, dc):
|
||||
"""
|
||||
render the image - in a real app, if your scrolled area
|
||||
is somewhat big, you will want to draw only visible parts,
|
||||
not everything like below
|
||||
"""
|
||||
dc.DrawBitmap(self.bitmap, 0, 0, False)
|
||||
|
||||
#// also check wxScrolledWindow::PrepareDC
|
||||
|
||||
# Run the program
|
||||
if __name__ == "__main__":
|
||||
app = wx.App(False)
|
||||
wx.InitAllImageHandlers();
|
||||
frame = wx.Frame(None)
|
||||
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
my_image = ScrolledImageComponent(frame, wx.ID_ANY, "/Users/graffy/data/Perso/mms_img1041085029.jpg")
|
||||
sizer.Add(my_image, 1, wx.ALL | wx.EXPAND, 120)
|
||||
frame.SetSizer(sizer);
|
||||
frame.Show()
|
||||
app.MainLoop()
|
||||
|
||||
"""
|
||||
class ScrolledImageComponent : public wxScrolledWindow
|
||||
{
|
||||
wxBitmap* bitmap;
|
||||
int w,h;
|
||||
public:
|
||||
ScrolledImageComponent(wxWindow* parent, wxWindowID id, wxString image_path) : wxScrolledWindow(parent, id)
|
||||
{
|
||||
wxImage image(image_path);
|
||||
if(!image.IsOk())
|
||||
{
|
||||
wxMessageBox(wxT("there was an error loading the image"));
|
||||
return;
|
||||
}
|
||||
|
||||
w = image.GetWidth();
|
||||
h = image.GetHeight();
|
||||
|
||||
/* init scrolled area size, scrolling speed, etc. */
|
||||
SetScrollbars(1,1, w, h, 0, 0);
|
||||
|
||||
bitmap = new wxBitmap( image );
|
||||
}
|
||||
~ScrolledImageComponent()
|
||||
{
|
||||
delete bitmap;
|
||||
}
|
||||
void OnDraw(wxDC& dc)
|
||||
{
|
||||
/* render the image - in a real app, if your scrolled area
|
||||
is somewhat big, you will want to draw only visible parts,
|
||||
not everything like below */
|
||||
dc.DrawBitmap(*bitmap, 0, 0, false);
|
||||
|
||||
// also check wxScrolledWindow::PrepareDC
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class MyApp: public wxApp
|
||||
{
|
||||
wxFrame *frame;
|
||||
public:
|
||||
|
||||
bool OnInit()
|
||||
{
|
||||
wxInitAllImageHandlers();
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
frame = new wxFrame((wxFrame *)NULL, -1, wxT("Scrolling an Image"), wxPoint(50,50), wxSize(650,650));
|
||||
|
||||
ScrolledImageComponent* my_image = new ScrolledImageComponent(frame, wxID_ANY, wxT("my_image.jpg") );
|
||||
sizer->Add(my_image, 1, wxALL | wxEXPAND, 120);
|
||||
frame->SetSizer(sizer);
|
||||
|
||||
frame->Show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
"""
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
from __future__ import print_function
|
||||
import abc
|
||||
import functools
|
||||
import wx.lib.agw.aui as aui
|
||||
import wx
|
||||
|
||||
|
||||
class IViewCreator(object):
|
||||
|
||||
@abc.abstractproperty
|
||||
def view_type_name(self):
|
||||
"""
|
||||
:return str:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_view(self, parent):
|
||||
"""
|
||||
:param wx.Window parent: the wx.Window that owns the view
|
||||
:return wx.Panel:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ViewFactory(object):
|
||||
|
||||
def __init__(self):
|
||||
self._view_creators = {} #: :type self._view_creators: dict(str, IViewCreator)
|
||||
|
||||
def register_view_creator(self, view_creator):
|
||||
"""
|
||||
:param IViewCreator view_creator:
|
||||
"""
|
||||
self._view_creators[view_creator.view_type_name] = view_creator
|
||||
|
||||
def get_view_type_names(self):
|
||||
return list(self._view_creators.iterkeys())
|
||||
|
||||
def create_view(self, view_type_name, container_window):
|
||||
"""
|
||||
:param str view_type_name: the type of view to create
|
||||
:param wx.Window container_window: the parent wx window
|
||||
"""
|
||||
return self._view_creators[view_type_name].create_view(container_window)
|
||||
|
||||
|
||||
class View(wx.Panel):
|
||||
"""
|
||||
panel who will be used to create the interface
|
||||
"""
|
||||
|
||||
def __init__(self, view_manager, view_id):
|
||||
"""
|
||||
:param ViewManager view_manager: the manager that manages this view
|
||||
:param int id: a number uniquely identifying this view in its view manager
|
||||
"""
|
||||
wx.Panel.__init__(self, view_manager, -1)
|
||||
self.view_manager = view_manager
|
||||
self.id = view_id
|
||||
|
||||
vbox = wx.BoxSizer(wx.VERTICAL)
|
||||
self.SetSizer(vbox)
|
||||
self.panel = None
|
||||
|
||||
@property
|
||||
def view_factory(self):
|
||||
return self.view_manager.view_factory
|
||||
|
||||
def set_view_type(self, view_type):
|
||||
"""
|
||||
:param str view_type:
|
||||
"""
|
||||
self.on_view_set(None, view_type)
|
||||
|
||||
def on_view_set(self, evt, view_type_name):
|
||||
"""
|
||||
called when the menu item that assigns a view type to this view is clicked by the user
|
||||
|
||||
:param str view_type_name: the type of view that the user just chose for this view
|
||||
"""
|
||||
|
||||
pane = self.view_manager.aui_manager.GetPaneByWidget(self)
|
||||
|
||||
index = self.view_manager.get_free_pane_index(view_type_name)
|
||||
|
||||
pane_name = view_type_name + ("" if index == 0 else " " + str(index))
|
||||
pane.caption = pane_name
|
||||
|
||||
if self.panel is not None:
|
||||
self.panel.Destroy()
|
||||
self.panel = None
|
||||
|
||||
self.panel = self.view_factory.create_view(view_type_name, self)
|
||||
self.GetSizer().Add(self.panel, proportion=1, flag=wx.EXPAND) # the new panel is the only item in the vbox sizer that is allowed to change its size, so any value other than 0 will do
|
||||
|
||||
# self.Update()
|
||||
# self.Refresh()
|
||||
# self.view_manager.Update()
|
||||
# self.view_manager.Refresh()
|
||||
self.view_manager.aui_manager.Update()
|
||||
self.view_manager.update_view_menu()
|
||||
# print self.selected_crystal_str
|
||||
|
||||
|
||||
class ViewManager(wx.Panel):
|
||||
"""a user interface that contains views
|
||||
"""
|
||||
|
||||
def __init__(self, parent, view_factory):
|
||||
"""
|
||||
:param ViewFactory view_factory: the factory that is used to create views
|
||||
"""
|
||||
super(ViewManager, self).__init__(parent)
|
||||
self.view_factory = view_factory
|
||||
|
||||
self.parent = parent
|
||||
|
||||
# manages the panes associated with it for a particular wxFrame
|
||||
self.aui_manager = aui.AuiManager(self)
|
||||
self.last_created_view_id = 0
|
||||
self.views = []
|
||||
self.view_set_menu = None # :param wxMenu self.view_set_menu: the menu View->Set that allows the user to assign a view type to a view
|
||||
|
||||
view = self.add_view(False, False) # @UnusedVariable the 1st view is special : it's not closable nor movable (to avoid holes)
|
||||
|
||||
self.aui_manager.Update()
|
||||
self.update_view_menu()
|
||||
|
||||
def pane_exists(self, panes, pane_name_prefix, pane_index):
|
||||
for pane_info in panes:
|
||||
if pane_info.caption == "%s %d" % (pane_name_prefix, pane_index):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_free_pane_index(self, pane_name_prefix):
|
||||
"""
|
||||
finds a non-used integer to uniquely identify a pane that we want to name "<pane_name_prefix> <i>"
|
||||
|
||||
:param str pane_name_prefix: the pane name prefix (usually the view type name)
|
||||
"""
|
||||
index = 1
|
||||
while self.pane_exists(self.aui_manager.GetAllPanes(), pane_name_prefix, index):
|
||||
index += 1
|
||||
return index
|
||||
|
||||
def add_view(self, movable=True, enable_close_button=True):
|
||||
"""
|
||||
:param bool movable: if True, the view is movable
|
||||
:param bool enable_close_button: if True a close button is added to the view
|
||||
:return View:
|
||||
"""
|
||||
self.last_created_view_id += 1
|
||||
view = View(self, self.last_created_view_id)
|
||||
|
||||
index = self.get_free_pane_index("Empty")
|
||||
|
||||
caption = 'Empty %d' % (index)
|
||||
pane_info = aui.AuiPaneInfo().Movable(movable).Floatable(False).CloseButton(enable_close_button).Caption(caption).DestroyOnClose(True)
|
||||
if view.id % 2 == 1:
|
||||
pane_info = pane_info.Center()
|
||||
else:
|
||||
pane_info = pane_info.Right()
|
||||
pane_info = pane_info.Layer(1)
|
||||
self.aui_manager.AddPane(view, pane_info)
|
||||
|
||||
self.aui_manager.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_close_pane)
|
||||
|
||||
self.aui_manager.OnFloatingPaneResized(view, None)
|
||||
self.aui_manager.Update()
|
||||
self.views.append(view)
|
||||
self.update_view_menu()
|
||||
return view
|
||||
|
||||
def on_close_pane(self, event):
|
||||
"""
|
||||
:param wx.Event event:
|
||||
"""
|
||||
pane = event.GetPane()
|
||||
self.views.remove(pane.window)
|
||||
items = self.view_set_menu.GetMenuItems()
|
||||
for i in items:
|
||||
if i.GetText() == pane.caption:
|
||||
self.view_set_menu.DestroyItem(i)
|
||||
|
||||
def update_view_menu(self):
|
||||
"""
|
||||
builds and updates the view menu to reflect the views in the view manager
|
||||
"""
|
||||
def populate_view_set_submenu(view_set_submenu, views, aui_manager, view_type_names, window_receiving_events):
|
||||
"""
|
||||
populate the View/Set menu
|
||||
|
||||
:param wxMenu view_set_submenu: the menu View/Set
|
||||
:param list(View) views: the list of existing views in the view manager
|
||||
:param wxAuiManager aui_manager: the aui manager that manages the views (which are widgets)
|
||||
:param list(str) view_type_names: the list of available view types
|
||||
:param wxWindow window_receiving_events: the window that receives the menu events
|
||||
"""
|
||||
for view in views:
|
||||
view_item = wx.Menu()
|
||||
view_set_submenu.AppendSubMenu(view_item, aui_manager.GetPaneByWidget(view).caption)
|
||||
for view_type_name in view_type_names:
|
||||
menu_item_id = wx.NewId()
|
||||
view_type_item = wx.MenuItem(view_item, menu_item_id, view_type_name)
|
||||
view_item.AppendItem(view_type_item)
|
||||
window_receiving_events.Bind(wx.EVT_MENU, functools.partial(view.on_view_set, view_type_name=view_type_name), view_type_item)
|
||||
|
||||
# create the menu bar if it doesn't exist yet
|
||||
if self.parent.GetMenuBar():
|
||||
menu_bar = self.parent.GetMenuBar()
|
||||
else:
|
||||
menu_bar = wx.MenuBar()
|
||||
|
||||
# delete the View menu if it already exists, as we are going to re-create it
|
||||
i = 0
|
||||
while i < menu_bar.GetMenuCount():
|
||||
if menu_bar.GetMenuLabelText(i) == "View":
|
||||
menu_bar.Remove(i)
|
||||
i += 1
|
||||
|
||||
# create the view menu
|
||||
menu_view = wx.Menu()
|
||||
self.view_set_menu = wx.Menu()
|
||||
|
||||
# create the menu item that allows the user to add a view to the view manager
|
||||
add_view_menu_item = wx.MenuItem(menu_view, wx.NewId(), "Add")
|
||||
menu_view.AppendItem(add_view_menu_item)
|
||||
# create the sub menu that allows the user to assign a view type to a view
|
||||
menu_view.AppendSubMenu(self.view_set_menu, "Set")
|
||||
menu_bar.Append(menu_view, "View")
|
||||
self.parent.Bind(wx.EVT_MENU, self.add_view, add_view_menu_item)
|
||||
self.parent.SetMenuBar(menu_bar)
|
||||
|
||||
populate_view_set_submenu(self.view_set_menu, self.views, self.aui_manager, self.view_factory.get_view_type_names(), self.parent)
|
||||
|
||||
"""
|
||||
def close_win(self, event):
|
||||
self.Close()
|
||||
|
||||
def maximize(self, event):
|
||||
self.Maximize(True)
|
||||
|
||||
def minimize(self, event):
|
||||
self.Maximize(False)
|
||||
"""
|
|
@ -0,0 +1,54 @@
|
|||
'''
|
||||
Created on Dec 23, 2015
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
|
||||
import msspecgui.msspec.cluster.clusterflow
|
||||
# import sys
|
||||
import msspecgui.dataflowxmlserializer
|
||||
|
||||
|
||||
class Workspace(object):
|
||||
'''
|
||||
classdocs
|
||||
'''
|
||||
|
||||
SETTINGS_FILE_NAME = 'settings.xml'
|
||||
|
||||
def __init__(self, workspace_path, is_new_workspace):
|
||||
'''Constructor
|
||||
|
||||
:param workspace_path: the file path to the workspace directory
|
||||
:type workspace_path: str
|
||||
:param is_new_workspace: if True, the workspace is created, otherwise the workspace is loaded from it path
|
||||
|
||||
'''
|
||||
self._workspace_path = workspace_path
|
||||
if not is_new_workspace:
|
||||
self.load()
|
||||
else:
|
||||
self._cluster_flow = msspecgui.msspec.cluster.clusterflow.ClusterFlow()
|
||||
debugging = False
|
||||
if debugging is True:
|
||||
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
|
||||
serializer.save_dataflow(self._cluster_flow, '%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME))
|
||||
|
||||
def load(self):
|
||||
'''Loads the workspace
|
||||
'''
|
||||
print('Workspace.load')
|
||||
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
|
||||
self._cluster_flow = msspecgui.msspec.cluster.clusterflow.ClusterFlow()
|
||||
serializer.load_dataflow('%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME), self._cluster_flow)
|
||||
|
||||
def save(self):
|
||||
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
|
||||
serializer.save_dataflow(self._cluster_flow, '%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME))
|
||||
|
||||
def get_cluster_flow(self):
|
||||
return self._cluster_flow
|
||||
|
||||
@property
|
||||
def workspace_path(self):
|
||||
return self._workspace_path
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""seagull module"""
|
||||
from scenegraph import *
|
|
@ -0,0 +1 @@
|
|||
from render import render_scene
|
|
@ -0,0 +1,347 @@
|
|||
# encoding:utf-8
|
||||
'''
|
||||
Created on Feb 19, 2016
|
||||
|
||||
@author: graffy
|
||||
'''
|
||||
import cairo
|
||||
import math
|
||||
import re
|
||||
from ..scenegraph.element._path import _quadric
|
||||
|
||||
|
||||
def scenegraph_matrix_to_cairo_matrix(sg_matrix):
|
||||
"""
|
||||
:type sg_matrix: scenegraph._Transform
|
||||
"""
|
||||
return cairo.Matrix(*sg_matrix.abcdef)
|
||||
|
||||
# from logging import log
|
||||
|
||||
|
||||
def render_group(group, cairo_context):
|
||||
""" renders the scenegraph group 'group' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param group: the scenegraph group to render
|
||||
:type group: scenegraph2d.Group
|
||||
:param cairo_context: the cairo drawing context
|
||||
:type cairo_context: cairo.Context
|
||||
"""
|
||||
# print('rendering group')
|
||||
cairo_context.transform(scenegraph_matrix_to_cairo_matrix(group.local_to_parent_matrix()))
|
||||
for child in group.children:
|
||||
render_node(child, cairo_context)
|
||||
|
||||
|
||||
def render_rectangle(rect, cairo_context):
|
||||
""" renders the scenegraph rectangle 'rect' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param rect: the scenegraph rectangle to render
|
||||
:type rect: scenegraph2d.Rectangle
|
||||
:param cairo_context: the cairo drawing context
|
||||
"""
|
||||
# cairo_context.set_source_rgb(0.5, 0.5, 0.5)
|
||||
cairo_context.rectangle(rect.x, rect.y, rect.width, rect.height)
|
||||
cairo_context.fill()
|
||||
|
||||
# print('rendering rectangle')
|
||||
|
||||
|
||||
def render_path(path, cairo_context):
|
||||
""" renders the scenegraph path 'path' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param path: the scenegraph path to render
|
||||
:type path: scenegraph2d.Path
|
||||
:param cairo_context: the cairo drawing context
|
||||
:type cairo_context: cairo.Context
|
||||
"""
|
||||
color = path.fill
|
||||
cairo_context.set_source_rgb(*color.rgb)
|
||||
path_as_svg_code = path.d
|
||||
# print('render_path : rendering path %s' % str(path_as_svg_code))
|
||||
paths = []
|
||||
path = []
|
||||
joins = []
|
||||
du2 = 1.
|
||||
path_data_iter = iter(path_as_svg_code)
|
||||
|
||||
def next_d():
|
||||
return next(path_data_iter)
|
||||
|
||||
def findCircleCenterPassingThroughPoints(p1, p2, r):
|
||||
"""
|
||||
the set of points (x,y) that are at a distance r from p1=(x1,y1) satisfy :
|
||||
(x-x1)^2 + (y-y1)^2 = r^2
|
||||
|
||||
the set of points (x,y) that are at a distance r from p2=(x2,y2) satisfy :
|
||||
(x-x2)^2 + (y-y2)^2 = r^2
|
||||
|
||||
|
||||
So here's another idea for solving the problem. It should lead to the
|
||||
same answer and the algebra will be less tedious. This method,
|
||||
however, uses some fancy ideas from "vector theory", which are
|
||||
probably strange and unfamiliar to you.
|
||||
|
||||
Think of the geometry. You know that for any two points there's a
|
||||
"mirror line" that goes halfway between them. Technically, the line
|
||||
consists of the locus of all points that are equidistant from your two
|
||||
circle points; you can think of the line as a mirror where each of
|
||||
your two points appears as a reflection of the other.
|
||||
|
||||
Well, this line will help us a lot in constructing our center, because
|
||||
we know that the center is on the line AND because we can use
|
||||
Pythagoras to tell us where on the line the point is. Here's how we
|
||||
can do all that with algebra.
|
||||
|
||||
First, find the distance between points 1 and 2. We'll call that q,
|
||||
and it's given by sqrt((x2-x1)^2 + (y2-y1)^2).
|
||||
|
||||
Second, find the point halfway between your two points. We'll call it
|
||||
(x3, y3). x3 = (x1+x2)/2 and y3 = (y1+y2)/2.
|
||||
|
||||
Now find the direction of the line between point 1 and point 2. That
|
||||
direction is (x1-x2, y1-y2).
|
||||
|
||||
What we really want to know is the direction of the mirror line, which
|
||||
is perpendicular to the line between point 1 and point 2.
|
||||
|
||||
Here's a crucial trick: you can find the direction of a perpendicular
|
||||
to a line just by swapping x with y and changing the sign of one. In
|
||||
other words, if the direction of the joining line was (x1-x2, y1-y2)
|
||||
then the direction of the mirror line is (y1-y2,x2-x1).
|
||||
|
||||
It will be helpful to "normalize" our direction which means to make
|
||||
the length of the line equal to 1. You do this just by dividing both
|
||||
the (y1-y2) and the (x2-x1) by q.
|
||||
|
||||
The two circle centers will both be on the mirror line, and we can use
|
||||
geometry to find how far they are from the point (x3,y3): First
|
||||
notice that the distance from point (x3,y3) to either point 1 or point
|
||||
2 is just half of q. Then the distance to move along the mirror line
|
||||
is:
|
||||
sqrt(r^2-(q/2)^2)
|
||||
|
||||
Move up the mirror line by adding a multiple of the direction line to
|
||||
the point (x3,y3) or move down the mirror line by subtracting the same
|
||||
multiple.
|
||||
|
||||
One answer will be:
|
||||
|
||||
x = x3 + sqrt(r^2-(q/2)^2)*(y1-y2)/q
|
||||
y = y3 + sqrt(r^2-(q/2)^2)*(x2-x1)/q
|
||||
|
||||
The other will be:
|
||||
|
||||
x = x3 - sqrt(r^2-(q/2)^2)*(y1-y2)/q
|
||||
y = y3 - sqrt(r^2-(q/2)^2)*(x2-x1)/q
|
||||
|
||||
"""
|
||||
#
|
||||
q = math.sqrt((p2[1] - p1[1]) ** 2 + (p2[0] - p1[0]) ** 2) # @UnusedVariable
|
||||
# let p3 be the midpoint between p1 and p2
|
||||
p3 = ((p1[0] + p2[0]) * 0.5, (p1[1] + p2[1]) * 0.5) # @UnusedVariable
|
||||
assert(False) # todo : finish this implementation
|
||||
|
||||
pn = p0 = (0., 0.)
|
||||
cn = None
|
||||
for c in path_data_iter:
|
||||
x0, y0 = p0
|
||||
xn, yn = pn
|
||||
|
||||
if c.islower(): # coordinates are then relative coordinates
|
||||
def next_p():
|
||||
dx, dy = next_d()
|
||||
return (x0 + dx, y0 + dy)
|
||||
|
||||
def next_x():
|
||||
dx = next_d()
|
||||
return x0 + dx
|
||||
|
||||
def next_y():
|
||||
dy = next_d()
|
||||
return y0 + dy
|
||||
c = c.upper()
|
||||
else:
|
||||
next_x = next_y = next_p = next_d
|
||||
|
||||
if c == 'M': # Moveto
|
||||
p1 = next_p()
|
||||
# if path:
|
||||
# paths.append((path, False, joins))
|
||||
# path = [p1]
|
||||
# joins = []
|
||||
cairo_context.move_to(*p1)
|
||||
|
||||
pn, p0 = p0, p1
|
||||
|
||||
elif c in "LHV":
|
||||
if c == 'L': # Lineto
|
||||
p1 = next_p()
|
||||
elif c == 'H': # Horizontal Lineto
|
||||
p1 = (next_x(), y0)
|
||||
elif c == 'V': # Vertical Lineto
|
||||
p1 = (x0, next_y())
|
||||
cairo_context.line_to(*p1)
|
||||
# path.append(p1)
|
||||
pn, p0 = p0, p1
|
||||
|
||||
elif c in "CS": # cubic bezier curve
|
||||
if c == 'C':
|
||||
p1 = next_p()
|
||||
else: # 'S'
|
||||
p1 = (2. * x0 - xn, 2 * y0 - yn) if cn in "CS" else p0
|
||||
p2, p3 = next_p(), next_p()
|
||||
# path += _cubic(p0, p1, p2, p3, du2)
|
||||
cairo_context.rel_curve_to(p1, p2, p3)
|
||||
|
||||
pn, p0 = p2, p3
|
||||
|
||||
elif c in 'QT': # quadratic bezier curve
|
||||
if c == 'Q':
|
||||
p1 = next_p()
|
||||
else: # 'T'
|
||||
p1 = (2. * x0 - xn, 2 * y0 - yn) if cn in "QT" else p0
|
||||
p2 = next_p()
|
||||
path += _quadric(p0, p1, p2, du2)
|
||||
pn, p0 = p1, p2
|
||||
|
||||
elif c == 'A': # Arcto
|
||||
rs, phi, flags = next_d(), next_d(), next_d() # @UnusedVariable
|
||||
# rs = (rx, ry) : radius in each direction
|
||||
# phi = rotation of the axis of the ellipse
|
||||
# flags = (large-arc-flag, sweep-flag)
|
||||
# large-arc-flag, indique si on doit afficher l’arc dont la mesure fait plus de la moitié du périmètre de l’ellipse (dans ce cas, la valeur est 1), ou l’arc dont la mesure fait moins de la moitié du périmètre (valeur : 0).
|
||||
# sweep-flag, indique quant à lui si l’arc doit être dessiné dans la direction négative des angles (dans lequel cas sa valeur est 0) ou dans la direction positive des angles (valeur : 1)
|
||||
p1 = next_p()
|
||||
# p1 : end point
|
||||
# path += _arc(p0, rs, phi, flags, p1, du2)
|
||||
assert(False) # incomplete implementation
|
||||
# cairo_context.rel_curve_to
|
||||
pn, p0 = p0, p1
|
||||
|
||||
elif c == 'Z': # Closepath
|
||||
x1, y1 = p1 = path[0]
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
if (dx * dx + dy * dy) * du2 > 1.:
|
||||
path.append(p1)
|
||||
paths.append((path, True, joins))
|
||||
path = []
|
||||
joins = []
|
||||
pn, p0 = p0, p1
|
||||
|
||||
cn = c
|
||||
joins.append(len(path) - 1)
|
||||
|
||||
if path:
|
||||
paths.append((path, False, joins))
|
||||
cairo_context.stroke()
|
||||
cairo_context.fill()
|
||||
|
||||
# print('rendering path')
|
||||
|
||||
|
||||
def render_circle(circle_node, cairo_context):
|
||||
""" renders the scenegraph circle 'circle_node' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param circle_node: the scenegraph text to render
|
||||
:type circle_node: scenegraph2d.Circle
|
||||
:param cairo_context: the cairo drawing context
|
||||
"""
|
||||
color = circle_node.fill
|
||||
cairo_context.set_source_rgb(*color.rgb)
|
||||
cairo_context.arc(circle_node.cx, circle_node.cy, circle_node.r, 0.0, math.pi * 2.0)
|
||||
cairo_context.fill()
|
||||
|
||||
# print('render_circle : rendering circle (%f, %f, %f)' % (circle_node.cx, circle_node.cy, circle_node.r))
|
||||
|
||||
|
||||
def render_text(text_node, cairo_context):
|
||||
""" renders the scenegraph text 'text' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param text_node: the scenegraph text to render
|
||||
:type text_node: scenegraph2d.Text
|
||||
:param cairo_context: the cairo drawing context
|
||||
"""
|
||||
# face = wx.lib.wxcairo.FontFaceFromFont(wx.FFont(10, wx.SWISS, wx.FONTFLAG_BOLD))
|
||||
# ctx.set_font_face(face)
|
||||
cairo_context.set_font_size(10)
|
||||
(x, y, width, height, dx, dy) = cairo_context.text_extents(text_node.text) # @UnusedVariable
|
||||
# ctx.move_to(*center.Get())
|
||||
# cairo_context.move_to(center.x - width/2, center.y + height/2)
|
||||
# cairo_context.set_source_rgb(0, 0, 0)
|
||||
|
||||
cairo_context.move_to(text_node._anchor(), 0.0)
|
||||
cairo_context.show_text(text_node.text)
|
||||
|
||||
# print('rendering text')
|
||||
|
||||
|
||||
def render_node(node, cairo_context):
|
||||
""" renders the scenegraph node 'node' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param node: the scenegraph node to render
|
||||
:type node: scenegraph2d.Element
|
||||
:param cairo_context: the cairo drawing context
|
||||
:type cairo_context: cairo.Context
|
||||
"""
|
||||
# print node.tag
|
||||
try:
|
||||
handler = {
|
||||
# "svg": render_group,
|
||||
"g": render_group,
|
||||
# "symbol": render_group,
|
||||
# "a": render_group,
|
||||
# "defs": render_group,
|
||||
# "clipPath": render_group,
|
||||
# "mask": render_group,
|
||||
# "path": sg.Path,
|
||||
"rect": render_rectangle,
|
||||
"circle": render_circle,
|
||||
# "ellipse": sg.Ellipse,
|
||||
"line": render_path,
|
||||
# "polyline": sg.Polyline,
|
||||
# "polygon": sg.Polygon,
|
||||
"text": render_text,
|
||||
}[node.tag]
|
||||
except KeyError:
|
||||
# log.warning("unhandled tag : %s" % node.tag)
|
||||
print("unhandled tag : %s" % node.tag)
|
||||
return
|
||||
|
||||
# save the parent transform matrix
|
||||
# parent_matrix = cairo_context.get_matrix()
|
||||
cairo_context.save()
|
||||
|
||||
if(hasattr(node, 'fill')):
|
||||
# print("node.fill = %s" % str(node.fill))
|
||||
if (isinstance(node.fill, str)):
|
||||
assert(False) # this shouldn't happen now that fill attribute is set to a Color node
|
||||
match = re.match('^rgb\((?P<r>[0-9]+),(?P<g>[0-9]+),(?P<b>[0-9]+)\)', node.fill)
|
||||
if match:
|
||||
r = float(match.group('r')) / 255.0
|
||||
g = float(match.group('g')) / 255.0
|
||||
b = float(match.group('b')) / 255.0
|
||||
else:
|
||||
assert(False)
|
||||
else:
|
||||
r, g, b = node.fill.get_rgb()
|
||||
cairo_context.set_source_rgb(r, g, b)
|
||||
|
||||
# render the node
|
||||
handler(node, cairo_context)
|
||||
|
||||
cairo_context.restore()
|
||||
|
||||
# restore the parent transform matrix (so that brother nodes car render relative to their parent)
|
||||
# cairo_context.set_matrix(parent_matrix)
|
||||
|
||||
|
||||
def render_scene(scene, cairo_context):
|
||||
""" renders the scenegraph 'scene' in the cairo drawing context 'cairo_context'
|
||||
|
||||
:param scene: the scenegraph to render
|
||||
:type scene: scenegraph2d.Group
|
||||
:param cairo_context: the cairo drawing context
|
||||
:type cairo_context: cairo.Context
|
||||
"""
|
||||
render_node(scene, cairo_context)
|
|
@ -0,0 +1,163 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""font abstraction"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from ctypes import byref, string_at
|
||||
|
||||
from .utils import get_font
|
||||
|
||||
from . import freetype2 as _ft2
|
||||
_FT = _ft2.FT
|
||||
_library = _ft2._library
|
||||
|
||||
|
||||
# globals ####################################################################
|
||||
|
||||
def _on(tag):
|
||||
return bool(tag & 1)
|
||||
|
||||
|
||||
def _cubic(tag):
|
||||
return bool(tag & 2)
|
||||
|
||||
|
||||
# face #######################################################################
|
||||
|
||||
class Face(object):
|
||||
def __init__(self, font_families, font_weight, font_style, px=10):
|
||||
font_name, index = get_font(font_families, font_weight, font_style)
|
||||
self.face = _ft2.Face()
|
||||
self.pen = _ft2.Vector()
|
||||
print('_library = %s' % str(_library))
|
||||
print('font_name = %s' % str(font_name))
|
||||
print('font_name.encode() = %s' % str(font_name.encode()))
|
||||
if _FT.New_Face(_library, font_name.encode(), index, byref(self.face)) != 0:
|
||||
raise ValueError("unable to create '%s' face" % font_name)
|
||||
_FT.Select_Charmap(self.face, _ft2.ENCODING_UNICODE)
|
||||
if px is not None:
|
||||
self.set_size(px)
|
||||
self._FT = _FT # keep a ref for finalizer
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self._FT.Done_Face(self.face)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def set_size(self, px):
|
||||
_FT.Set_Pixel_Sizes(self.face, 0, px)
|
||||
|
||||
def set_transform(self, a=1., b=0., c=0., d=1., e=0., f=0.):
|
||||
matrix = _ft2.Matrix()
|
||||
matrix.xx, matrix.xy = int(a * 0x10000), int(b * 0x10000)
|
||||
matrix.yx, matrix.yy = int(c * 0x10000), int(d * 0x10000)
|
||||
self.pen.x = int(e * 64)
|
||||
self.pen.y = int(-f * 64)
|
||||
_FT.Set_Transform(self.face, byref(matrix), byref(self.pen))
|
||||
|
||||
def _glyph(self, uc):
|
||||
glyph_index = _FT.Get_Char_Index(self.face, ord(uc))
|
||||
_FT.Load_Glyph(self.face, glyph_index, _ft2.LOAD_DEFAULT)
|
||||
return self.face.contents.glyph.contents
|
||||
|
||||
def get_hkerning(self, ucl, ucr):
|
||||
if ucl is None:
|
||||
return 0.
|
||||
|
||||
left_glyph = _FT.Get_Char_Index(self.face, ord(ucl))
|
||||
right_glyph = _FT.Get_Char_Index(self.face, ord(ucr))
|
||||
kerning = _ft2.Vector()
|
||||
_FT.Get_Kerning(self.face, left_glyph, right_glyph,
|
||||
_ft2.KERNING_DEFAULT, byref(kerning))
|
||||
return kerning.x / 64.
|
||||
|
||||
def get_bbox(self, text):
|
||||
width = 0
|
||||
top, bottom = 0, 0
|
||||
up = None
|
||||
glyph = None
|
||||
for uc in text:
|
||||
width += self.get_hkerning(up, uc)
|
||||
up = uc
|
||||
|
||||
glyph = self._glyph(uc)
|
||||
width += glyph.metrics.horiAdvance / 64.
|
||||
top = max(top, glyph.metrics.horiBearingY / 64.)
|
||||
bottom = min(bottom, (glyph.metrics.horiBearingY -
|
||||
glyph.metrics.height) / 64.)
|
||||
if glyph:
|
||||
width += (glyph.metrics.horiBearingX + glyph.metrics.width -
|
||||
glyph.metrics.horiAdvance) / 64.
|
||||
return (0., -top), (width, top - bottom)
|
||||
|
||||
def bitmap(self, uc):
|
||||
glyph = self._glyph(uc)
|
||||
_FT.Render_Glyph(byref(glyph), _ft2.RENDER_MODE_NORMAL)
|
||||
|
||||
origin = glyph.bitmap_left, -glyph.bitmap_top
|
||||
bitmap = glyph.bitmap
|
||||
assert bitmap.pixel_mode == _ft2.PIXEL_MODE_GRAY, bitmap.pixel_mode
|
||||
|
||||
rows, columns = bitmap.rows, bitmap.pitch
|
||||
size = columns, rows
|
||||
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
|
||||
data = string_at(bitmap.buffer, rows * columns)
|
||||
data = bytes(chain(*([255, 255, 255, c] for c in data)))
|
||||
return origin, size, offset, data
|
||||
|
||||
def outline(self, uc):
|
||||
glyph = self._glyph(uc)
|
||||
|
||||
outline = glyph.outline
|
||||
|
||||
data = []
|
||||
b = 0
|
||||
for c in range(outline.n_contours):
|
||||
e = outline.contours[c]
|
||||
|
||||
# ensure we start with an 'on' point
|
||||
for s in range(b, e + 1):
|
||||
if _on(outline.tags[s]):
|
||||
break
|
||||
|
||||
# generate path data
|
||||
contour = []
|
||||
command, offs = 'M', []
|
||||
for i in chain(range(s, e + 1), range(b, s + 1)):
|
||||
point, tag = outline.points[i], outline.tags[i]
|
||||
point = (point.x / 64., -point.y / 64.)
|
||||
if _on(tag): # 'on' point
|
||||
contour.append(command)
|
||||
if command is 'Q' and len(offs) >= 2:
|
||||
(x0, y0) = offs[0]
|
||||
for (x1, y1) in offs[1:]:
|
||||
contour += [(x0, y0), ((x0 + x1) / 2, (y0 + y1) / 2), 'Q']
|
||||
x0, y0 = x1, y1
|
||||
contour.append((x0, y0))
|
||||
else: # 'off' point
|
||||
contour += offs
|
||||
contour.append(point)
|
||||
command, offs = 'L', []
|
||||
else:
|
||||
offs.append(point)
|
||||
command = 'C' if _cubic(tag) else 'Q'
|
||||
if contour:
|
||||
contour.append('Z')
|
||||
data += contour
|
||||
b = e + 1
|
||||
|
||||
# bbox
|
||||
bbox = _ft2.BBox()
|
||||
_FT.Outline_Get_BBox(byref(outline), byref(bbox))
|
||||
xmin, xmax = bbox.xMin / 64., bbox.xMax / 64.
|
||||
ymin, ymax = bbox.yMin / 64., bbox.yMax / 64.
|
||||
|
||||
origin = xmin, -ymax
|
||||
size = xmax - xmin, ymax - ymin
|
||||
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
|
||||
return origin, size, offset, data
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Cocoa implementation of get_font"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from CoreText import ( # pylint: disable=import-error
|
||||
CTFontCollectionCreateFromAvailableFonts, # @UnresolvedImport
|
||||
CTFontCollectionCreateMatchingFontDescriptors, # @UnresolvedImport
|
||||
kCTFontFamilyNameAttribute, # @UnresolvedImport
|
||||
kCTFontTraitsAttribute, # @UnresolvedImport
|
||||
kCTFontURLAttribute, # @UnresolvedImport
|
||||
kCTFontSymbolicTrait, # @UnresolvedImport
|
||||
kCTFontItalicTrait, # @UnresolvedImport
|
||||
kCTFontBoldTrait, # @UnresolvedImport
|
||||
)
|
||||
|
||||
|
||||
# fonts ######################################################################
|
||||
|
||||
_font_collection = CTFontCollectionCreateFromAvailableFonts({})
|
||||
_font_descriptors = CTFontCollectionCreateMatchingFontDescriptors(_font_collection)
|
||||
|
||||
_FONTS = {}
|
||||
_FONT_NAMES = {}
|
||||
for _font in _font_descriptors:
|
||||
family = _font[kCTFontFamilyNameAttribute]
|
||||
_fonts = _FONTS.get(family, {})
|
||||
_traits = _font[kCTFontTraitsAttribute]
|
||||
_bold = bool(_traits[kCTFontSymbolicTrait] & kCTFontBoldTrait)
|
||||
_italic = bool(_traits[kCTFontSymbolicTrait] & kCTFontItalicTrait)
|
||||
_font_name = _font[kCTFontURLAttribute].path()
|
||||
_key = _italic, _bold
|
||||
_fonts[_key] = _font_name
|
||||
_names = _FONT_NAMES.get(_font_name, [])
|
||||
_names.append(_key) # TODO: no idea how to retreive fonst index in dfont file
|
||||
_names.sort() # so rely on arbitrary assunption
|
||||
_FONT_NAMES[_font_name] = _names
|
||||
_FONTS[family] = _fonts
|
||||
|
||||
|
||||
# utils ######################################################################
|
||||
|
||||
def _get_font(family, bold, italic):
|
||||
key = italic, bold
|
||||
font_name = _FONTS[family][key]
|
||||
return font_name, _FONT_NAMES[font_name].index(key)
|
|
@ -0,0 +1,304 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""freetype2."""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from ctypes import cdll, c_int, c_short, c_long, c_uint, c_ushort, c_char, c_byte, c_ubyte, c_void_p, Structure, POINTER, CFUNCTYPE, byref
|
||||
from ctypes.util import find_library
|
||||
|
||||
|
||||
# platform libraries #########################################################
|
||||
|
||||
from sys import platform as _platform
|
||||
if _platform == "darwin":
|
||||
# patching find_library to look into X11 libs
|
||||
X11lib = "/usr/X11/lib"
|
||||
from os import environ
|
||||
try:
|
||||
DYLD_LIBRARY_PATH = environ["DYLD_LIBRARY_PATH"]
|
||||
except KeyError:
|
||||
environ["DYLD_LIBRARY_PATH"] = X11lib
|
||||
else:
|
||||
environ["DYLD_LIBRARY_PATH"] = ":".join([DYLD_LIBRARY_PATH,
|
||||
X11lib])
|
||||
|
||||
|
||||
class FreetypeWrapper(object):
|
||||
def __init__(self, ft):
|
||||
self.ft = ft
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.ft, "FT_%s" % name)
|
||||
|
||||
FT = FreetypeWrapper(cdll.LoadLibrary(find_library("freetype")))
|
||||
|
||||
if _platform == "darwin":
|
||||
try:
|
||||
environ["DYLD_LIBRARY_PATH"] = DYLD_LIBRARY_PATH
|
||||
del DYLD_LIBRARY_PATH
|
||||
except NameError:
|
||||
del environ["DYLD_LIBRARY_PATH"]
|
||||
|
||||
|
||||
# types ######################################################################
|
||||
|
||||
Int = c_int
|
||||
Short = c_short
|
||||
Long = c_long
|
||||
UInt = c_uint
|
||||
UShort = c_ushort
|
||||
String = c_char
|
||||
|
||||
Fixed = c_long
|
||||
Pos = c_long
|
||||
|
||||
|
||||
class Vector(Structure):
|
||||
_fields_ = [("x", Pos),
|
||||
("y", Pos)]
|
||||
|
||||
|
||||
class Matrix(Structure):
|
||||
_fields_ = [("xx", Fixed),
|
||||
("xy", Fixed),
|
||||
("yx", Fixed),
|
||||
("yy", Fixed)]
|
||||
|
||||
|
||||
class Bitmap(Structure):
|
||||
_fields_ = [("rows", c_int),
|
||||
("width", c_int),
|
||||
("pitch", c_int),
|
||||
("buffer", POINTER(c_ubyte)), # c_void_p),
|
||||
("num_grays", c_short),
|
||||
("pixel_mode", c_char),
|
||||
("palette_mode", c_char),
|
||||
("palette", c_void_p)]
|
||||
|
||||
|
||||
class Outline(Structure):
|
||||
_fields_ = [("n_contours", c_short),
|
||||
("n_points", c_short),
|
||||
("points", POINTER(Vector)),
|
||||
("tags", POINTER(c_byte)),
|
||||
("contours", POINTER(c_short)),
|
||||
("flags", c_int)]
|
||||
|
||||
|
||||
class Size_Metrics(Structure):
|
||||
_fields_ = [("x_ppem", UShort),
|
||||
("y_ppem", UShort),
|
||||
("x_scale", Fixed),
|
||||
("y_scale", Fixed),
|
||||
("ascender", Pos),
|
||||
("descender", Pos),
|
||||
("height", Pos),
|
||||
("max_advance", Pos)]
|
||||
|
||||
|
||||
class Bitmap_Size(Structure):
|
||||
_fields_ = [("height", Short),
|
||||
("width", Short),
|
||||
("size", Pos),
|
||||
("x_ppem", Pos),
|
||||
("y_ppem", Pos)]
|
||||
|
||||
|
||||
class BBox(Structure):
|
||||
_fields_ = [("xMin", Pos),
|
||||
("yMin", Pos),
|
||||
("xMax", Pos),
|
||||
("yMax", Pos)]
|
||||
|
||||
|
||||
class Glyph_Metrics(Structure):
|
||||
_fields_ = [("width", Pos),
|
||||
("height", Pos),
|
||||
("horiBearingX", Pos),
|
||||
("horiBearingY", Pos),
|
||||
("horiAdvance", Pos),
|
||||
("vertBearingX", Pos),
|
||||
("vertBearingY", Pos),
|
||||
("vertAdvance", Pos)]
|
||||
|
||||
|
||||
Generic_Finalizer = CFUNCTYPE(c_void_p, c_void_p)
|
||||
|
||||
|
||||
class Generic(Structure):
|
||||
_fields_ = [("data", c_void_p),
|
||||
("finalizer", Generic_Finalizer)]
|
||||
|
||||
|
||||
class Glyph_Format(c_int):
|
||||
def __repr__(self):
|
||||
v = self.value
|
||||
return "".join(chr((v >> 8 * i) & 255) for i in reversed(range(4)))
|
||||
|
||||
Encoding = c_int # enum
|
||||
|
||||
SubGlyph = c_void_p # POINTER(SubGlyphRec)
|
||||
Slot_Internal = c_void_p # POINTER(Slot_InternalRec)
|
||||
Size_Internal = c_void_p # POINTER(Size_InternalRec)
|
||||
|
||||
|
||||
class CharMapRec(Structure):
|
||||
pass
|
||||
CharMap = POINTER(CharMapRec)
|
||||
|
||||
|
||||
class GlyphSlotRec(Structure):
|
||||
pass
|
||||
GlyphSlot = POINTER(GlyphSlotRec)
|
||||
|
||||
|
||||
class SizeRec(Structure):
|
||||
pass
|
||||
Size = POINTER(SizeRec)
|
||||
|
||||
|
||||
class FaceRec(Structure):
|
||||
pass
|
||||
Face = POINTER(FaceRec)
|
||||
|
||||
|
||||
Library = c_void_p
|
||||
|
||||
|
||||
CharMapRec._fields_ = [
|
||||
("face", Face),
|
||||
("encoding", Encoding),
|
||||
("platform_id", UShort),
|
||||
("encoding_id", UShort)
|
||||
]
|
||||
|
||||
GlyphSlotRec._fields_ = [
|
||||
("library", Library),
|
||||
("face", Face),
|
||||
("next", GlyphSlot),
|
||||
("reserved", UInt),
|
||||
("generic", Generic),
|
||||
("metrics", Glyph_Metrics),
|
||||
("linearHoriAdvance", Fixed),
|
||||
("linearVertAdvance", Fixed),
|
||||
("advance", Vector),
|
||||
("format", Glyph_Format),
|
||||
("bitmap", Bitmap),
|
||||
("bitmap_left", Int),
|
||||
("bitmap_top", Int),
|
||||
("outline", Outline),
|
||||
("num_subglyphs", UInt),
|
||||
("subglyphs", SubGlyph),
|
||||
("control_data", c_void_p),
|
||||
("control_len", c_long),
|
||||
("lsb_delta", Pos),
|
||||
("rsb_delta", Pos),
|
||||
("other", c_void_p),
|
||||
("internal", Slot_Internal),
|
||||
]
|
||||
|
||||
SizeRec._fields_ = [
|
||||
("face", Face),
|
||||
("generic", Generic),
|
||||
("metrics", Size_Metrics),
|
||||
("internal", Size_Internal),
|
||||
]
|
||||
|
||||
FaceRec._fields_ = [
|
||||
("num_faces", Long),
|
||||
("face_index", Long),
|
||||
("face_flags", Long),
|
||||
("style_flags", Long),
|
||||
("num_glyphs", Long),
|
||||
("family_name", POINTER(String)),
|
||||
("style_name", POINTER(String)),
|
||||
("num_fixed_sizes", Int),
|
||||
("available_sizes", POINTER(Bitmap_Size)),
|
||||
("num_charmaps", Int),
|
||||
("charmaps", POINTER(CharMap)),
|
||||
("generic", Generic),
|
||||
("bbox", BBox),
|
||||
("unit_per_EM", UShort),
|
||||
("ascender", Short),
|
||||
("descender", Short),
|
||||
("height", Short),
|
||||
("max_advance_width", Short),
|
||||
("max_advance_height", Short),
|
||||
("underline_position", Short),
|
||||
("underline_thickness", Short),
|
||||
("glyph", GlyphSlot),
|
||||
("size", Size),
|
||||
("charmap", CharMap),
|
||||
]
|
||||
|
||||
|
||||
# constants ##################################################################
|
||||
|
||||
LOAD_DEFAULT = 0x0 # @IgnorePep8
|
||||
LOAD_NO_SCALE = 0x1 # @IgnorePep8
|
||||
LOAD_NO_HINTING = 0x2 # @IgnorePep8
|
||||
LOAD_RENDER = 0x4 # @IgnorePep8
|
||||
LOAD_NO_BITMAP = 0x8 # @IgnorePep8
|
||||
LOAD_VERTICAL_LAYOUT = 0x10 # @IgnorePep8
|
||||
LOAD_FORCE_AUTOHINT = 0x20 # @IgnorePep8
|
||||
LOAD_CROP_BITMAP = 0x40 # @IgnorePep8
|
||||
LOAD_PEDANTIC = 0x80 # @IgnorePep8
|
||||
LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 0x200
|
||||
LOAD_NO_RECURSE = 0x400 # @IgnorePep8
|
||||
LOAD_IGNORE_TRANSFORM = 0x800 # @IgnorePep8
|
||||
LOAD_MONOCHROME = 0x1000 # @IgnorePep8
|
||||
LOAD_LINEAR_DESIGN = 0x2000 # @IgnorePep8
|
||||
|
||||
[
|
||||
PIXEL_MODE_NONE,
|
||||
PIXEL_MODE_MONO,
|
||||
PIXEL_MODE_GRAY,
|
||||
PIXEL_MODE_GRAY2,
|
||||
PIXEL_MODE_GRAY4,
|
||||
PIXEL_MODE_LCD,
|
||||
PIXEL_MODE_LCD_V,
|
||||
PIXEL_MODE_MAX
|
||||
] = [bytes([i]) for i in range(8)]
|
||||
|
||||
[
|
||||
RENDER_MODE_NORMAL,
|
||||
RENDER_MODE_LIGHT,
|
||||
RENDER_MODE_MONO,
|
||||
RENDER_MODE_LCD,
|
||||
RENDER_MODE_LCD_V,
|
||||
RENDER_MODE_MAX
|
||||
] = range(6)
|
||||
|
||||
[
|
||||
KERNING_DEFAULT,
|
||||
KERNING_UNFITTED,
|
||||
KERNING_UNSCALED,
|
||||
] = range(3)
|
||||
|
||||
|
||||
def LOAD_TARGET_(x):
|
||||
return ((x) & 15) << 16
|
||||
|
||||
LOAD_TARGET_NORMAL = LOAD_TARGET_(RENDER_MODE_NORMAL ) # @IgnorePep8
|
||||
LOAD_TARGET_LIGHT = LOAD_TARGET_(RENDER_MODE_LIGHT ) # @IgnorePep8
|
||||
LOAD_TARGET_MONO = LOAD_TARGET_(RENDER_MODE_MONO ) # @IgnorePep8
|
||||
LOAD_TARGET_LCD = LOAD_TARGET_(RENDER_MODE_LCD ) # @IgnorePep8
|
||||
LOAD_TARGET_LCD_V = LOAD_TARGET_(RENDER_MODE_LCD_V ) # @IgnorePep8
|
||||
|
||||
|
||||
def ENC_TAG(s):
|
||||
a, b, c, d = s
|
||||
return (ord(a) << 24 |
|
||||
ord(b) << 16 |
|
||||
ord(c) << 8 | # @IgnorePep8
|
||||
ord(d))
|
||||
|
||||
ENCODING_UNICODE = ENC_TAG("unic")
|
||||
|
||||
|
||||
# initialisation #############################################################
|
||||
|
||||
_library = Library()
|
||||
FT.Init_FreeType(byref(_library))
|
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
import os
|
||||
|
||||
from sys import platform as _platform
|
||||
|
||||
if _platform == "darwin":
|
||||
try:
|
||||
from ._cocoa import _get_font
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
_get_font
|
||||
except NameError:
|
||||
def _get_font(family, bold, italic): # pylint: disable=function-redefined
|
||||
raise LookupError
|
||||
|
||||
|
||||
# constants ##################################################################
|
||||
|
||||
_CONTRIBS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
"..", "..",
|
||||
"contribs", "freefont-20120503"))
|
||||
|
||||
_FALLBACK_FONTS = {
|
||||
"sans-serif": {
|
||||
(False, False): os.path.join(_CONTRIBS_PATH, "FreeSans.otf"),
|
||||
(False, True): os.path.join(_CONTRIBS_PATH, "FreeSansOblique.otf"),
|
||||
(True, False): os.path.join(_CONTRIBS_PATH, "FreeSansBold.otf"),
|
||||
(True, True): os.path.join(_CONTRIBS_PATH, "FreeSansBoldOblique.otf"),
|
||||
},
|
||||
"serif": {
|
||||
(False, False): os.path.join(_CONTRIBS_PATH, "FreeSerif.otf"),
|
||||
(False, True): os.path.join(_CONTRIBS_PATH, "FreeSerifItalic.otf"),
|
||||
(True, False): os.path.join(_CONTRIBS_PATH, "FreeSerifBold.otf"),
|
||||
(True, True): os.path.join(_CONTRIBS_PATH, "FreeSerifBoldItalic.otf"),
|
||||
},
|
||||
"mono": {
|
||||
(False, False): os.path.join(_CONTRIBS_PATH, "FreeMono.otf"),
|
||||
(False, True): os.path.join(_CONTRIBS_PATH, "FreeMonoOblique.otf"),
|
||||
(True, False): os.path.join(_CONTRIBS_PATH, "FreeMonoBold.otf"),
|
||||
(True, True): os.path.join(_CONTRIBS_PATH, "FreeMonoBoldOblique.otf"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# font lookup ################################################################
|
||||
|
||||
|
||||
def _get_fallback_font(family, bold=False, italic=False):
|
||||
return _FALLBACK_FONTS[family][bold, italic], 0
|
||||
|
||||
|
||||
def get_font(families, weight="normal", style="normal"):
|
||||
bold = weight in ["bold", "bolder", "600", "700", "800", "900"]
|
||||
italic = style in ["italic", "oblique"]
|
||||
families = [family.strip() for family in families.split(',')] + ["sans-serif"]
|
||||
font_name, index = None, 0
|
||||
for font_getter in [_get_font, _get_fallback_font]:
|
||||
for family in families:
|
||||
try:
|
||||
font_name, index = font_getter(family, bold, italic)
|
||||
except LookupError:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
return font_name, index
|
||||
|
||||
|
||||
__all__ = [
|
||||
"get_font",
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""seagull.scenegraph module"""
|
||||
|
||||
from .paint import Color, LinearGradient, RadialGradient, Pattern
|
||||
from .transform import Translate, Scale, Rotate, SkewX, SkewY, Matrix
|
||||
from .element import (Use, Group, Path,
|
||||
Rectangle, Circle, Ellipse,
|
||||
Line, Polyline, Polygon,
|
||||
Text, Image)
|
|
@ -0,0 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
misc utility functions and classes
|
||||
"""
|
||||
|
||||
|
||||
# utils ######################################################################
|
||||
|
||||
def _indent(s, level=1, tab="\t"):
|
||||
"""indent blocks"""
|
||||
indent = tab * level
|
||||
return "\n".join("%s%s" % (indent, line) for line in s.split("\n"))
|
||||
|
||||
|
||||
def _u(v, encoding="utf8"):
|
||||
"""provides a unicode string from anything."""
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
elif isinstance(v, (list, tuple)):
|
||||
return " ".join(_u(vi, encoding) for vi in v)
|
||||
elif v is None:
|
||||
return "none"
|
||||
else:
|
||||
return str(v)
|
||||
|
||||
|
||||
# base classes ###############################################################
|
||||
|
||||
class _Base(object):
|
||||
"""equality based on state rather than id"""
|
||||
|
||||
_state_attributes = []
|
||||
|
||||
def _state(self):
|
||||
return {name: getattr(self, name)
|
||||
for name in self._state_attributes}
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return other._state() == self._state()
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
# def __hash__(self): return hash(self._state())
|
||||
|
||||
def __hash__(self):
|
||||
raise RuntimeError("state is not hashable")
|
||||
|
||||
|
||||
class _Element(_Base):
|
||||
"""element with xml serialization support"""
|
||||
|
||||
_state_attributes = ["tag"]
|
||||
attributes = []
|
||||
|
||||
def __init__(self):
|
||||
super(_Element, self).__init__()
|
||||
self.id = None
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
"""the svg tag that represents this element
|
||||
"""
|
||||
raise NotImplementedError # this tag is suppsed to be defined in derived classes
|
||||
|
||||
def _xml(self, defs):
|
||||
"""xml serialization"""
|
||||
u = "<%s %s" % (self.tag, self._xml_attributes(defs))
|
||||
content = self._xml_content(defs)
|
||||
if content.strip():
|
||||
u += ">\n" + \
|
||||
_indent(content) + "\n" + \
|
||||
"</%s>" % self.tag
|
||||
else:
|
||||
u += "/>"
|
||||
return u
|
||||
|
||||
def _xml_content(self, defs):
|
||||
"""xml serialization of content"""
|
||||
return ""
|
||||
|
||||
def _xml_attributes(self, defs):
|
||||
"""xml serialization of attributes"""
|
||||
return " ".join(self._xml_attribute(name, defs) for name in self.attributes)
|
||||
|
||||
def _xml_attribute(self, name, defs):
|
||||
"""unicode serialization of attribute/value pair"""
|
||||
attribute = getattr(self, name)
|
||||
if name == "href":
|
||||
name = "xlink:href"
|
||||
try:
|
||||
href = "#%s" % attribute.id
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
defs.append(attribute)
|
||||
attribute = href
|
||||
try:
|
||||
u = attribute._xml_attr(defs)
|
||||
except AttributeError:
|
||||
u = _u(attribute)
|
||||
return "%s='%s'" % (name.replace("_", "-"), u) if u else ""
|
||||
|
||||
def _xml_attr(self, defs):
|
||||
defs.append(self)
|
||||
return "url(#%s)" % self.id
|
|
@ -0,0 +1,294 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element
|
||||
"""
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from weakref import WeakValueDictionary as _weakdict
|
||||
|
||||
# from ...opengl.utils import OffscreenContext
|
||||
from .._common import _Element
|
||||
from ..paint import Color, _Texture, _MaskContext
|
||||
from ..transform import Matrix, Translate, stretch, product
|
||||
|
||||
|
||||
# element ####################################################################
|
||||
|
||||
_elements_by_id = _weakdict()
|
||||
|
||||
|
||||
def _id(element):
|
||||
element_id = getattr(element, "_id", "%X" % id(element))
|
||||
_elements_by_id[element_id] = element
|
||||
return element_id
|
||||
|
||||
|
||||
def get_element_by_id(element_id):
|
||||
return _elements_by_id[element_id]
|
||||
|
||||
"""
|
||||
List of all possible attributes for svg nodes
|
||||
"""
|
||||
_ATTRIBUTES = [
|
||||
"id", "href",
|
||||
"x", "y",
|
||||
"width", "height",
|
||||
"r", "rx", "ry",
|
||||
"cx", "cy",
|
||||
"points",
|
||||
"x1", "x2", "y1", "y2",
|
||||
"opacity", "color",
|
||||
"fill", "fill_opacity", "fill_rule",
|
||||
"stroke", "stroke_opacity", "stroke_width",
|
||||
"stroke_linecap", "stroke_linejoin", "stroke_miterlimit",
|
||||
"stroke_dasharray", "stroke_dashoffset",
|
||||
"font_family", "font_weight", "font_style", "font_size",
|
||||
"text_anchor",
|
||||
"transform",
|
||||
"clip_path",
|
||||
"mask",
|
||||
"d",
|
||||
]
|
||||
|
||||
_INHERITEDS = {
|
||||
"color": Color.black,
|
||||
"fill": Color.black,
|
||||
"fill_opacity": 1.,
|
||||
"fill_rule": 'nonzero',
|
||||
"stroke": Color.none,
|
||||
"stroke_opacity": 1.,
|
||||
"stroke_width": 1,
|
||||
"stroke_linecap": 'butt',
|
||||
"stroke_linejoin": 'miter',
|
||||
"stroke_miterlimit": 4.,
|
||||
"stroke_dasharray": None,
|
||||
"stroke_dashoffset": 0.,
|
||||
"font_family": 'sans-serif',
|
||||
"font_weight": 'normal',
|
||||
"font_style": 'normal',
|
||||
"font_size": 10,
|
||||
"text_anchor": 'start',
|
||||
}
|
||||
|
||||
|
||||
class Element(_Element):
|
||||
""" A node in the svg tree
|
||||
"""
|
||||
x, y = 0, 0
|
||||
transform = None
|
||||
|
||||
opacity = 1.
|
||||
clip_path = None
|
||||
mask = None
|
||||
|
||||
_state_attributes = _Element._state_attributes + list(_INHERITEDS) + [
|
||||
"x", "y", "transform",
|
||||
"opacity", "clip_path", "mask"
|
||||
]
|
||||
|
||||
def __init__(self, **attributes):
|
||||
"""
|
||||
:param attributes: svg attributes (eg for a circle element : { 'cx':'5.0', 'cy':'10.0', 'r':'6.0', 'transform':'translate(30,40) rotate(45)' })
|
||||
"""
|
||||
self._attributes = set()
|
||||
self._inheriteds = _INHERITEDS
|
||||
for attribute in attributes:
|
||||
setattr(self, attribute, attributes[attribute])
|
||||
# the transform attribute contains a list of transformations associated to this Element. It doesn't take into account its parents transforms.
|
||||
if self.transform is None:
|
||||
# empty list of transforms if the transform svg attribute is not present in the svg node
|
||||
self.transform = []
|
||||
self._parent = None # the svg group containing this element
|
||||
|
||||
def __setattr__(self, attribute, value):
|
||||
if attribute in _ATTRIBUTES:
|
||||
self._attributes.add(attribute)
|
||||
super(Element, self).__setattr__(attribute, value)
|
||||
|
||||
def __delattr__(self, attribute):
|
||||
super(Element, self).__delattr__(attribute)
|
||||
if attribute in _ATTRIBUTES:
|
||||
self._attributes.remove(attribute)
|
||||
|
||||
def __getattr__(self, attribute):
|
||||
if attribute in _INHERITEDS:
|
||||
return self._inheriteds[attribute]
|
||||
try:
|
||||
return super(Element, self).__getattr__(attribute)
|
||||
except AttributeError:
|
||||
return super(Element, self).__getattribute__(attribute)
|
||||
|
||||
def _inherit(self, inheriteds):
|
||||
self._inheriteds = inheriteds
|
||||
return {attr: getattr(self, attr) for attr in _INHERITEDS}
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
self._attributes.add("id")
|
||||
return _id(self)
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return (name for name in _ATTRIBUTES if name in self._attributes)
|
||||
|
||||
def __hash__(self):
|
||||
return id(self) # hash((self.name, self.location))
|
||||
|
||||
def __eq__(self, other):
|
||||
return id(self) == id(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
# Not strictly necessary, but to avoid having both x==y and x!=y
|
||||
# True at the same time
|
||||
return not(self == other)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
"""
|
||||
:rtype: scenegraph.Element
|
||||
"""
|
||||
return self._parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent_group):
|
||||
self._parent = parent_group
|
||||
|
||||
# transformations
|
||||
|
||||
def local_to_parent_matrix(self):
|
||||
"""returns the matrix that converts coordinates in this node's space to its parent's space
|
||||
|
||||
:rtype: scenegraph.Matrix
|
||||
"""
|
||||
return product(*self.transform + [Translate(self.x, self.y)])
|
||||
|
||||
def parent_to_world_matrix(self):
|
||||
"""returns the matrix that converts coordinates in this node's parent's space to the world space
|
||||
|
||||
:rtype: scenegraph.Matrix
|
||||
"""
|
||||
if self.parent is None:
|
||||
return Matrix()
|
||||
else:
|
||||
return self.parent.parent_to_world_matrix() * self.parent.local_to_parent_matrix() # pylint: disable=no-member
|
||||
|
||||
def local_to_world_matrix(self):
|
||||
"""returns the matrix that converts coordinates in this node's space to the world space
|
||||
|
||||
:rtype: scenegraph.Matrix
|
||||
"""
|
||||
return self.parent_to_world_matrix() * self.local_to_parent_matrix() # pylint: disable=no-member
|
||||
|
||||
# axis-aligned bounding box
|
||||
|
||||
def aabbox(self, transform=Matrix(), inheriteds=_INHERITEDS):
|
||||
"""returns the axis-aligned bounding box of this xml element
|
||||
"""
|
||||
inheriteds = self._inherit(inheriteds)
|
||||
return self._aabbox(transform * self.local_to_parent_matrix(), inheriteds)
|
||||
|
||||
def _aabbox(self, transform, inheriteds):
|
||||
raise NotImplementedError
|
||||
|
||||
def _units(self, elem, attr, default="userSpaceOnUse"):
|
||||
units = getattr(elem, attr, default)
|
||||
if units == "userSpaceOnUse":
|
||||
transform = Matrix()
|
||||
elif units == "objectBoundingBox":
|
||||
(x_min, y_min), (x_max, y_max) = self.aabbox()
|
||||
transform = stretch(x_min, y_min, x_max - x_min, y_max - y_min)
|
||||
else:
|
||||
raise ValueError("unknown units %s" % units)
|
||||
return product(*self.transform) * transform
|
||||
|
||||
# rendering
|
||||
|
||||
def _color(self, color):
|
||||
if color == Color.current:
|
||||
return self.color
|
||||
return color
|
||||
|
||||
def render(self, transform=Matrix(), inheriteds=_INHERITEDS, context=None,
|
||||
clipping=True, masking=True, opacity=True):
|
||||
inheriteds = self._inherit(inheriteds)
|
||||
if context is None:
|
||||
# context = OffscreenContext()
|
||||
assert False
|
||||
|
||||
if (clipping and self.clip_path) or (masking and self.mask):
|
||||
if clipping and self.clip_path:
|
||||
clipping = False
|
||||
mask, units = self.clip_path, "clipPathUnits"
|
||||
else:
|
||||
masking = False
|
||||
mask, units = self.mask, "maskContentUnits"
|
||||
|
||||
mask_transform = self._units(mask, units)
|
||||
with context(mask.aabbox(transform * mask_transform),
|
||||
(0., 0., 0., 0.)) as ((x, y), (width, height),
|
||||
mask_texture_id):
|
||||
if not mask_texture_id:
|
||||
return
|
||||
mask.render(transform * mask_transform, context=context)
|
||||
|
||||
with _MaskContext((x, y), (width, height), mask_texture_id):
|
||||
self.render(transform, inheriteds, context,
|
||||
clipping=clipping, masking=masking, opacity=opacity)
|
||||
|
||||
elif opacity and self.opacity < 1.:
|
||||
with context(self.aabbox(transform, inheriteds)) as \
|
||||
((x, y), (width, height), elem_texture_id):
|
||||
if not elem_texture_id:
|
||||
return
|
||||
self.render(transform, inheriteds, context,
|
||||
clipping=clipping, masking=masking, opacity=False)
|
||||
|
||||
Rectangle(x=x, y=y, width=width, height=height,
|
||||
fill=_Texture(elem_texture_id),
|
||||
fill_opacity=self.opacity).render(context=context)
|
||||
|
||||
else:
|
||||
self._render(transform * self.local_to_parent_matrix(), inheriteds, context)
|
||||
|
||||
def _render(self, transform, inheriteds, context):
|
||||
raise NotImplementedError
|
||||
|
||||
# picking
|
||||
|
||||
def _hit_test(self, x, y, transform):
|
||||
"""tests if the position (x,y) collides with this shape (not its children)
|
||||
"""
|
||||
return []
|
||||
|
||||
def pick(self, x=0, y=0, parent_to_world=Matrix()):
|
||||
"""
|
||||
returns the list of svg nodes that are hit when picking at position x,y
|
||||
|
||||
:param parent_to_world: the transformation matrix that converts coordinates from this element's parent space to the scene's world space (the space in which x and y coordinates are expressed)
|
||||
"""
|
||||
parent_to_world = parent_to_world * self.local_to_parent_matrix()
|
||||
hits = self._hit_test(x, y, parent_to_world)
|
||||
hits += [([self] + e, p) for e, p in self._pick_content(x, y, parent_to_world)]
|
||||
return hits
|
||||
|
||||
def _pick_content(self, x, y, transform):
|
||||
"""tests if the position (x,y) collides with the children shapes of this shape
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
# elements ###################################################################
|
||||
|
||||
from .use import Use # @IgnorePep8
|
||||
from .group import Group # @IgnorePep8
|
||||
from .rectangle import Rectangle # @IgnorePep8
|
||||
from .circle import Circle # @IgnorePep8
|
||||
from .ellipse import Ellipse # @IgnorePep8
|
||||
from .line import Line # @IgnorePep8
|
||||
from .polyline import Polyline # @IgnorePep8
|
||||
from .polygon import Polygon # @IgnorePep8
|
||||
from .path import Path # @IgnorePep8
|
||||
from .text import Text # @IgnorePep8
|
||||
from .image import Image # @IgnorePep8
|
|
@ -0,0 +1,361 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element._path
|
||||
|
||||
Low level path utility functions suitable for optimizations based on typing.
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from math import hypot, sqrt, pi, cos, sin, atan2, radians
|
||||
|
||||
|
||||
# constants ##################################################################
|
||||
|
||||
INF = float("inf")
|
||||
|
||||
|
||||
# geometry ###################################################################
|
||||
|
||||
|
||||
def _line(p0, p1):
|
||||
"""equation of (p0, p1) in the ax+by+c=0 form."""
|
||||
(x0, y0), (x1, y1) = p0, p1
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
return dy, -dx, y0 * dx - x0 * dy
|
||||
|
||||
|
||||
def _intersection(l0, l1, e=1e-6):
|
||||
"""intersection of lines."""
|
||||
a0, b0, c0 = l0
|
||||
a1, b1, c1 = l1
|
||||
w = a0 * b1 - a1 * b0
|
||||
x = b0 * c1 - b1 * c0
|
||||
y = c0 * a1 - c1 * a0
|
||||
if abs(w) < e:
|
||||
raise ZeroDivisionError
|
||||
return x / w, y / w
|
||||
|
||||
|
||||
def _parallel(l, p):
|
||||
"""parallel to l passing by p."""
|
||||
a, b, c = l # @UnusedVariable
|
||||
x, y = p
|
||||
return a, b, -(a * x + b * y)
|
||||
|
||||
|
||||
def _h(p0, p1):
|
||||
"""distance between two points."""
|
||||
(x0, y0), (x1, y1) = p0, p1
|
||||
return hypot(x1 - x0, y1 - y0)
|
||||
|
||||
|
||||
def _lerp(p0, p1, t=.5):
|
||||
(x0, y0), (x1, y1) = p0, p1
|
||||
return x0 + t * (x1 - x0), y0 + t * (y1 - y0)
|
||||
|
||||
|
||||
# flattening #################################################################
|
||||
|
||||
# Bézier splines
|
||||
|
||||
_L2_RATIO = 4 # trade-off precision for polygons
|
||||
|
||||
|
||||
def _casteljau(p0, p1, p2, p3, t=.5):
|
||||
"""de Casteljau subdivision of cubic Bézier curve."""
|
||||
p01, p12, p23 = _lerp(p0, p1, t), _lerp(p1, p2, t), _lerp(p2, p3, t)
|
||||
p012, p123 = _lerp(p01, p12, t), _lerp(p12, p23, t)
|
||||
p0123 = _lerp(p012, p123, t)
|
||||
return p01, p12, p23, p012, p123, p0123
|
||||
|
||||
|
||||
def _cubic(p0, p1, p2, p3, du2):
|
||||
"""cubic Bézier spline flattenization."""
|
||||
if (p0, p2) == (p1, p3):
|
||||
return [p3]
|
||||
|
||||
(x0, y0), (x1, y1), (x2, y2), (x3, y3) = p0, p1, p2, p3
|
||||
d1 = (x3 - x0) * (y1 - y0) - (y3 - y0) * (x1 - x0)
|
||||
d2 = (x3 - x0) * (y2 - y0) - (y3 - y0) * (x2 - x0)
|
||||
dd03 = (x3 - x0) * (x3 - x0) + (y3 - y0) * (y3 - y0)
|
||||
if (d1 * d1 + d2 * d2) * du2 < dd03 * _L2_RATIO:
|
||||
return [_lerp(p1, p2), p3]
|
||||
else:
|
||||
p01, p12, p23, p012, p123, p0123 = _casteljau(p0, p1, p2, p3) # @UnusedVariable
|
||||
return _cubic(p0, p01, p012, p0123, du2) + \
|
||||
_cubic(p0123, p123, p23, p3, du2)
|
||||
|
||||
|
||||
def _quadric(p0, p1, p2, du2):
|
||||
"""quadric Bézier spline flattenization by transforming it to cubic."""
|
||||
return _cubic(p0, _lerp(p0, p1, 2 / 3.), _lerp(p1, p2, 1 / 3.), p2, du2)
|
||||
|
||||
|
||||
# arc
|
||||
|
||||
def _arc(p0, rs, phi, flags, p1, du2):
|
||||
"""arc flatenization.
|
||||
|
||||
implementation derived from
|
||||
<http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes>
|
||||
"""
|
||||
if p0 == p1:
|
||||
return []
|
||||
|
||||
rx, ry = rs
|
||||
if rx == 0 or ry == 0:
|
||||
return [p1]
|
||||
rx, ry = abs(rx), abs(ry)
|
||||
|
||||
phi = radians(phi) % pi
|
||||
c, s = cos(phi), sin(phi)
|
||||
|
||||
large_arc, sweep = map(bool, flags)
|
||||
|
||||
(x0, y0), (x1, y1) = p0, p1
|
||||
|
||||
ux, uy = .5 * (x0 - x1), .5 * (y0 - y1)
|
||||
X, Y = c * ux + s * uy, -s * ux + c * uy
|
||||
|
||||
X2, Y2, r2x, r2y = X * X, Y * Y, rx * rx, ry * ry
|
||||
L2 = X2 / r2x + Y2 / r2y
|
||||
|
||||
if L2 > 1.:
|
||||
L = sqrt(L2)
|
||||
rx, ry = L * rx, L * ry
|
||||
r2x, r2y = L2 * r2x, L2 * r2y
|
||||
|
||||
K = sqrt(max(0., (r2x * r2y - r2x * Y2 - r2y * X2) / (r2x * Y2 + r2y * X2)))
|
||||
if large_arc == sweep:
|
||||
K = -K
|
||||
Xc, Yc = K * Y * rx / ry, -K * X * ry / rx
|
||||
|
||||
a0 = atan2(-(Yc - Y) / ry, -(Xc - X) / rx)
|
||||
da = atan2(-(Yc + Y) / ry, -(Xc + X) / rx) - a0
|
||||
if sweep:
|
||||
if da < 0:
|
||||
da += 2 * pi
|
||||
else:
|
||||
if da > 0:
|
||||
da -= 2 * pi
|
||||
|
||||
path = []
|
||||
xc, yc = c * Xc - s * Yc + ux + x1, s * Xc + c * Yc + uy + y1
|
||||
N = int((((r2x + r2y) * du2) ** .25) * abs(da))
|
||||
for i in range(N - 1):
|
||||
a = a0 + da * (i + 1) / N
|
||||
X, Y = rx * cos(a), ry * sin(a)
|
||||
path.append((c * X - s * Y + xc, s * X + c * Y + yc))
|
||||
path.append(p1) # i in range(N) introduce numerical errors for p1
|
||||
|
||||
return path
|
||||
|
||||
|
||||
# stroking ###################################################################
|
||||
|
||||
# caps
|
||||
|
||||
def _offset(p0, p1, hw):
|
||||
if p0 == p1:
|
||||
return 0., hw
|
||||
(x0, y0), (x1, y1) = p0, p1
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
w = hw / hypot(dx, dy)
|
||||
return dy * w, -dx * w
|
||||
|
||||
|
||||
def _caps_butt(p0, p1, hw, du=1, start=True):
|
||||
"""compute butt cap of width 2*hw for [p0,p1]."""
|
||||
aw, bw = _offset(p0, p1, hw)
|
||||
if start:
|
||||
x, y = p0
|
||||
else:
|
||||
x, y = p1
|
||||
return [(x + aw, y + bw), (x - aw, y - bw)]
|
||||
|
||||
|
||||
def _caps_square(p0, p1, hw, du=1, start=True):
|
||||
"""compute square cap of width 2*hw for [p0,p1]."""
|
||||
aw, bw = _offset(p0, p1, hw)
|
||||
if start:
|
||||
x, y = p0
|
||||
return [(x + aw + bw, y + bw - aw), (x - aw + bw, y - bw - aw),
|
||||
(x + aw, y + bw), (x - aw, y - bw)]
|
||||
else:
|
||||
x, y = p1
|
||||
return [(x + aw, y + bw), (x - aw, y - bw),
|
||||
(x + aw - bw, y + bw + aw), (x - aw - bw, y - bw + aw)]
|
||||
|
||||
|
||||
def _caps_round(p0, p1, hw, du=1, start=True):
|
||||
"""compute round cap of width 2*hw for [p0,p1]."""
|
||||
aw, bw = _offset(p0, p1, hw)
|
||||
n = int(sqrt(hw * du)) + 1 # 1/(du*hw) ~ 1 - cos(da/2) ~ daˆ2/8 at first order
|
||||
da = pi / (2 * n + 1)
|
||||
if start:
|
||||
x, y = p0
|
||||
n0 = n
|
||||
else:
|
||||
x, y = p1
|
||||
n0 = 0
|
||||
r = []
|
||||
for i in range(n + 1):
|
||||
a = (n0 - i) * da
|
||||
c, s = cos(a), sin(a)
|
||||
r += [(x + c * aw + s * bw, y + c * bw - s * aw), (x - c * aw + s * bw, y - c * bw - s * aw)]
|
||||
return r
|
||||
|
||||
# join
|
||||
|
||||
|
||||
def _join_miter(p0, p1, p2, hw, du, miterlimit):
|
||||
p0a, p0b, p1a, p1b = _join_bevel(p0, p1, p2, hw, du, miterlimit)
|
||||
l0, l1 = _line(p0, p1), _line(p1, p2)
|
||||
try:
|
||||
pa = _intersection(_parallel(l0, p0a), _parallel(l1, p1a))
|
||||
pb = _intersection(_parallel(l0, p0b), _parallel(l1, p1b))
|
||||
except ZeroDivisionError:
|
||||
return [p1a, p1b]
|
||||
r = miterlimit * hw / _h(p1a, pa)
|
||||
if r < 1.:
|
||||
return [_lerp(p0a, pa, r), _lerp(p0b, pb, r),
|
||||
_lerp(p1a, pa, r), _lerp(p1b, pb, r)]
|
||||
# return [p0a, p0b, p1a, p1b]
|
||||
else:
|
||||
return [pa, pb]
|
||||
|
||||
|
||||
def _join_bevel(p0, p1, p2, hw, du, miterlimit):
|
||||
return _caps_butt(p0, p1, hw, start=False) + \
|
||||
_caps_butt(p1, p2, hw)
|
||||
|
||||
|
||||
def _join_round(p0, p1, p2, hw, du, miterlimit):
|
||||
return _caps_butt(p0, p1, hw, du, start=False) + \
|
||||
_caps_round(p1, p2, hw, du)
|
||||
|
||||
# stroke
|
||||
|
||||
_caps = {
|
||||
'butt': _caps_butt,
|
||||
'square': _caps_square,
|
||||
'round': _caps_round,
|
||||
}
|
||||
|
||||
_joins = {
|
||||
'miter': _join_miter,
|
||||
'bevel': _join_bevel,
|
||||
'round': _join_round,
|
||||
}
|
||||
|
||||
|
||||
def _enumerate_unique(path):
|
||||
previous = None
|
||||
for i, p in enumerate(path):
|
||||
if p != previous:
|
||||
yield i, p
|
||||
previous = p
|
||||
|
||||
|
||||
def _stroke(path, closed, joins, width, du=1.,
|
||||
cap='butt', join='miter', miterlimit=4.):
|
||||
"""compute a stroke from discretized path."""
|
||||
hw = width / 2.
|
||||
_cap = _caps[cap]
|
||||
_join = _joins[join]
|
||||
stroke = []
|
||||
|
||||
path_points = _enumerate_unique(path)
|
||||
(i0, p0) = next(path_points)
|
||||
(i1, p1) = next(path_points, (i0, p0))
|
||||
p0i, p1i = p0, p1
|
||||
|
||||
join_indices = iter(joins)
|
||||
next_join = next(join_indices)
|
||||
while next_join < i1:
|
||||
next_join = next(join_indices)
|
||||
|
||||
for i2, p2 in path_points:
|
||||
if i1 == next_join:
|
||||
j = _join(p0, p1, p2, hw, du, miterlimit)
|
||||
next_join = next(join_indices, 0)
|
||||
else:
|
||||
j = _join_miter(p0, p1, p2, hw, du, 1.)
|
||||
stroke += j
|
||||
i1 = i2
|
||||
p0, p1 = p1, p2
|
||||
|
||||
if closed:
|
||||
b = e = _join(p0, p1, p1i, hw, du, miterlimit)
|
||||
else:
|
||||
b = _cap(p0i, p1i, hw, du)
|
||||
e = _cap(p0, p1, hw, du, start=False)
|
||||
|
||||
return b + stroke + e
|
||||
|
||||
|
||||
# filling ####################################################################
|
||||
|
||||
def _triangle_strip_hits(strip, x, y):
|
||||
"""yields hits and signs in triangles stored as strip."""
|
||||
strip_iter = iter(strip)
|
||||
p0, p1 = next(strip_iter), next(strip_iter)
|
||||
a0, b0, c0 = _line(p0, p1)
|
||||
s0, s = a0 * x + b0 * y + c0, 1
|
||||
for p2 in strip_iter:
|
||||
a1, b1, c1 = _line(p1, p2)
|
||||
s1 = a1 * x + b1 * y + c1
|
||||
a2, b2, c2 = _line(p2, p0)
|
||||
s2 = a2 * x + b2 * y + c2
|
||||
yield (s0 * s1 > 0) and (s1 * s2 > 0), s0 * s > 0
|
||||
p0, p1 = p1, p2
|
||||
s0, s = s1, -s
|
||||
|
||||
|
||||
def _evenodd_hit(x, y, fills):
|
||||
"""even/odd hit test on interior of a path."""
|
||||
# print( '_evenodd_hit : coucou')
|
||||
in_count = 0
|
||||
for hit, _ in _triangle_strip_hits(fills, x, y):
|
||||
if hit:
|
||||
in_count += 1
|
||||
# print( '_evenodd_hit : in_count=%d' % in_count )
|
||||
return (in_count % 2) == 1
|
||||
|
||||
|
||||
def _nonzero_hit(x, y, fills):
|
||||
"""non-zero hit test on interior of a path."""
|
||||
# print( '_nonzero_hit : coucou')
|
||||
in_count = 0
|
||||
for hit, positive in _triangle_strip_hits(fills, x, y):
|
||||
if hit:
|
||||
if positive:
|
||||
in_count += 1
|
||||
# print( '_nonzero_hit : positive hit : in_count=%d' % in_count )
|
||||
else:
|
||||
in_count -= 1
|
||||
# print( '_nonzero_hit : negative hit : in_count=%d' % in_count )
|
||||
# print( '_nonzero_hit : in_count=%d' % in_count )
|
||||
return in_count != 0
|
||||
|
||||
|
||||
def _stroke_hit(x, y, strokes):
|
||||
"""hit test on stroke of a path."""
|
||||
for hit, _ in _triangle_strip_hits(strokes, x, y):
|
||||
if hit:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _bbox(paths):
|
||||
"""bounding box of a path."""
|
||||
x_min = y_min = +INF
|
||||
x_max = y_max = -INF
|
||||
for path in paths:
|
||||
xs, ys = zip(*path)
|
||||
x_min, x_max = min(x_min, min(xs)), max(x_max, max(xs))
|
||||
y_min, y_max = min(y_min, min(ys)), max(y_max, max(ys))
|
||||
return (x_min, y_min), (x_max, y_max)
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element.circle
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from .path import Path
|
||||
|
||||
|
||||
# circle #####################################################################
|
||||
|
||||
class Circle(Path):
|
||||
tag = "circle"
|
||||
|
||||
cx, cy = 0, 0
|
||||
r = 0
|
||||
|
||||
@property
|
||||
def d(self):
|
||||
cx, cy = self.cx, self.cy
|
||||
r = self.r
|
||||
return ['M', (cx - r, cy), 'a', (r, r), 0, (0, 0), (2 * r, 0),
|
||||
'a', (r, r), 0, (0, 0), (-2 * r, 0), 'Z']
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element.ellipse
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from .path import Path
|
||||
|
||||
|
||||
# ellipse ####################################################################
|
||||
|
||||
class Ellipse(Path):
|
||||
tag = "ellipse"
|
||||
|
||||
cx, cy = 0, 0
|
||||
rx, ry = 0, 0
|
||||
|
||||
@property
|
||||
def d(self):
|
||||
cx, cy = self.cx, self.cy
|
||||
rx, ry = self.rx, self.ry
|
||||
if rx <= 0. or ry <= 0.:
|
||||
return []
|
||||
return ['M', (cx - rx, cy), 'a', (rx, ry), 0, (0, 0), (2 * rx, 0),
|
||||
'a', (rx, ry), 0, (0, 0), (-2 * rx, 0), 'Z']
|
|
@ -0,0 +1,60 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element.group
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from . import Element
|
||||
from ._path import _bbox
|
||||
|
||||
|
||||
# group ######################################################################
|
||||
|
||||
_empty_bbox = _bbox([])
|
||||
|
||||
|
||||
class Group(Element):
|
||||
tag = "g"
|
||||
|
||||
_state_attributes = Element._state_attributes + [
|
||||
"children",
|
||||
]
|
||||
|
||||
def __init__(self, children=None, **attributes):
|
||||
super(Group, self).__init__(**attributes)
|
||||
self.children = children if children is not None else []
|
||||
|
||||
def _aabbox(self, transform, inheriteds):
|
||||
bboxes = (child.aabbox(transform, inheriteds) for child in self.children)
|
||||
return _bbox(bbox for bbox in bboxes if bbox != _empty_bbox)
|
||||
|
||||
def _render(self, transform, inheriteds, context):
|
||||
for child in self.children:
|
||||
child.render(transform, inheriteds, context)
|
||||
|
||||
def _pick_content(self, x, y, transform):
|
||||
hits = []
|
||||
for child in self.children:
|
||||
# print('Group._pick_content : child = %s' % str(child) )
|
||||
hits += child.pick(x, y, transform)
|
||||
# print('Group._pick_content : len(hits) = %d' % len(hits) )
|
||||
|
||||
return hits
|
||||
|
||||
def _xml_content(self, defs):
|
||||
return "\n".join(child._xml(defs) for child in self.children)
|
||||
|
||||
def add_child(self, child):
|
||||
"""
|
||||
:param child: the new child to append to the list of children of this svg group
|
||||
:type child: a scenegraph.element.Element derived object
|
||||
"""
|
||||
self.children.append(child)
|
||||
child.parent = self
|
||||
# assert(child.parent == self)
|
||||
|
||||
def remove_child(self, child_to_remove):
|
||||
self.children = [child for child in self.children if child is not child_to_remove]
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element.image
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from os.path import abspath
|
||||
|
||||
try:
|
||||
from PIL import Image as _Image
|
||||
except ImportError:
|
||||
class _Image(object):
|
||||
size = (0, 0)
|
||||
mode = "RGBA"
|
||||
|
||||
@classmethod
|
||||
def open(klass, href):
|
||||
return _Image()
|
||||
|
||||
def tobytes(self):
|
||||
return b""
|
||||
|
||||
def convert(self, img_format):
|
||||
raise NotImplementedError
|
||||
|
||||
# from ...opengl.utils import create_texture
|
||||
from ..paint import Color, _Texture
|
||||
from .rectangle import Rectangle
|
||||
|
||||
|
||||
# image ######################################################################
|
||||
|
||||
class Image(Rectangle):
|
||||
tag = "image"
|
||||
|
||||
_state_attributes = Rectangle._state_attributes + [
|
||||
"href",
|
||||
]
|
||||
|
||||
fill = Color.white
|
||||
|
||||
def __init__(self, href, width=None, height=None, **attributes):
|
||||
href = abspath(href)
|
||||
pil_image = _Image.open(href)
|
||||
if pil_image.mode not in ["RGB", "RGBA"]:
|
||||
pil_image = pil_image.convert("RGBA")
|
||||
iw, ih = pil_image.size
|
||||
if width is None:
|
||||
width = iw
|
||||
if height is None:
|
||||
height = ih
|
||||
super(Image, self).__init__(width=width, height=height,
|
||||
**attributes)
|
||||
self.href = "file://%s" % href
|
||||
|
||||
self._texture_args = (pil_image.size,
|
||||
pil_image.tobytes(),
|
||||
pil_image.mode)
|
||||
|
||||
def _render(self, transform, inheriteds, context):
|
||||
(width, height), data, texture_format = self._texture_args
|
||||
del self._texture_args
|
||||
|
||||
# self.fill = _Texture(create_texture(width, height, data, texture_format), self.fill)
|
||||
assert False # call to opengl code removed but there doesn't seem to be a cairo implementation
|
||||
self._attributes.remove("fill")
|
||||
|
||||
super(Image, self)._render(transform, inheriteds, context)
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
scenegraph.element.line
|
||||
"""
|
||||
|
||||
|
||||
# imports ####################################################################
|
||||
|
||||
from .path import Path
|
||||
|
||||
|
||||
# line #######################################################################
|
||||
|
||||
class Line(Path):
|
||||
tag = "line"
|
||||
|
||||
fill = None
|
||||
|
||||
x1, y1 = 0, 0
|
||||
x2, y2 = 0, 0
|
||||
|
||||
@property
|
||||
def d(self):
|
||||
return ['M', (self.x1, self.y1), 'L', (self.x2, self.y2)]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue