#!/usr/bin/perl -ws
use Data::Dump 'pp';
our($dpi,$print_levels,$index,$bg,$no_png,$jmol_dir,$G,$ZPE,$Edisp,
$h,$help,$dir,$unit,$null,$delete_null,$no_highlight);
if ($h or $help) {
print "Usage: $0 fig.svg
Требуется бинарник inkscape. Для работы апплета jmol нужен www-сервер.
fig.svg получаем xyz2levels *.xyz > fig.svg.
Опции:
-dpi Разрешение. Умолчаемое 108 (масштаб в 1.2 раза больше, чем отображается
в inkscape).
-dir=directory Если *.xyz или *.xyzppm расположены в директории directory
(относительно соответствующего index.html), то эта опция добавит путь
в file.htm. Default -dir=.
-unit В каких единицах энергия в *.xyz.
Default a.u. Возможны -unit=kcal -unit=kJ
-ZPE К энергиям прибавляются ZPE-поправки
Во 2-й строке xyz-файлов д.б. подстрока типа 'ZPE 0.095000' (a.e.)
-G Аналогично, термические поправки
Во 2-й строке xyz-файлов д.б. 'G(298.25) 33.98' (kcal/mol)
Д.б. только одна из -G и -ZPE (т.к. в терм. поправки ZPE уже входит)
-Edisp Дисперсионные поправки Grimme. См. скрипт Edisp.
Во 2-й строке xyz-файлов д.б. подстрока типа 'Edisp -9.79' (kcal/mol)
-null=regexp за 0 будет приниматься энергия той структуры, имя файла которой
(только имя, без расширения) подходит regexp.
-delete_null не помещать в htm-файл информацию о \"нулевых\" уровнях
и не создавать соответствующие html-файлы. Это нужно, если эти уровни
на завершающем этапе удаляются с диаграммы.
-print_levels Печатает координаты (x и y) уровней в png-файле и их надписи
-index С этой опцией будет создан полный html-файл, готовый для запуска в
браузере. Без нее - только кусок тегои
HTM
if ($index) {
print HTM <
HTM
}
close HTM;
# Make png
if (! $no_png) {
my $file_svg_for_png = $file_svg;
if ($delete_null) {
warn "\nGenerate svg without null level.\n";
$file_svg_for_png = "PNG_$file_svg";
open IN, '<', $file_svg or die "Can't open $file_svg: $!\n";
local $/;
my $svg = ;
close IN;
open OUT, '>', $file_svg_for_png or die "Can't write $file_svg_for_png: $!\n";
foreach my $l (@levels) {
if ($l->[1][0] =~ /$null/) {
my $text = $l->[1][0];
my ($id1,$id2,$id3) = $l->[0][0]=~/(rect\S+?)(rect\S+?)(rect\S+)/;
#warn "$text,$id1,$id2,$id3\n";
$svg =~ s/]*>\s*(]*>)?\s*\Q$text\E\s*(<\/tspan>)?\s*<\/text>//s;
# $svg =~ s/]*>\s*\s*\Q$text\E\s*\s*<\/text>//s;
foreach my $id ($id1,$id2,$id3) {
$svg =~ s///s;
}
}
}
print OUT $svg;
close OUT;
}
$bg = 'white' if $bg && $bg eq '1';
my @args = ($inkscape_x,
"--export-dpi=$dpi", "--export-area-drawing", "--export-png=$filename.png",
"--without-gui", $file_svg_for_png);
splice(@args, -2, 0, "--export-background=$bg") if $bg;
warn "\nRun `@args`\n";
system(@args) == 0 or warn "System\n@args\nfailed: $?\n";
unlink $file_svg_for_png if $delete_null;
}
if ($index) {
warn "\nTest if jmol directory exists (DocumentRoot$jmol_dir) and run $index in browser\n";
} else {
warn "\nInsert $htm in your html-template\n";
}
######################################################################
# Get array of molecules
# Return hash of isomers in which keys is formulas
# and isomer is ref to array of molecules with this formula.
sub isomers {
my @mols = @_;
my %isomers;
foreach my $mol (@mols) {
my $formula = get_formula($mol);
push @{$isomers{$formula}}, $mol;
}
foreach (values %isomers) {
$_ = [sort {($a->[0]{Energy}||1e6) <=> ($b->[0]{Energy}||1e6)} @$_];
}
return %isomers;
}
sub get_formula {
my @mol = @{$_[0]};
my %f;
my $formula = '';
$f{ucfirst lc $_->[0]}++ foreach @mol[1..$#mol];
foreach (sort by_Hill keys %f) {
$formula .= $_ . ($f{$_}==1 ? '' : $f{$_});
}
return $formula;
}
sub by_Hill {
return -1 if uc($a) eq 'C' and uc($b) ne 'C';
return 1 if uc($a) ne 'C' and uc($b) eq 'C';
return -1 if uc($a) eq 'H' and uc($b) ne 'H';
return 1 if uc($a) ne 'H' and uc($b) eq 'H';
uc($a) cmp uc($b);
}
sub by_formula {
my $aa = $a;
my $bb = $b;
$aa =~ s/([A-Z](?:[a-z]*))(?!\d)/${1}1/g;
$bb =~ s/([A-Z](?:[a-z]*))(?!\d)/${1}1/g;
$aa =~ s/(\d+)/sprintf("%03d",$1)/eg;
$bb =~ s/(\d+)/sprintf("%03d",$1)/eg;
$aa cmp $bb;
}
######################################################################
# Читает xyz. Параметр - имя xyz-файла.
# Возвращает массив найденных молекул.
sub read_molden {
open L, '<', shift or do {warn "Can't open $_[0]: $!\n"; return};
return undef if eof(L);
my @mol;
# 1-st string
if (=~/^\s*(\d+)\s*$/) {
$N = $1;
} else {
return undef;
}
# 2-nd string
my $line = ;
($mol[0]{Energy}) = $line=~/(-?\d+\.\d+)/;
if ($G) {
$line=~/\sG\(($ddd)\)\s+($ddd)/ && ($mol[0]{Energy} += $2/627.51);
}
if ($ZPE) {
$line=~/\sZPE\s+($ddd)/ && ($mol[0]{Energy} += $1);
}
if ($Edisp) {
$line=~/\sEdisp\s+($ddd)/ && ($mol[0]{Energy} += $1/627.51);
}
# Geometry
for ($i=1;$i<=$N;$i++) {
my ($atom,$x,$y,$z,$ppm) = split ' ', ;
return undef unless $atom=~/^\w\w?/ &&
$x=~/^-?\d+\.\d+/ &&
$y=~/^-?\d+\.\d+/ &&
$z=~/^-?\d+\.\d+/;
$mol[$i] = [$atom,$x,$y,$z,$ppm];
}
close L;
return \@mol;
}
# Печатает xyz. Параметры -- список молекул, в конце м.б. имя файла.
# Если последний элемент списка - имя файла (не ссылка на массив),
# то печать в этот файл, иначе - на stdout
sub write_molden {
my $fh = \*STDOUT;
if (ref($_[-1]) ne 'ARRAY') {
my $file = pop @_;
open $fh, '>', $file or die "Can't write to $file: $!\n";
}
foreach my $mol (@_) {
my $N = $#{$mol};
print $fh " $N\n";
print $fh " Energy $mol->[0]{Energy} " if $mol->[0]{Energy};
print $fh " Symmetry $mol->[0]{Symmetry} " if $mol->[0]{Symmetry};
print $fh "\n";
for (my $i=1; $i<=$N; $i++) {
my ($atom,$x,$y,$z,$ppm) = @{$mol->[$i]};
printf $fh " %-2s %12.8f %12.8f %12.8f", $atom, $x, $y, $z;
printf $fh uc($atom) eq 'H' ? " %10.3f" : " %9.2f" , $ppm if $ppm;
print $fh "\n";
}
}
#close $fh;
}
sub svg2levels_inkscape {
#Получает dpi, имя svg-файла.
#Через inkscape извлекает координаты и размеры всех элементов.
#Парсит svg-файл и находит ближайшие к уровням надписи (имена файлов).
#Возвращает массив ([ [IDIDID,X,Y,width,height], [text,X,Y,width,height] ], ...)
#где IDIDID - ID прямоугольников, составляющих уровень, text - ближайшая надпись.
#Расстояние определяется по серединам левых сторон.
my ($dpi,$svg_file) = @_; # input
my @levels; # output
# Через inkscape получает координаты и размеры всех элементов в массив @elements
# и в отдельные массивы - прямоугольники и текст.
# @elements = ([ID,x,y,width,height], ...)
my @arg = ($inkscape_x, '--query-all', "--without-gui", $file_svg);
warn "Run `@arg`\n";
open L, '-|', @arg or die "Can't run $inkscape_x: $!\n";
my @elements;
while () {
chomp;
my @ar = split ',';
push @elements, [@ar];
push @rect_elements, [@ar] if $ar[0] =~ /^rect/;
push @text_elements, [@ar] if $ar[0] =~ /^text/;
}
close L;
my $X = $elements[0][1];
my $Y = $elements[0][2];
my $R = $dpi/90;
# Из массива прямоугольников выделяем в подмассивы расположенные на одинаковом уровне
my %hhh;
foreach (@rect_elements) {
push @{$hhh{$_->[2]}}, $_;
}
#pp %hhh; exit;
# сортируем по уровню (по Y)
@rect_elements = sort {$a->[0][2] <=> $b->[0][2]} values %hhh;
#pp @rect_elements; exit;
# Выделяем тройки примыкающих друг к другу прямоугольников
my @res; # в нем накапливаем подходящие тройки
foreach (@rect_elements) {
next if @$_< 3;
@$_ = sort {$a->[1] <=> $b->[1]} @$_;
my @t = @$_; # массив прямоугольников на одном уровне
#pp @t;
LOOP:
while (1) {
my $triples; # счетчик троек
for (my $i=0; $i<@t-2; $i++) {
for (my $j=$i+1; $j<@t-1; $j++) {
#print "abs($t[$j][1]-$t[$i][1]-$t[$i][3])>1\n";
next if abs($t[$j][1]-$t[$i][1]-$t[$i][3])>1;
for (my $k=$j+1; $k<@t; $k++) {
#print "abs($t[$k][1]-$t[$j][1]-$t[$j][3])<1\n";
if (abs($t[$k][1]-$t[$j][1]-$t[$j][3])<1) {
if (@res &&
$res[-1][0][2] == $t[$i][2] &&
$res[-1][2][1]+$res[-1][2][3] > $t[$i][1])
{
#pp @res;
#print "$res[-1][2][1]+$res[-1][2][3] > $t[$i][1]\n";
warn "Levels $res[-1][0][0]-$res[-1][1][0]-$res[-1][2][0] and $t[$i][0]-$t[$j][0]-$t[$k][0] are interlaced\n";
}
push @res, [ $t[$i],$t[$j],$t[$k] ];
$triples++;
splice @t, $k, 1;
splice @t, $j, 1;
splice @t, $i, 1;
next LOOP;
}
}
}
}
last LOOP unless $triples;
}
}
#pp @res; exit;
@rect_elements = @res;
undef @res;
open L, '<', $svg_file or die "Can't open $svg_file: $!\n";
my $svg = join "", ;
close L;
my ($H) = $svg =~ /