refactor command to be more flexible and capable

This commit is contained in:
Axel Kohlmeyer
2025-02-08 22:52:19 -05:00
parent f693577262
commit 3cd028fd01
2 changed files with 208 additions and 49 deletions

View File

@ -8,17 +8,27 @@ Syntax
.. code-block:: LAMMPS .. code-block:: LAMMPS
region2vmd file args region2vmd file keyword arg ...
* file = name of VMD script file to write * filename = name of file to write VMD script commands to
* args = one or more region IDs may be appended * zero or more keyword/arg pairs may be appended
* keyword = *region* or *color* or *material* or *command*
.. parsed-literal::
*region* region-ID = name of region to translate to VMD graphics
*color* color-name = set color for following visualized objects
*material* material-name = set material for following visualized objects
*command* string = string with custom VMD script command (in quotes)
Examples Examples
"""""""" """"""""
.. code-block:: LAMMPS .. code-block:: LAMMPS
region2vmd regions.vmd box c1 c2 region2vmd regions.vmd material Opaque color red region c1 color green region c2
region2vmd vizbox.vmd command "mol new system.lammpstrj waitfor all" region box
region2vmd regdefs.vmd region upper region lower region hole
Description Description
""""""""""" """""""""""
@ -28,17 +38,106 @@ Description
Write a `VMD <https:://ks.uiuc.edu/Research/vmd/>`_ Tcl script file with Write a `VMD <https:://ks.uiuc.edu/Research/vmd/>`_ Tcl script file with
commands that aim to create a visualization of :doc:`LAMMPS regions commands that aim to create a visualization of :doc:`LAMMPS regions
<region>`. There may be multiple region visualizations stored in a <region>`. There may be multiple region visualizations stored in a
single file. Only a limited amount of region styles and settings are single file.
currently supported. See **Restrictions** below.
The visualization is implemented by creating a new (and empty) "VMD The visualization is implemented by creating a new (and empty) "VMD
molecule" and then using VMD graphics primitives to represent the region molecule" and then assigning a sequence of VMD graphics primitives to
in VMD. Each region will be stored in a separate "VMD molecule" with represent the region in VMD. Each region will be stored in a separate
the name "LAMMPS region <region ID>". "VMD molecule" with the name "LAMMPS region <region ID>".
The *region2vmd* command is following by the filename for the resulting
VMD script and an arbitrary number of keyword argument pairs to either
write out a new *region* visualization, change the *color* or *material*
setting, or to insert arbitrary VMD script *command*s. The keywords
and arguments are processed in sequence.
The *region* keyword must be followed by a previously defined LAMMPS
:doc:`region <region>`. Only a limited set region styles and region
settings are currently supported. See **Restrictions** below.
Unsupported region styles or regions with unsupported settings will be
skipped and a corresponding message is printed.
The *color* keyword must be followed by a color name that is defined in
VMD. This color will be used by all following region visualizations.
The default setting is 'silver'. VMD has the following colors
pre-defined:
.. table_from_list::
:columns: 11
* blue
* red
* gray
* orange
* yellow
* tan
* silver
* green
* white
* pink
* cyan
* purple
* lime
* mauve
* ochre
* iceblue
* black
* yellow2
* yellow3
* green2
* green3
* cyan2
* cyan3
* blue2
* blue3
* violet
* violet2
* magenta
* magenta2
* red2
* red3
* orange2
* orange3
The *material* keyword must be followed by a material name that is defined in
VMD. This material will be used by all following visualizations. The
default setting is 'Transparent'. VMD has the following materials
pre-defined:
.. table_from_list::
:columns: 7
* Opaque
* Transparent
* BrushedMetal
* Diffuse
* Ghost
* Glass1
* Glass2
* Glass3
* Glossy
* HardPlastic
* MetallicPastel
* Steel
* Translucent
* Edgy
* EdgyShiny
* EdgyGlass
* Goodsell
* AOShiny
* AOChalky
* AOEdgy
* BlownGlass
* GlassBubble
* RTChrome
The *command* keyword must be followed by a VMD script command as a
single string in quotes. This command will be directly inserted into
the created VMD script.
The created file can be loaded into VMD either from the command line The created file can be loaded into VMD either from the command line
with the -e flag, or from the command prompt with play <script file>, or with the '-e' flag, or from the command prompt with 'play <script
from the File menu via "Load VMD visualization state". file>', or from the File menu via "Load VMD visualization state".
---------- ----------
@ -62,7 +161,7 @@ Related commands
:doc:`region <region>` :doc:`region <region>`
Default Defaults
""""""" """"""""
none *color* = silver, *material* = Transparent

View File

@ -27,20 +27,49 @@
#include "region_cylinder.h" #include "region_cylinder.h"
#include "region_sphere.h" #include "region_sphere.h"
#include <cmath> #include <cstring>
#include <unordered_set>
using namespace LAMMPS_NS; using namespace LAMMPS_NS;
static constexpr double SMALL = 1.0e-10; static constexpr double SMALL = 1.0e-10;
static const std::unordered_set<std::string> vmdcolors{
"blue", "red", "gray", "orange", "yellow", "tan", "silver", "green", "white",
"pink", "cyan", "purple", "lime", "mauve", "ochre", "iceblue", "black", "yellow2",
"yellow3", "green2", "green3", "cyan2", "cyan3", "blue2", "blue3", "violet", "violet2",
"magenta", "magenta2", "red2", "red3", "orange2", "orange3"};
static const std::unordered_set<std::string> vmdmaterials{
"Opaque", "Transparent", "BrushedMetal", "Diffuse", "Ghost", "Glass1",
"Glass2", "Glass3", "Glossy", "HardPlastic", "MetallicPastel", "Steel",
"Translucent", "Edgy", "EdgyShiny", "EdgyGlass", "Goodsell", "AOShiny",
"AOChalky", "AOEdgy", "BlownGlass", "GlassBubble", "RTChrome"};
// class that "owns" the file pointer and closes it when going out of scope.
// this avoids a lot of redundant checks and calls.
class AutoClose {
public:
AutoClose() = delete;
AutoClose(const AutoClose &) = delete;
AutoClose(const AutoClose &&) = delete;
explicit AutoClose(FILE *_fp) : fp(_fp) {};
~AutoClose()
{
if (fp) fclose(fp);
}
private:
FILE *fp;
};
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
void Region2VMD::command(int narg, char **arg) void Region2VMD::command(int narg, char **arg)
{ {
if (narg < 3) utils::missing_cmd_args(FLERR, "region2vmd", error);
FILE *fp = nullptr; FILE *fp = nullptr;
if (narg < 2) utils::missing_cmd_args(FLERR, "region2vmd", error);
if (comm->me == 0) { if (comm->me == 0) {
fp = fopen(arg[0], "w"); fp = fopen(arg[0], "w");
if (fp == nullptr) { if (fp == nullptr) {
@ -52,21 +81,67 @@ void Region2VMD::command(int narg, char **arg)
} }
} }
for (int iarg = 1; iarg < narg; ++iarg) { // automatically close fp when fpowner goes out of scope
auto *region = domain->get_region_by_id(arg[iarg]); AutoClose fpowner(fp);
if (!region) {
if (fp) fclose(fp); // defaults
error->all(FLERR, iarg, "Region {} does not exist", arg[iarg]); std::string color = "silver";
std::string material = "Transparent";
int iarg = 1;
std::string thisarg = arg[iarg];
while (iarg < narg) {
if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "region2vmd", error);
thisarg = arg[iarg];
++iarg;
if (thisarg == "color") {
color = arg[iarg];
if (const auto &search = vmdcolors.find(color); search == vmdcolors.end())
error->all(FLERR, iarg, "Color {} is not a known VMD color", color);
} else if (thisarg == "material") {
material = arg[iarg];
if (const auto &search = vmdmaterials.find(material); search == vmdmaterials.end())
error->all(FLERR, iarg, "Material {} is not a known VMD material", material);
} else if (thisarg == "command") {
if (fp) {
fputs("\n# custom command\n", fp);
fputs(arg[iarg], fp);
fputs("\n", fp);
}
} else if (thisarg == "region") {
auto *region = domain->get_region_by_id(arg[iarg]);
if (!region) {
error->all(FLERR, iarg, "Region {} does not exist", arg[iarg]);
} else {
if (fp) {
utils::logmesg(lmp, " writing region {} ...", region->id);
utils::print(fp, "\n# region {} of style {}\n", region->id, region->style);
fputs("# create new empty VMD molecule to store the graphics primitives\n"
"set gfxmol [mol new]\nmol top $gfxmol\n",
fp);
utils::print(fp, "mol rename $gfxmol {{LAMMPS region {}}}\n", region->id);
fputs("# set color and material\n", fp);
utils::print(fp, "graphics $gfxmol color {}\n", color);
utils::print(fp, "graphics $gfxmol material {}\n", material);
write_region(fp, region);
}
}
} else { } else {
write_region(fp, region); error->all(FLERR, iarg - 1, "Unknown region2vmd keyword {}", thisarg);
} }
++iarg;
} }
// done, close file // done. file will be close automatically
if (comm->me == 0) { if (fp) {
// reset views and restore previous top molecule // reset views and restore previous top molecule
fputs("display resetview\nif {$oldtop >= 0} {mol top $oldtop}\n", fp); fputs("after idle {if {$oldtop >= 0} {mol top $oldtop}; display resetview}\n", fp);
fclose(fp);
} }
} }
@ -80,26 +155,11 @@ void Region2VMD::write_region(FILE *fp, Region *region)
if (!fp || !region) return; if (!fp || !region) return;
if (region->dynamic_check()) { if (region->dynamic_check()) {
utils::logmesg(lmp, "Cannot (yet) handle moving or rotating region {}. Skipping... ", utils::logmesg(lmp, "Cannot (yet) handle moving or rotating regions {}. Skipping... ",
region->id); region->id);
return; return;
} }
// create a new (empty) VMD molecule to hold the graphics for this specific region.
utils::logmesg(lmp, " writing region {} ...", region->id);
utils::print(fp, "\n# region {} of style {}\n", region->id, region->style);
fputs("# create new empty VMD molecule to store the graphics primitives\n"
"set gfxmol [mol new]\nmol top $gfxmol\n",
fp);
utils::print(fp, "mol rename $gfxmol {{LAMMPS region {}}}\n", region->id);
fputs("# set color to desired value. Use 'silver' by default\n"
"graphics $gfxmol color silver\n",
fp);
fputs("# set material to desired choice\n"
"graphics $gfxmol material Transparent\n",
fp);
// translate compatible regions to VMD graphics primitives, skip others. // translate compatible regions to VMD graphics primitives, skip others.
const std::string regstyle = region->style; const std::string regstyle = region->style;
@ -108,9 +168,7 @@ void Region2VMD::write_region(FILE *fp, Region *region)
if (!block) { if (!block) {
error->one(FLERR, Error::NOLASTLINE, "Region {} is not of style 'block'", region->id); error->one(FLERR, Error::NOLASTLINE, "Region {} is not of style 'block'", region->id);
} else { } else {
// a block is represented by 12 triangles // a block is represented by 12 triangles
utils::print(fp, utils::print(fp,
"draw triangle {{{0} {2} {4}}} {{{0} {2} {5}}} {{{0} {3} {4}}}\n" "draw triangle {{{0} {2} {4}}} {{{0} {2} {5}}} {{{0} {3} {4}}}\n"
"draw triangle {{{0} {3} {4}}} {{{0} {3} {5}}} {{{0} {2} {5}}}\n" "draw triangle {{{0} {3} {4}}} {{{0} {3} {5}}} {{{0} {2} {5}}}\n"
@ -132,10 +190,9 @@ void Region2VMD::write_region(FILE *fp, Region *region)
if (!cone) { if (!cone) {
error->one(FLERR, Error::NOLASTLINE, "Region {} is not of style 'cone'", region->id); error->one(FLERR, Error::NOLASTLINE, "Region {} is not of style 'cone'", region->id);
} else { } else {
// The VMD cone primitive requires one radius set to zero // The VMD cone primitive requires one radius set to zero
if (cone->radiuslo < SMALL) { if (cone->radiuslo < SMALL) {
// a cone uses a single cone primitive // a VMD cone uses a single cone primitive
if (cone->axis == 'x') { if (cone->axis == 'x') {
utils::print(fp, "draw cone {{{1} {2} {3}}} {{{0} {2} {3}}} radius {4} resolution 20\n", utils::print(fp, "draw cone {{{1} {2} {3}}} {{{0} {2} {3}}} radius {4} resolution 20\n",
cone->lo, cone->hi, cone->c1, cone->c2, cone->radiushi); cone->lo, cone->hi, cone->c1, cone->c2, cone->radiushi);
@ -147,7 +204,7 @@ void Region2VMD::write_region(FILE *fp, Region *region)
cone->lo, cone->hi, cone->c1, cone->c2, cone->radiushi); cone->lo, cone->hi, cone->c1, cone->c2, cone->radiushi);
} }
} else if (cone->radiushi < SMALL) { } else if (cone->radiushi < SMALL) {
// a cone uses a single cone primitive // a VMD cone uses a single cone primitive
if (cone->axis == 'x') { if (cone->axis == 'x') {
utils::print(fp, "draw cone {{{0} {2} {3}}} {{{1} {2} {3}}} radius {4} resolution 20\n", utils::print(fp, "draw cone {{{0} {2} {3}}} {{{1} {2} {3}}} radius {4} resolution 20\n",
cone->lo, cone->hi, cone->c1, cone->c2, cone->radiuslo); cone->lo, cone->hi, cone->c1, cone->c2, cone->radiuslo);
@ -163,6 +220,7 @@ void Region2VMD::write_region(FILE *fp, Region *region)
"Cannot (yet) translate a truncated cone to VMD graphics. Skipping...\n"); "Cannot (yet) translate a truncated cone to VMD graphics. Skipping...\n");
} }
} }
} else if (regstyle == "cylinder") { } else if (regstyle == "cylinder") {
const auto cylinder = dynamic_cast<RegCylinder *>(region); const auto cylinder = dynamic_cast<RegCylinder *>(region);
if (!cylinder) { if (!cylinder) {
@ -180,6 +238,7 @@ void Region2VMD::write_region(FILE *fp, Region *region)
cylinder->lo, cylinder->hi, cylinder->c1, cylinder->c2, cylinder->radius); cylinder->lo, cylinder->hi, cylinder->c1, cylinder->c2, cylinder->radius);
} }
} }
} else if (regstyle == "sphere") { } else if (regstyle == "sphere") {
const auto sphere = dynamic_cast<RegSphere *>(region); const auto sphere = dynamic_cast<RegSphere *>(region);
if (!sphere) { if (!sphere) {
@ -189,6 +248,7 @@ void Region2VMD::write_region(FILE *fp, Region *region)
utils::print(fp, "draw sphere {{{} {} {}}} radius {} resolution 20\n", sphere->xc, sphere->yc, utils::print(fp, "draw sphere {{{} {} {}}} radius {} resolution 20\n", sphere->xc, sphere->yc,
sphere->zc, sphere->radius); sphere->zc, sphere->radius);
} }
} else { } else {
utils::logmesg(lmp, utils::logmesg(lmp,
"Cannot (yet) translate region {} of style {} to VMD graphics. Skipping... ", "Cannot (yet) translate region {} of style {} to VMD graphics. Skipping... ",