From 425fe429936301324dc1bbd3c342bb8df0d9810f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 00:58:22 -0500 Subject: [PATCH 01/54] move enum for object types passed from external code to header for consistency --- src/BODY/body_nparticle.cpp | 4 ++-- src/BODY/body_rounded_polygon.cpp | 6 +++--- src/BODY/body_rounded_polyhedron.cpp | 7 +++---- src/dump_image.cpp | 1 - src/dump_image.h | 3 ++- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/BODY/body_nparticle.cpp b/src/BODY/body_nparticle.cpp index 2d4c06ae9e5..0e1a8fc30dc 100644 --- a/src/BODY/body_nparticle.cpp +++ b/src/BODY/body_nparticle.cpp @@ -16,6 +16,7 @@ #include "atom.h" #include "atom_vec_body.h" +#include "dump_image.h" #include "error.h" #include "math_extra.h" #include "math_eigen.h" @@ -27,7 +28,6 @@ using namespace LAMMPS_NS; static constexpr double EPSILON = 1.0e-7; -enum{SPHERE,LINE,TRI}; // also in DumpImage /* ---------------------------------------------------------------------- */ @@ -355,7 +355,7 @@ int BodyNparticle::image(int ibonus, double flag1, double /*flag2*/, int n = bonus->ivalue[0]; for (int i = 0; i < n; i++) { - imflag[i] = SPHERE; + imflag[i] = DumpImage::SPHERE; MathExtra::quat_to_mat(bonus->quat,p); MathExtra::matvec(p,&bonus->dvalue[3*i],imdata[i]); diff --git a/src/BODY/body_rounded_polygon.cpp b/src/BODY/body_rounded_polygon.cpp index 366db6264f1..3fc366440fd 100644 --- a/src/BODY/body_rounded_polygon.cpp +++ b/src/BODY/body_rounded_polygon.cpp @@ -21,6 +21,7 @@ #include "atom.h" #include "atom_vec_body.h" #include "domain.h" +#include "dump_image.h" #include "error.h" #include "math_extra.h" #include "math_eigen.h" @@ -33,7 +34,6 @@ using namespace LAMMPS_NS; static constexpr double EPSILON = 1.0e-7; -enum{SPHERE,LINE}; // also in DumpImage /* ---------------------------------------------------------------------- */ @@ -510,7 +510,7 @@ int BodyRoundedPolygon::image(int ibonus, double flag1, double /*flag2*/, if (n == 1) { for (int i = 0; i < n; i++) { - imflag[i] = SPHERE; + imflag[i] = DumpImage::SPHERE; MathExtra::quat_to_mat(bonus->quat,p); MathExtra::matvec(p,&bonus->dvalue[3*i],imdata[i]); @@ -528,7 +528,7 @@ int BodyRoundedPolygon::image(int ibonus, double flag1, double /*flag2*/, // first end pt of each line for (int i = 0; i < n; i++) { - imflag[i] = LINE; + imflag[i] = DumpImage::LINE; MathExtra::quat_to_mat(bonus->quat,p); MathExtra::matvec(p,&bonus->dvalue[3*i],imdata[i]); diff --git a/src/BODY/body_rounded_polyhedron.cpp b/src/BODY/body_rounded_polyhedron.cpp index bd16dac96c3..30b757eb2bc 100644 --- a/src/BODY/body_rounded_polyhedron.cpp +++ b/src/BODY/body_rounded_polyhedron.cpp @@ -20,6 +20,7 @@ #include "atom.h" #include "atom_vec_body.h" +#include "dump_image.h" #include "error.h" #include "math_extra.h" #include "math_eigen.h" @@ -34,8 +35,6 @@ using namespace LAMMPS_NS; static constexpr double EPSILON = 1.0e-7; static constexpr int MAX_FACE_SIZE = 4; // maximum number of vertices per face (for now) -enum { SPHERE, LINE }; // also in DumpImage - /* ---------------------------------------------------------------------- */ BodyRoundedPolyhedron::BodyRoundedPolyhedron(LAMMPS *lmp, int narg, char **arg) : @@ -615,7 +614,7 @@ int BodyRoundedPolyhedron::image(int ibonus, double flag1, double /*flag2*/, if (nvertices == 1) { // spheres for (int i = 0; i < nvertices; i++) { - imflag[i] = SPHERE; + imflag[i] = DumpImage::SPHERE; MathExtra::quat_to_mat(bonus->quat,p); MathExtra::matvec(p,&bonus->dvalue[3*i],imdata[i]); @@ -637,7 +636,7 @@ int BodyRoundedPolyhedron::image(int ibonus, double flag1, double /*flag2*/, int pt1, pt2; for (int i = 0; i < nedges; i++) { - imflag[i] = LINE; + imflag[i] = DumpImage::LINE; pt1 = static_cast(edge_ends[2*i]); pt2 = static_cast(edge_ends[2*i+1]); diff --git a/src/dump_image.cpp b/src/dump_image.cpp index ad07a2e5d04..e3c7399faa9 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -281,7 +281,6 @@ using MathConst::DEG2RAD; static constexpr double BIG = 1.0e20; enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE }; -enum { SPHERE, LINE, TRI }; // also in some Body and Fix child classes enum { STATIC, DYNAMIC }; enum { NO = 0, YES = 1, AUTO = 2 }; enum { FILLED, FRAME, POINTS }; diff --git a/src/dump_image.h b/src/dump_image.h index 1cb13c6ff2d..d9c694a533c 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -26,7 +26,8 @@ namespace LAMMPS_NS { class Region; class DumpImage : public DumpCustom { public: - int multifile_override; // used by write_dump command + int multifile_override; // used by write_dump command + enum { SPHERE, LINE, TRI, CYLINDER }; // used by some Body and Fix child classes DumpImage(class LAMMPS *, int, char **); ~DumpImage() override; From d9e0c5ec8ddf7b2d6fb727857ebf28641f787a27 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 00:59:55 -0500 Subject: [PATCH 02/54] improve dump image support for rendering objects from fixes --- doc/src/dump_image.rst | 36 ++++++++-------- src/dump_image.cpp | 94 ++++++++++++++++++++++++++++-------------- src/dump_image.h | 2 + 3 files changed, 84 insertions(+), 48 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index ec769b5e8f5..6b7f3909154 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -160,6 +160,8 @@ Syntax *color* args = name R G B name = name of color R,G,B = red/green/blue numeric values from 0.0 to 1.0 + *fcolor* args = color + color = name of color for fix objects *framerate* arg = fps fps = frames per second for movie *gmap* args = identical to *amap* args @@ -505,26 +507,18 @@ change this via the dump_modify command. ---------- The *fix* keyword can be used with a :doc:`fix ` that produces -objects to be drawn. +objects to be drawn. Below is a list of supported fixes: -The *fflag1* and *fflag2* settings are numerical values which are -passed to the fix to affect how the drawing of its objects is done. -See the individual fix page for a description of what these -parameters mean for a particular fix. +* :doc:`fix indent ` -The only setting currently allowed for the *color* value is *type*, -which will color the fix objects according to their type. By default -the mapping of types to colors is as follows: - -* type 1 = red -* type 2 = green -* type 3 = blue -* type 4 = yellow -* type 5 = aqua -* type 6 = cyan +The *fflag1* and *fflag2* settings are numerical values which are used +by *dump image* to adjust how the drawing of the objects communicated +by the fix is done. See the documentation of the individual fixes for +a description of what these parameters mean. -and repeats itself for types > 6. There is not yet an option to -change this via the dump_modify command. +The only setting currently allowed for the *color* value is *type*, +which will color the fix objects in the same color as atom type 1. +By default this will be "red". ---------- @@ -1029,6 +1023,14 @@ pre-defined color names with new RBG values. ---------- +.. versionadded:: TBD + +The *fcolor* keyword sets the color of any image objects created by a +fix. The color name can be any of the 140 pre-defined colors (see +below) or a color name defined by the *dump_modify color* option. + +---------- + The *framerate* keyword can be used with the :doc:`dump movie ` command to define the duration of the resulting movie file. Movie files written by the dump *movie* command have a default diff --git a/src/dump_image.cpp b/src/dump_image.cpp index e3c7399faa9..9f7496ceb6c 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -294,7 +294,7 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : colorelement(nullptr), bcolortype(nullptr), grid2d(nullptr), grid3d(nullptr), id_grid_compute(nullptr), id_grid_fix(nullptr), grid_compute(nullptr), grid_fix(nullptr), gbuf(nullptr), avec_line(nullptr), avec_tri(nullptr), avec_body(nullptr), fixptr(nullptr), - image(nullptr), chooseghost(nullptr), bufcopy(nullptr) + id_fix(nullptr), fcolor(nullptr), image(nullptr), chooseghost(nullptr), bufcopy(nullptr) { if (binary || multiproc) error->all(FLERR, 4, "Invalid dump image filename {}", filename); @@ -363,7 +363,6 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : bdiam = NUMERIC; bdiamvalue = 0.5; } - char *fixID = nullptr; cflag = STATIC; cx = cy = cz = 0.5; @@ -477,7 +476,8 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : } else if (strcmp(arg[iarg],"fix") == 0) { if (iarg+5 > narg) utils::missing_cmd_args(FLERR,"dump image fix", error); fixflag = YES; - fixID = arg[iarg+1]; + delete[] id_fix; + id_fix = utils::strdup(arg[iarg+1]); if (strcmp(arg[iarg+2],"type") == 0) fixcolor = TYPE; else error->all(FLERR, iarg+2, "Dump image fix only supports color by type"); fixflag1 = utils::numeric(FLERR,arg[iarg+3],false,lmp); @@ -687,10 +687,9 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : if (lineflag || triflag || bodyflag) extraflag = 1; if (fixflag) { - fixptr = modify->get_fix_by_id(fixID); + fixptr = modify->get_fix_by_id(id_fix); if (!fixptr) - error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", fixID); - + error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", id_fix); } // allocate image buffer now that image size is known @@ -783,6 +782,7 @@ DumpImage::~DumpImage() delete[] id_grid_compute; delete[] id_grid_fix; + delete[] id_fix; } /* ---------------------------------------------------------------------- */ @@ -914,6 +914,13 @@ void DumpImage::init_style() ((domain->dimension == 3) && domain->zperiodic && (bondcutoff > domain->zprd))) error->all(FLERR, "Dump image autobond cutoff is larger than periodic domain"); } + + // check if fix with visualization still exists + if (fixflag) { + fixptr = modify->get_fix_by_id(id_fix); + if (!fixptr) + error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", id_fix); + } } /* ---------------------------------------------------------------------- */ @@ -1680,7 +1687,8 @@ void DumpImage::create_image() // render objects provided by a fix if (fixflag) { - int tridraw=0,edgedraw=0; + int tridraw = 0; + int edgedraw = 0; if (domain->dimension == 3) { tridraw = 1; edgedraw = 1; @@ -1689,30 +1697,48 @@ void DumpImage::create_image() } n = fixptr->image(fixvec,fixarray); - - for (i = 0; i < n; i++) { - if (fixvec[i] == SPHERE) { - // no fix draws spheres yet - } else if (fixvec[i] == LINE) { - if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } - image->draw_cylinder(&fixarray[i][1],&fixarray[i][4], - color,fixflag1,3); - } else if (fixvec[i] == TRI) { - if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } - p1 = &fixarray[i][1]; - p2 = &fixarray[i][4]; - p3 = &fixarray[i][7]; - if (tridraw) image->draw_triangle(p1,p2,p3,color); - if (edgedraw) { - image->draw_cylinder(p1,p2,color,fixflag2,3); - image->draw_cylinder(p2,p3,color,fixflag2,3); - image->draw_cylinder(p3,p1,color,fixflag2,3); + if (fixvec && fixarray) { + for (i = 0; i < n; i++) { + if (fixvec[i] == SPHERE) { + if (fcolor) { + color = fcolor; + } else if (fixcolor == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } + image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]-fixflag2); + } else if (fixvec[i] == LINE) { + if (fcolor) { + color = fcolor; + } else if (fixcolor == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixflag1,3); + } else if (fixvec[i] == TRI) { + if (fcolor) { + color = fcolor; + } else if (fixcolor == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } + p1 = &fixarray[i][1]; + p2 = &fixarray[i][4]; + p3 = &fixarray[i][7]; + if (tridraw) image->draw_triangle(p1,p2,p3,color); + if (edgedraw) { + image->draw_cylinder(p1,p2,color,fixflag2,3); + image->draw_cylinder(p2,p3,color,fixflag2,3); + image->draw_cylinder(p3,p1,color,fixflag2,3); + } + } else if (fixvec[i] == CYLINDER) { + if (fcolor) { + color = fcolor; + } else if (fixcolor == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]-fixflag2,(int)fixflag1); } } } @@ -2579,5 +2605,11 @@ int DumpImage::modify_param(int narg, char **arg) return 5; } + if (strcmp(arg[0],"fcolor") == 0) { + if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); + fcolor = image->color2rgb(arg[1]); + return 2; + } + return 0; } diff --git a/src/dump_image.h b/src/dump_image.h index d9c694a533c..9f5688212c8 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -99,6 +99,8 @@ class DumpImage : public DumpCustom { class AtomVecBody *avec_body; class Fix *fixptr; // ptr to Fix that provides image data + char *id_fix; + double *fcolor; // custom color choice for fix class Image *image; // class that renders each image From 242803aba0b3e8d0e7279c24f02948df8b1ec3c0 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 01:00:53 -0500 Subject: [PATCH 03/54] add support for rendering indenter objects with dump image --- doc/src/fix_indent.rst | 21 +++++++++++ examples/indent/in.indent | 78 ++++++++++++++++++++------------------- src/fix_indent.cpp | 65 ++++++++++++++++++++++++++++++-- src/fix_indent.h | 9 ++++- 4 files changed, 131 insertions(+), 42 deletions(-) diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 3e269654ac5..953811fb1aa 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -201,6 +201,27 @@ contains *xlat*, *ylat*, *zlat* keywords of the :doc:`thermo_style variable k equal 100.0/xlat/xlat fix 1 all indent $k sphere ... +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +Fix indent supports the *fix* keyword of :doc:`dump image `. +The fix will pass geometry information about the indenter to *dump +image* so that the indenter object will be included in the rendered +image. + +This feature currently only supports spherical and cylindrical +indenters. The *fflag1* setting of *dump image fix* has no impact on +rendering a spherical indenter. For a cylindrical indenter it +determines whether the cylinder is capped with a sphere at the ends: 0 +means no caps, 1 means the lower end is capped, 2 means the upper end is +capped, and 3 means both ends are capped. The *fflag2* setting allows +to adjust the radius of the rendered object. In many cases you want to +use a value > 0 so that the rendered indenter object does not obscure +atoms close to it. + + Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/examples/indent/in.indent b/examples/indent/in.indent index ba19e93cf51..77caaf2f174 100644 --- a/examples/indent/in.indent +++ b/examples/indent/in.indent @@ -1,67 +1,71 @@ # 2d indenter simulation -dimension 2 -boundary p s p +dimension 2 +boundary p s p -atom_style atomic -neighbor 0.3 bin -neigh_modify delay 5 +atom_style atomic +neighbor 0.3 bin +neigh_modify delay 5 # create geometry -lattice hex 0.9 -region box block 0 20 0 10 -0.25 0.25 -create_box 2 box -create_atoms 1 box +lattice hex 0.9 +region box block 0 20 0 10 -0.25 0.25 +create_box 2 box +create_atoms 1 box -mass 1 1.0 -mass 2 1.0 +mass 1 1.0 +mass 2 1.0 # LJ potentials -pair_style lj/cut 2.5 -pair_coeff * * 1.0 1.0 2.5 +pair_style lj/cut 2.5 +pair_coeff * * 1.0 1.0 2.5 # define groups -region 1 block INF INF INF 1.25 INF INF -group lower region 1 -group mobile subtract all lower -set group lower type 2 +region 1 block INF INF INF 1.25 INF INF +group lower region 1 +group mobile subtract all lower +set group lower type 2 # initial velocities -compute new mobile temp -velocity mobile create 0.2 482748 temp new -fix 1 all nve -fix 2 lower setforce 0.0 0.0 0.0 -fix 3 all temp/rescale 100 0.1 0.1 0.01 1.0 +compute new mobile temp +velocity mobile create 0.2 482748 temp new +fix 1 all nve +fix 2 lower setforce 0.0 0.0 0.0 +fix 3 all temp/rescale 100 0.1 0.1 0.01 1.0 # run with indenter -timestep 0.003 -variable k equal 1000.0/xlat +timestep 0.003 +variable k equal 1000.0/xlat variable y equal "13.0*ylat - step*dt*0.02*ylat" -fix 4 all indent $k sphere 10 v_y 0 5.0 -fix 5 all enforce2d +fix 4 all indent $k sphere 10 v_y 0 5.0 +fix 5 all enforce2d -thermo 1000 -thermo_modify temp new +thermo 1000 +thermo_modify temp new -#dump 1 all atom 250 dump.indent +#dump 1 all atom 250 dump.indent -#dump 2 all image 1000 image.*.jpg type type & -# zoom 1.6 adiam 1.5 -#dump_modify 2 pad 5 +#dump 2 all image 1000 image.*.jpg type type & +# zoom 1.6 adiam 1.5 fix 4 0.0 2.0 +#dump_modify 2 pad 5 fcolor purple -#dump 3 all movie 1000 movie.mpg type type & -# zoom 1.6 adiam 1.5 -#dump_modify 3 pad 5 +#dump 3 all movie 1000 movie.mpg type type & +# zoom 1.6 adiam 1.5 +#dump_modify 3 pad 5 -run 30000 +run 30000 # run without indenter unfix 4 -run 30000 +#undump 2 +#dump 2 all image 1000 image.*.jpg type type & +# zoom 1.6 adiam 1.5 +#dump_modify 2 pad 5 +run 30000 diff --git a/src/fix_indent.cpp b/src/fix_indent.cpp index f076d1a7492..857923b7fa8 100644 --- a/src/fix_indent.cpp +++ b/src/fix_indent.cpp @@ -19,10 +19,12 @@ #include "atom.h" #include "domain.h" +#include "dump_image.h" #include "error.h" #include "input.h" #include "lattice.h" #include "math_extra.h" +#include "memory.h" #include "modify.h" #include "respa.h" #include "update.h" @@ -41,7 +43,8 @@ enum { INSIDE, OUTSIDE }; FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), xstr(nullptr), ystr(nullptr), zstr(nullptr), rstr(nullptr), pstr(nullptr), - rlostr(nullptr), rhistr(nullptr), lostr(nullptr), histr(nullptr) + rlostr(nullptr), rhistr(nullptr), lostr(nullptr), histr(nullptr), imgobjs(nullptr), + imgparms(nullptr) { if (narg < 4) utils::missing_cmd_args(FLERR, "fix indent", error); @@ -111,13 +114,29 @@ FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : pvalue *= zscale; } else - error->all(FLERR, "Unknown fix indent keyword: {}", istyle); + error->all(FLERR, "Unknown fix indent style: {}", istyle); varflag = 0; if (xstr || ystr || zstr || rstr || pstr || rlostr || rhistr || lostr || histr) varflag = 1; indenter_flag = 0; indenter[0] = indenter[1] = indenter[2] = indenter[3] = 0.0; + + // set up indenter visualization + + if (istyle == SPHERE) { + // only one object to draw + memory->create(imgobjs, 1, "fix_indent:imgobjs"); + memory->create(imgparms, 1, 5, "fix_indent:imgparms"); + imgobjs[0] = DumpImage::SPHERE; + imgparms[0][0] = atom->ntypes; // use color of the last atom type + } else if (istyle == CYLINDER) { + // only one object to draw + memory->create(imgobjs, 1, "fix_indent:imgobjs"); + memory->create(imgparms, 1, 8, "fix_indent:imgparms"); + imgobjs[0] = DumpImage::CYLINDER; + imgparms[0][0] = atom->ntypes; // use color of the last atom type + } } /* ---------------------------------------------------------------------- */ @@ -133,6 +152,9 @@ FixIndent::~FixIndent() delete[] rhistr; delete[] lostr; delete[] histr; + + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -268,7 +290,7 @@ void FixIndent::post_force(int /*vflag*/) double radius = rstr ? input->variable->compute_equal(rvar) : rvalue; if (radius < 0.0) error->all(FLERR, "Illegal fix indent sphere radius: {}", radius); - for (int i = 0; i < nlocal; i++) + for (int i = 0; i < nlocal; i++) { if (mask[i] & groupbit) { delx = x[i][0] - ctr[0]; dely = x[i][1] - ctr[1]; @@ -294,6 +316,14 @@ void FixIndent::post_force(int /*vflag*/) indenter[2] -= fy; indenter[3] -= fz; } + } + + // store indenter object visualization parameters + + imgparms[0][1] = ctr[0]; + imgparms[0][2] = ctr[1]; + imgparms[0][3] = ctr[2]; + imgparms[0][4] = 2.0 * radius; // cylindrical indenter @@ -309,7 +339,7 @@ void FixIndent::post_force(int /*vflag*/) double radius{rstr ? input->variable->compute_equal(rvar) : rvalue}; if (radius < 0.0) error->all(FLERR, "Illegal fix indent cylinder radius: {}", radius); - for (int i = 0; i < nlocal; i++) + for (int i = 0; i < nlocal; i++) { if (mask[i] & groupbit) { double del[3] = {x[i][0] - ctr[0], x[i][1] - ctr[1], x[i][2] - ctr[2]}; del[cdim] = 0; @@ -334,6 +364,18 @@ void FixIndent::post_force(int /*vflag*/) indenter[2] -= fy; indenter[3] -= fz; } + } + + // store indenter object visualization parameters + + imgparms[0][1] = ctr[0]; + imgparms[0][2] = ctr[1]; + imgparms[0][3] = ctr[2]; + ctr[cdim] = domain->boxhi[cdim]; + imgparms[0][4] = ctr[0]; + imgparms[0][5] = ctr[1]; + imgparms[0][6] = ctr[2]; + imgparms[0][7] = 2.0 * radius; // conical indenter @@ -853,3 +895,18 @@ double FixIndent::closest(double *x, double *near, double *nearest, double dsq) nearest[2] = near[2]; return rsq; } + +/* ---------------------------------------------------------------------- + provide graphics information to dump image to render indenter +------------------------------------------------------------------------- */ +int FixIndent::image(int *&objs, double **&parms) +{ + objs = imgobjs; + parms = imgparms; + if (istyle == SPHERE) + return 1; + else if (istyle == CYLINDER) + return 1; + else + return 0; +} diff --git a/src/fix_indent.h b/src/fix_indent.h index 833f576b467..2b682a95c85 100644 --- a/src/fix_indent.h +++ b/src/fix_indent.h @@ -38,7 +38,9 @@ class FixIndent : public Fix { double compute_scalar() override; double compute_vector(int) override; - private: + int image(int *&, double **&) override; + +private: int istyle, scaleflag, side; double k, k3; char *xstr, *ystr, *zstr, *rstr, *pstr; @@ -53,6 +55,11 @@ class FixIndent : public Fix { int rlovar, rhivar, lovar, hivar; double rlovalue, rhivalue, lovalue, hivalue; + // arrays for dump image rendering + + int *imgobjs; + double **imgparms; + // methods for argument parsing int geometry(int, char **); From 4b440860355843bcd2f0a105166fb6c4c64084f6 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 01:15:03 -0500 Subject: [PATCH 04/54] add color name "none" which will return a null pointer --- src/image.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/image.cpp b/src/image.cpp index c806268617f..7fe44b856b0 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -1557,6 +1557,7 @@ double *Image::color2rgb(const char *color, int index) } if (color) { + if (strcmp(color,"none") == 0) return nullptr; for (int i = 0; i < ncolors; i++) if (strcmp(color,username[i]) == 0) return userrgb[i]; for (int i = 0; i < NCOLORS; i++) From 7e5f869383d756b0d5a340d4d0eaf02afa8fa5fc Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 01:18:40 -0500 Subject: [PATCH 05/54] document Fix::image() in fix modification/addition docs --- doc/src/Modify_fix.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/src/Modify_fix.rst b/doc/src/Modify_fix.rst index 18b51d6e137..ef7f06059d3 100644 --- a/doc/src/Modify_fix.rst +++ b/doc/src/Modify_fix.rst @@ -129,6 +129,8 @@ derived class. See ``src/fix.h`` for additional details. +---------------------------+--------------------------------------------------------------------------------------------+ | thermo | compute quantities for thermodynamic output (optional) | +---------------------------+--------------------------------------------------------------------------------------------+ +| image | pass lists of graphics objects and their parameters to :doc:`dump image fix | ++---------------------------+--------------------------------------------------------------------------------------------+ Typically, only a small fraction of these methods are defined for a particular fix. Setmask is mandatory, as it determines when the fix From bbea94110e3d41d4d9f966d52b98110aafe62092 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 01:35:04 -0500 Subject: [PATCH 06/54] small improvements and corrections suggested by GitHub Copilot --- doc/src/Modify_fix.rst | 2 +- examples/indent/in.indent | 2 +- src/dump_image.cpp | 8 ++++++++ src/fix_indent.cpp | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/src/Modify_fix.rst b/doc/src/Modify_fix.rst index ef7f06059d3..fd089efa5db 100644 --- a/doc/src/Modify_fix.rst +++ b/doc/src/Modify_fix.rst @@ -129,7 +129,7 @@ derived class. See ``src/fix.h`` for additional details. +---------------------------+--------------------------------------------------------------------------------------------+ | thermo | compute quantities for thermodynamic output (optional) | +---------------------------+--------------------------------------------------------------------------------------------+ -| image | pass lists of graphics objects and their parameters to :doc:`dump image fix | +| image | pass lists of graphics objects and their parameters to :doc:`dump image fix ` | +---------------------------+--------------------------------------------------------------------------------------------+ Typically, only a small fraction of these methods are defined for a diff --git a/examples/indent/in.indent b/examples/indent/in.indent index 77caaf2f174..2bbf6928ed9 100644 --- a/examples/indent/in.indent +++ b/examples/indent/in.indent @@ -52,7 +52,7 @@ thermo_modify temp new #dump 1 all atom 250 dump.indent #dump 2 all image 1000 image.*.jpg type type & -# zoom 1.6 adiam 1.5 fix 4 0.0 2.0 +# zoom 1.6 adiam 1.5 fix 4 type 0.0 2.0 #dump_modify 2 pad 5 fcolor purple #dump 3 all movie 1000 movie.mpg type type & diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 9f7496ceb6c..592e0ba80d1 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1705,6 +1705,8 @@ void DumpImage::create_image() } else if (fixcolor == TYPE) { itype = static_cast(fixarray[i][0]); color = colortype[itype]; + } else { + color = image->color2rgb("red"); } image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]-fixflag2); } else if (fixvec[i] == LINE) { @@ -1713,6 +1715,8 @@ void DumpImage::create_image() } else if (fixcolor == TYPE) { itype = static_cast(fixarray[i][0]); color = colortype[itype]; + } else { + color = image->color2rgb("red"); } image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixflag1,3); } else if (fixvec[i] == TRI) { @@ -1721,6 +1725,8 @@ void DumpImage::create_image() } else if (fixcolor == TYPE) { itype = static_cast(fixarray[i][0]); color = colortype[itype]; + } else { + color = image->color2rgb("red"); } p1 = &fixarray[i][1]; p2 = &fixarray[i][4]; @@ -1737,6 +1743,8 @@ void DumpImage::create_image() } else if (fixcolor == TYPE) { itype = static_cast(fixarray[i][0]); color = colortype[itype]; + } else { + color = image->color2rgb("red"); } image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]-fixflag2,(int)fixflag1); } diff --git a/src/fix_indent.cpp b/src/fix_indent.cpp index 857923b7fa8..2d02d3a3990 100644 --- a/src/fix_indent.cpp +++ b/src/fix_indent.cpp @@ -129,13 +129,13 @@ FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 5, "fix_indent:imgparms"); imgobjs[0] = DumpImage::SPHERE; - imgparms[0][0] = atom->ntypes; // use color of the last atom type + imgparms[0][0] = 1; // use color of first atom type } else if (istyle == CYLINDER) { // only one object to draw memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 8, "fix_indent:imgparms"); imgobjs[0] = DumpImage::CYLINDER; - imgparms[0][0] = atom->ntypes; // use color of the last atom type + imgparms[0][0] = 1; // use color of first atom type } } From 0c3e5139ab3bd6def402179ada1d0c56c68c75d5 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 12:11:08 -0500 Subject: [PATCH 07/54] invert logic for fflag2 dump image settings to avoid double negative adjustments --- doc/src/fix_indent.rst | 5 +++-- examples/indent/in.indent | 2 +- src/dump_image.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 953811fb1aa..da9086c7f9c 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -217,8 +217,9 @@ rendering a spherical indenter. For a cylindrical indenter it determines whether the cylinder is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. The *fflag2* setting allows -to adjust the radius of the rendered object. In many cases you want to -use a value > 0 so that the rendered indenter object does not obscure +to adjust the radius of the rendered object for spherical and +cylindrical indenders. In many cases you want to use a value < 0 to +reduce the radius of the rendered object to that it does not obscure atoms close to it. diff --git a/examples/indent/in.indent b/examples/indent/in.indent index 2bbf6928ed9..6338d86fa5b 100644 --- a/examples/indent/in.indent +++ b/examples/indent/in.indent @@ -52,7 +52,7 @@ thermo_modify temp new #dump 1 all atom 250 dump.indent #dump 2 all image 1000 image.*.jpg type type & -# zoom 1.6 adiam 1.5 fix 4 type 0.0 2.0 +# zoom 1.6 adiam 1.5 fix 4 type 0.0 -2.0 #dump_modify 2 pad 5 fcolor purple #dump 3 all movie 1000 movie.mpg type type & diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 592e0ba80d1..59143cb7deb 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1708,7 +1708,7 @@ void DumpImage::create_image() } else { color = image->color2rgb("red"); } - image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]-fixflag2); + image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]+fixflag2); } else if (fixvec[i] == LINE) { if (fcolor) { color = fcolor; @@ -1746,7 +1746,7 @@ void DumpImage::create_image() } else { color = image->color2rgb("red"); } - image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]-fixflag2,(int)fixflag1); + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]+fixflag2,(int)fixflag1); } } } From e7ea8315ac70c856bcf0a7af199b61811b3190db Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 12:11:41 -0500 Subject: [PATCH 08/54] add support for planar indenter --- doc/src/fix_indent.rst | 4 +- src/dump_image.cpp | 10 +++++ src/dump_image.h | 4 +- src/fix_indent.cpp | 85 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index da9086c7f9c..64d1a643fa4 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -211,9 +211,9 @@ The fix will pass geometry information about the indenter to *dump image* so that the indenter object will be included in the rendered image. -This feature currently only supports spherical and cylindrical +This feature currently only supports spherical, cylindrical, and planar indenters. The *fflag1* setting of *dump image fix* has no impact on -rendering a spherical indenter. For a cylindrical indenter it +rendering a spherical or planar indenter. For a cylindrical indenter it determines whether the cylinder is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. The *fflag2* setting allows diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 59143cb7deb..999643013e2 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1747,7 +1747,17 @@ void DumpImage::create_image() color = image->color2rgb("red"); } image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]+fixflag2,(int)fixflag1); + } else if (fixvec[i] == TRIANGLE) { + if (fcolor) { + color = fcolor; + } else if (fixcolor == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } else { + color = image->color2rgb("red"); + } } + image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); } } } diff --git a/src/dump_image.h b/src/dump_image.h index 9f5688212c8..af9b74b7730 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -26,8 +26,8 @@ namespace LAMMPS_NS { class Region; class DumpImage : public DumpCustom { public: - int multifile_override; // used by write_dump command - enum { SPHERE, LINE, TRI, CYLINDER }; // used by some Body and Fix child classes + int multifile_override; // used by write_dump command + enum { SPHERE, LINE, TRI, CYLINDER, TRIANGLE }; // used by some Body and Fix child classes DumpImage(class LAMMPS *, int, char **); ~DumpImage() override; diff --git a/src/fix_indent.cpp b/src/fix_indent.cpp index 2d02d3a3990..da6884f852e 100644 --- a/src/fix_indent.cpp +++ b/src/fix_indent.cpp @@ -125,17 +125,25 @@ FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : // set up indenter visualization if (istyle == SPHERE) { - // only one object to draw + // only one sphere object to draw memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 5, "fix_indent:imgparms"); imgobjs[0] = DumpImage::SPHERE; imgparms[0][0] = 1; // use color of first atom type } else if (istyle == CYLINDER) { - // only one object to draw + // only one cylinder object to draw memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 8, "fix_indent:imgparms"); imgobjs[0] = DumpImage::CYLINDER; imgparms[0][0] = 1; // use color of first atom type + } else if (istyle == PLANE) { + // two triangle objects to draw + memory->create(imgobjs, 2, "fix_indent:imgobjs"); + memory->create(imgparms, 2, 10, "fix_indent:imgparms"); + imgobjs[0] = DumpImage::TRIANGLE; + imgobjs[1] = DumpImage::TRIANGLE; + imgparms[0][0] = 1; // use color of first atom type by default + imgparms[1][0] = 1; // use color of first atom type by default } } @@ -366,7 +374,7 @@ void FixIndent::post_force(int /*vflag*/) } } - // store indenter object visualization parameters + // store indenter object visualization parameters: positions of cylinder edges and diameter imgparms[0][1] = ctr[0]; imgparms[0][2] = ctr[1]; @@ -451,7 +459,7 @@ void FixIndent::post_force(int /*vflag*/) double plane{pstr ? input->variable->compute_equal(pvar) : pvalue}; - for (int i = 0; i < nlocal; i++) + for (int i = 0; i < nlocal; i++) { if (mask[i] & groupbit) { dr = planeside * (plane - x[i][cdim]); if (dr >= 0.0) continue; @@ -460,6 +468,72 @@ void FixIndent::post_force(int /*vflag*/) indenter[0] -= k3 * dr * dr * dr; indenter[cdim + 1] -= fmag; } + } + + // store indenter object visualization parameters: positions of cylinder edges and diameter + + switch (cdim) { + case 0: + imgparms[0][1] = planeside * plane; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = planeside * plane; + imgparms[0][5] = domain->boxhi[1]; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = planeside * plane; + imgparms[0][8] = domain->boxlo[1]; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = planeside * plane; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = planeside * plane; + imgparms[1][5] = domain->boxlo[1]; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = planeside * plane; + imgparms[1][8] = domain->boxhi[1]; + imgparms[1][9] = domain->boxlo[2]; + break; + case 1: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = planeside * plane; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = planeside * plane; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = planeside * plane; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = planeside * plane; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = planeside * plane; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = planeside * plane; + imgparms[1][9] = domain->boxlo[2]; + break; + case 2: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = planeside * plane; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = domain->boxlo[1]; + imgparms[0][6] = planeside * plane; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = domain->boxhi[1]; + imgparms[0][9] = planeside * plane; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = planeside * plane; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = domain->boxhi[1]; + imgparms[1][6] = planeside * plane; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = domain->boxlo[1]; + imgparms[1][9] = planeside * plane; + break; + } } if (varflag) modify->addstep_compute(update->ntimestep + 1); @@ -898,6 +972,7 @@ double FixIndent::closest(double *x, double *near, double *nearest, double dsq) /* ---------------------------------------------------------------------- provide graphics information to dump image to render indenter + data has been copied to dedicated storage during fix indent execution ------------------------------------------------------------------------- */ int FixIndent::image(int *&objs, double **&parms) { @@ -907,6 +982,8 @@ int FixIndent::image(int *&objs, double **&parms) return 1; else if (istyle == CYLINDER) return 1; + else if (istyle == PLANE) + return 2; else return 0; } From 6df3e016bb5dc0e79b6cfaa1dccb8612e11c4a90 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 16:09:38 -0500 Subject: [PATCH 09/54] apply clang-format --- src/fix_indent.cpp | 122 ++++++++++++++++++++++----------------------- src/fix_indent.h | 2 +- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/fix_indent.cpp b/src/fix_indent.cpp index da6884f852e..0b9ed939e3d 100644 --- a/src/fix_indent.cpp +++ b/src/fix_indent.cpp @@ -453,7 +453,7 @@ void FixIndent::post_force(int /*vflag*/) // planar indenter - } else { + } else { // if (istyle == PLANE) // plane = current plane position @@ -473,66 +473,66 @@ void FixIndent::post_force(int /*vflag*/) // store indenter object visualization parameters: positions of cylinder edges and diameter switch (cdim) { - case 0: - imgparms[0][1] = planeside * plane; - imgparms[0][2] = domain->boxlo[1]; - imgparms[0][3] = domain->boxlo[2]; - imgparms[0][4] = planeside * plane; - imgparms[0][5] = domain->boxhi[1]; - imgparms[0][6] = domain->boxlo[2]; - imgparms[0][7] = planeside * plane; - imgparms[0][8] = domain->boxlo[1]; - imgparms[0][9] = domain->boxhi[2]; - imgparms[1][1] = planeside * plane; - imgparms[1][2] = domain->boxhi[1]; - imgparms[1][3] = domain->boxhi[2]; - imgparms[1][4] = planeside * plane; - imgparms[1][5] = domain->boxlo[1]; - imgparms[1][6] = domain->boxhi[2]; - imgparms[1][7] = planeside * plane; - imgparms[1][8] = domain->boxhi[1]; - imgparms[1][9] = domain->boxlo[2]; - break; - case 1: - imgparms[0][1] = domain->boxlo[0]; - imgparms[0][2] = planeside * plane; - imgparms[0][3] = domain->boxlo[2]; - imgparms[0][4] = domain->boxhi[0]; - imgparms[0][5] = planeside * plane; - imgparms[0][6] = domain->boxlo[2]; - imgparms[0][7] = domain->boxlo[0]; - imgparms[0][8] = planeside * plane; - imgparms[0][9] = domain->boxhi[2]; - imgparms[1][1] = domain->boxhi[0]; - imgparms[1][2] = planeside * plane; - imgparms[1][3] = domain->boxhi[2]; - imgparms[1][4] = domain->boxlo[0]; - imgparms[1][5] = planeside * plane; - imgparms[1][6] = domain->boxhi[2]; - imgparms[1][7] = domain->boxhi[0]; - imgparms[1][8] = planeside * plane; - imgparms[1][9] = domain->boxlo[2]; - break; - case 2: - imgparms[0][1] = domain->boxlo[0]; - imgparms[0][2] = domain->boxlo[1]; - imgparms[0][3] = planeside * plane; - imgparms[0][4] = domain->boxhi[0]; - imgparms[0][5] = domain->boxlo[1]; - imgparms[0][6] = planeside * plane; - imgparms[0][7] = domain->boxlo[0]; - imgparms[0][8] = domain->boxhi[1]; - imgparms[0][9] = planeside * plane; - imgparms[1][1] = domain->boxhi[0]; - imgparms[1][2] = domain->boxhi[1]; - imgparms[1][3] = planeside * plane; - imgparms[1][4] = domain->boxlo[0]; - imgparms[1][5] = domain->boxhi[1]; - imgparms[1][6] = planeside * plane; - imgparms[1][7] = domain->boxhi[0]; - imgparms[1][8] = domain->boxlo[1]; - imgparms[1][9] = planeside * plane; - break; + case 0: + imgparms[0][1] = planeside * plane; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = planeside * plane; + imgparms[0][5] = domain->boxhi[1]; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = planeside * plane; + imgparms[0][8] = domain->boxlo[1]; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = planeside * plane; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = planeside * plane; + imgparms[1][5] = domain->boxlo[1]; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = planeside * plane; + imgparms[1][8] = domain->boxhi[1]; + imgparms[1][9] = domain->boxlo[2]; + break; + case 1: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = planeside * plane; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = planeside * plane; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = planeside * plane; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = planeside * plane; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = planeside * plane; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = planeside * plane; + imgparms[1][9] = domain->boxlo[2]; + break; + case 2: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = planeside * plane; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = domain->boxlo[1]; + imgparms[0][6] = planeside * plane; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = domain->boxhi[1]; + imgparms[0][9] = planeside * plane; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = planeside * plane; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = domain->boxhi[1]; + imgparms[1][6] = planeside * plane; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = domain->boxlo[1]; + imgparms[1][9] = planeside * plane; + break; } } diff --git a/src/fix_indent.h b/src/fix_indent.h index 2b682a95c85..01b54657673 100644 --- a/src/fix_indent.h +++ b/src/fix_indent.h @@ -40,7 +40,7 @@ class FixIndent : public Fix { int image(int *&, double **&) override; -private: + private: int istyle, scaleflag, side; double k, k3; char *xstr, *ystr, *zstr, *rstr, *pstr; From 62c37e3e1259e74cf96e6ccb05a33c25ca1a0cb1 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 17:16:32 -0500 Subject: [PATCH 10/54] add dump image support to walls derived from FixWall --- doc/src/dump_image.rst | 9 +++++ doc/src/fix_indent.rst | 1 - doc/src/fix_wall.rst | 67 ++++++++++++++++++------------- src/fix_wall.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++ src/fix_wall.h | 4 ++ 5 files changed, 141 insertions(+), 29 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 6b7f3909154..5ad827fecaf 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -510,6 +510,15 @@ The *fix* keyword can be used with a :doc:`fix ` that produces objects to be drawn. Below is a list of supported fixes: * :doc:`fix indent ` +* :doc:`fix wall/lj93 ` +* :doc:`fix wall/lj126 ` +* :doc:`fix wall/lj1043 ` +* :doc:`fix wall/colloid ` +* :doc:`fix wall/harmonic ` +* :doc:`fix wall/harmonic/outside ` +* :doc:`fix wall/lepton ` +* :doc:`fix wall/morse ` +* :doc:`fix wall/table ` The *fflag1* and *fflag2* settings are numerical values which are used by *dump image* to adjust how the drawing of the objects communicated diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 64d1a643fa4..09eb96349d5 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -222,7 +222,6 @@ cylindrical indenders. In many cases you want to use a value < 0 to reduce the radius of the rendered object to that it does not obscure atoms close to it. - Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/src/fix_wall.rst b/doc/src/fix_wall.rst index c570d85c395..00628cbbb65 100644 --- a/doc/src/fix_wall.rst +++ b/doc/src/fix_wall.rst @@ -497,49 +497,60 @@ if you want the interpolation tables of length Ntable to match exactly what is in the tabulated file (with effectively no preliminary interpolation), you should set Ntable = Nfile. ----------- +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +These wall fixes support the *fix* keyword of :doc:`dump image `. +The fixes will pass geometry information about the walls to *dump +image* so that the walls will be included in the rendered image. + +Neither the *fflag1* setting nor the *fflag2* setting of *dump image fix* +has any impact on the rendered image. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" -No information about this fix is written to :doc:`binary restart files -`. +No information about these fixes are written to :doc:`binary restart +files `. -The :doc:`fix_modify ` *energy* option is supported by -this fix to add the energy of interaction between atoms and all the -specified walls to the global potential energy of the system as part -of :doc:`thermodynamic output `. The default setting -for this fix is :doc:`fix_modify energy no `. +The :doc:`fix_modify ` *energy* option is supported by these +fixes to add the energy of interaction between atoms and all the +specified walls to the global potential energy of the system as part of +:doc:`thermodynamic output `. The default setting for +these fixes is :doc:`fix_modify energy no `. The :doc:`fix_modify ` *virial* option is supported by -this fix to add the contribution due to the interaction between atoms +these fixes to add the contribution due to the interaction between atoms and all the specified walls to both the global pressure and per-atom stress of the system via the :doc:`compute pressure ` and :doc:`compute stress/atom ` commands. The former can be accessed by :doc:`thermodynamic output `. The default setting for -this fix is :doc:`fix_modify virial no `. +these fixes is :doc:`fix_modify virial no `. -The :doc:`fix_modify ` *respa* option is supported by this -fix. This allows to set at which level of the :doc:`r-RESPA -` integrator the fix is adding its forces. Default is the +The :doc:`fix_modify ` *respa* option is supported by these +fixes. This allows to set at which level of the :doc:`r-RESPA +` integrator a fix is adding its forces. Default is the outermost level. -This fix computes a global scalar energy and a global vector of forces, -which can be accessed by various :doc:`output commands `. -Note that the scalar energy is the sum of interactions with all defined -walls. If you want the energy on a per-wall basis, you need to use -multiple fix wall commands. The length of the vector is equal to the -number of walls defined by the fix. Each vector value is the normal -force on a specific wall. Note that an outward force on a wall will be -a negative value for *lo* walls and a positive value for *hi* walls. -The scalar and vector values calculated by this fix are "extensive". - -No parameter of this fix can be used with the *start/stop* keywords of -the :doc:`run ` command. - -The forces due to this fix are imposed during an energy minimization, -invoked by the :doc:`minimize ` command. +These fixes compute a global scalar energy and a global vector of +forces, which can be accessed by various :doc:`output commands +`. Note that the scalar energy is the sum of interactions +with all defined walls. If you want the energy on a per-wall basis, you +need to use multiple fix wall commands. The length of the vector is +equal to the number of walls defined by the fix. Each vector value is +the normal force on a specific wall. Note that an outward force on a +wall will be a negative value for *lo* walls and a positive value for +*hi* walls. The scalar and vector values calculated by this fix are +"extensive". + +No parameter of these fixes can be used with the *start/stop* keywords +of the :doc:`run ` command. + +The forces due to these fixes *are* imposed during an energy +minimization, invoked by the :doc:`minimize ` command. .. note:: diff --git a/src/fix_wall.cpp b/src/fix_wall.cpp index 303994c5736..5df065b899b 100644 --- a/src/fix_wall.cpp +++ b/src/fix_wall.cpp @@ -14,9 +14,11 @@ #include "fix_wall.h" #include "domain.h" +#include "dump_image.h" #include "error.h" #include "input.h" #include "lattice.h" +#include "memory.h" #include "modify.h" #include "respa.h" #include "update.h" @@ -232,6 +234,16 @@ FixWall::FixWall(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), nwall eflag = 0; for (int m = 0; m <= nwall; m++) ewall[m] = 0.0; + + // for rendering walls with dump image: two triangle objects per wall to draw + memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); + memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); + for (int m = 0; m < nwall; ++m) { + imgobjs[2 * m] = DumpImage::TRIANGLE; + imgobjs[2 * m + 1] = DumpImage::TRIANGLE; + imgparms[2 * m][0] = 1; // use color of first atom type by default + imgparms[2 * m + 1][0] = 1; // use color of first atom type by default + } } /* ---------------------------------------------------------------------- */ @@ -377,6 +389,71 @@ void FixWall::post_force(int vflag) } wall_particle(m, wallwhich[m], coord); + switch (wallwhich[m]) { + case XLO: // fallthrough + case XHI: + imgparms[2 * m][1] = coord; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = coord; + imgparms[2 * m][5] = domain->boxhi[1]; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = coord; + imgparms[2 * m][8] = domain->boxlo[1]; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = coord; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = coord; + imgparms[2 * m + 1][5] = domain->boxlo[1]; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = coord; + imgparms[2 * m + 1][8] = domain->boxhi[1]; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case YLO: // fallthrough + case YHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = coord; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = coord; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = coord; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = coord; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = coord; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = coord; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case ZLO: // fallthrough + case ZHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = coord; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = domain->boxlo[1]; + imgparms[2 * m][6] = coord; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = domain->boxhi[1]; + imgparms[2 * m][9] = coord; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = coord; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = domain->boxhi[1]; + imgparms[2 * m + 1][6] = coord; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = domain->boxlo[1]; + imgparms[2 * m + 1][9] = coord; + break; + } } if (varflag) modify->addstep_compute(update->ntimestep + 1); @@ -425,3 +502,15 @@ double FixWall::compute_vector(int n) } return ewall_all[n + 1]; } + +/* ---------------------------------------------------------------------- + provide graphics information to dump image to render wall as plane + data has been copied to dedicated storage during fix indent execution +------------------------------------------------------------------------- */ + +int FixWall::image(int *&objs, double **&parms) +{ + objs = imgobjs; + parms = imgparms; + return 2*nwall; +} diff --git a/src/fix_wall.h b/src/fix_wall.h index 871f4acf495..f7b8d4bcae3 100644 --- a/src/fix_wall.h +++ b/src/fix_wall.h @@ -43,6 +43,8 @@ class FixWall : public Fix { double compute_scalar() override; double compute_vector(int) override; + int image(int *&, double **&) override; + virtual void precompute(int) = 0; virtual void wall_particle(int, int, double) = 0; @@ -57,6 +59,8 @@ class FixWall : public Fix { int eflag; // per-wall flag for energy summation int ilevel_respa; int fldflag; + int *imgobjs; + double **imgparms; }; } // namespace LAMMPS_NS From 84d8f0d8d0be328066b6d76ce66c084b3eeb279f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 22:29:02 -0500 Subject: [PATCH 11/54] render planar indenters or walls as cylinders in 2d. --- doc/src/fix_indent.rst | 32 ++++--- doc/src/fix_wall.rst | 22 +++-- src/dump_image.cpp | 5 +- src/fix_indent.cpp | 183 ++++++++++++++++++++++---------------- src/fix_wall.cpp | 194 +++++++++++++++++++++++++---------------- 5 files changed, 270 insertions(+), 166 deletions(-) diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 09eb96349d5..9b2c33862a0 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -209,18 +209,26 @@ Dump image info Fix indent supports the *fix* keyword of :doc:`dump image `. The fix will pass geometry information about the indenter to *dump image* so that the indenter object will be included in the rendered -image. - -This feature currently only supports spherical, cylindrical, and planar -indenters. The *fflag1* setting of *dump image fix* has no impact on -rendering a spherical or planar indenter. For a cylindrical indenter it -determines whether the cylinder is capped with a sphere at the ends: 0 -means no caps, 1 means the lower end is capped, 2 means the upper end is -capped, and 3 means both ends are capped. The *fflag2* setting allows -to adjust the radius of the rendered object for spherical and -cylindrical indenders. In many cases you want to use a value < 0 to -reduce the radius of the rendered object to that it does not obscure -atoms close to it. +image. This feature currently only supports spherical, cylindrical, and +planar indenters. Please note, that for :doc:`2d systems `, +a planar indenter rendered as a plane would be invisible and it is thus +rendered as a cylinder. + +The *fflag1* setting of *dump image fix* has no impact on rendering a +spherical indenter or a planar indenter in 3d systems. For a +cylindrical indenter and a planar indenter in 2d systems it determines +whether the cylinder is capped with a sphere at the ends: 0 means no +caps, 1 means the lower end is capped, 2 means the upper end is capped, +and 3 means both ends are capped. + +The *fflag2* setting allows to adjust the radius of the rendered object +for spherical indenters, cylindrical indenders, and planar indenters in +2d systems. In many cases you want to use a value < 0 to reduce the +radius of the rendered object to that it does not obscure atoms close to +it. For a planar indenter in 2d systems, it should be set to a value > +0 or the indenter will not be visible since the diameter is set +internally to zero in that case due to lack of a suitable heuristic for +deriving a meaningful diameter. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/src/fix_wall.rst b/doc/src/fix_wall.rst index 00628cbbb65..568ffdf87f1 100644 --- a/doc/src/fix_wall.rst +++ b/doc/src/fix_wall.rst @@ -502,12 +502,22 @@ Dump image info .. versionadded:: TBD -These wall fixes support the *fix* keyword of :doc:`dump image `. -The fixes will pass geometry information about the walls to *dump -image* so that the walls will be included in the rendered image. - -Neither the *fflag1* setting nor the *fflag2* setting of *dump image fix* -has any impact on the rendered image. +These wall fixes support the *fix* keyword of :doc:`dump image +`. The fixes will pass geometry information about the walls +to *dump image* so that the walls will be included in the rendered +image. Please note, that for :doc:`2d systems `, a wall +rendered as a plane would be invisible and it is thus rendered as a +cylinder. + +The *fflag1* setting and the *fflag2* setting of *dump image fix* are +only relevant for 2d systems. The *fflag1* setting determines whether +the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +means the lower end is capped, 2 means the upper end is capped, and 3 +means both ends are capped. The *fflag2* setting allows to adjust the +radius of the rendered cylinder. It should be set to a value > 0 or the +cylinder will not be visible since the diameter is set internally to +zero due to lack of a suitable heuristic for deriving a meaningful +diameter for all types of walls. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 999643013e2..526dfd4ea8f 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1746,7 +1746,8 @@ void DumpImage::create_image() } else { color = image->color2rgb("red"); } - image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixarray[i][7]+fixflag2,(int)fixflag1); + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color, + fixarray[i][7]+fixflag2,(int)fixflag1); } else if (fixvec[i] == TRIANGLE) { if (fcolor) { color = fcolor; @@ -1756,8 +1757,8 @@ void DumpImage::create_image() } else { color = image->color2rgb("red"); } + image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); } - image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); } } } diff --git a/src/fix_indent.cpp b/src/fix_indent.cpp index 0b9ed939e3d..0d98d43ba6f 100644 --- a/src/fix_indent.cpp +++ b/src/fix_indent.cpp @@ -59,7 +59,7 @@ FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : ilevel_respa = 0; k = utils::numeric(FLERR, arg[3], false, lmp); - if (k < 0.0) error->all(FLERR, "Illegal fix indent force constant: {}", k); + if (k < 0.0) error->all(FLERR, 3, "Illegal fix indent force constant: {}", k); k3 = k / 3.0; // read geometry of indenter and optional args @@ -125,25 +125,33 @@ FixIndent::FixIndent(LAMMPS *lmp, int narg, char **arg) : // set up indenter visualization if (istyle == SPHERE) { - // only one sphere object to draw + // one sphere object to draw memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 5, "fix_indent:imgparms"); imgobjs[0] = DumpImage::SPHERE; imgparms[0][0] = 1; // use color of first atom type } else if (istyle == CYLINDER) { - // only one cylinder object to draw + // one cylinder object to draw memory->create(imgobjs, 1, "fix_indent:imgobjs"); memory->create(imgparms, 1, 8, "fix_indent:imgparms"); imgobjs[0] = DumpImage::CYLINDER; imgparms[0][0] = 1; // use color of first atom type } else if (istyle == PLANE) { - // two triangle objects to draw - memory->create(imgobjs, 2, "fix_indent:imgobjs"); - memory->create(imgparms, 2, 10, "fix_indent:imgparms"); - imgobjs[0] = DumpImage::TRIANGLE; - imgobjs[1] = DumpImage::TRIANGLE; - imgparms[0][0] = 1; // use color of first atom type by default - imgparms[1][0] = 1; // use color of first atom type by default + if (domain->dimension == 2) { + // one cylinder object to draw in 2d + memory->create(imgobjs, 1, "fix_indent:imgobjs"); + memory->create(imgparms, 1, 8, "fix_indent:imgparms"); + imgobjs[0] = DumpImage::CYLINDER; + imgparms[0][0] = 1; // use color of first atom type + } else { + // two triangle objects to draw in 3d + memory->create(imgobjs, 2, "fix_indent:imgobjs"); + memory->create(imgparms, 2, 10, "fix_indent:imgparms"); + imgobjs[0] = DumpImage::TRIANGLE; + imgobjs[1] = DumpImage::TRIANGLE; + imgparms[0][0] = 1; // use color of first atom type by default + imgparms[1][0] = 1; // use color of first atom type by default + } } } @@ -470,69 +478,95 @@ void FixIndent::post_force(int /*vflag*/) } } - // store indenter object visualization parameters: positions of cylinder edges and diameter + // store indenter object visualization parameters - switch (cdim) { - case 0: - imgparms[0][1] = planeside * plane; - imgparms[0][2] = domain->boxlo[1]; - imgparms[0][3] = domain->boxlo[2]; - imgparms[0][4] = planeside * plane; - imgparms[0][5] = domain->boxhi[1]; - imgparms[0][6] = domain->boxlo[2]; - imgparms[0][7] = planeside * plane; - imgparms[0][8] = domain->boxlo[1]; - imgparms[0][9] = domain->boxhi[2]; - imgparms[1][1] = planeside * plane; - imgparms[1][2] = domain->boxhi[1]; - imgparms[1][3] = domain->boxhi[2]; - imgparms[1][4] = planeside * plane; - imgparms[1][5] = domain->boxlo[1]; - imgparms[1][6] = domain->boxhi[2]; - imgparms[1][7] = planeside * plane; - imgparms[1][8] = domain->boxhi[1]; - imgparms[1][9] = domain->boxlo[2]; - break; - case 1: - imgparms[0][1] = domain->boxlo[0]; - imgparms[0][2] = planeside * plane; - imgparms[0][3] = domain->boxlo[2]; - imgparms[0][4] = domain->boxhi[0]; - imgparms[0][5] = planeside * plane; - imgparms[0][6] = domain->boxlo[2]; - imgparms[0][7] = domain->boxlo[0]; - imgparms[0][8] = planeside * plane; - imgparms[0][9] = domain->boxhi[2]; - imgparms[1][1] = domain->boxhi[0]; - imgparms[1][2] = planeside * plane; - imgparms[1][3] = domain->boxhi[2]; - imgparms[1][4] = domain->boxlo[0]; - imgparms[1][5] = planeside * plane; - imgparms[1][6] = domain->boxhi[2]; - imgparms[1][7] = domain->boxhi[0]; - imgparms[1][8] = planeside * plane; - imgparms[1][9] = domain->boxlo[2]; - break; - case 2: - imgparms[0][1] = domain->boxlo[0]; - imgparms[0][2] = domain->boxlo[1]; - imgparms[0][3] = planeside * plane; - imgparms[0][4] = domain->boxhi[0]; - imgparms[0][5] = domain->boxlo[1]; - imgparms[0][6] = planeside * plane; - imgparms[0][7] = domain->boxlo[0]; - imgparms[0][8] = domain->boxhi[1]; - imgparms[0][9] = planeside * plane; - imgparms[1][1] = domain->boxhi[0]; - imgparms[1][2] = domain->boxhi[1]; - imgparms[1][3] = planeside * plane; - imgparms[1][4] = domain->boxlo[0]; - imgparms[1][5] = domain->boxhi[1]; - imgparms[1][6] = planeside * plane; - imgparms[1][7] = domain->boxhi[0]; - imgparms[1][8] = domain->boxlo[1]; - imgparms[1][9] = planeside * plane; - break; + if (domain->dimension == 2) { + switch (cdim) { + case 0: + imgparms[0][1] = planeside * plane; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = 0.0; + imgparms[0][4] = planeside * plane; + imgparms[0][5] = domain->boxhi[1]; + imgparms[0][6] = 0.0; + imgparms[0][7] = 0.0; // no simple guess for diameter. need to use fflag2 to adjust + break; + case 1: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = planeside * plane; + imgparms[0][3] = 0.0; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = planeside * plane; + imgparms[0][6] = 0.0; + imgparms[0][7] = 0.0; // no simple guess for diameter. need to use fflag2 to adjust + break; + case 2:; // no planar indenter allowed in z-direction for 2d systems + break; + } + } else { + // two triangles + switch (cdim) { + case 0: + imgparms[0][1] = planeside * plane; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = planeside * plane; + imgparms[0][5] = domain->boxhi[1]; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = planeside * plane; + imgparms[0][8] = domain->boxlo[1]; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = planeside * plane; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = planeside * plane; + imgparms[1][5] = domain->boxlo[1]; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = planeside * plane; + imgparms[1][8] = domain->boxhi[1]; + imgparms[1][9] = domain->boxlo[2]; + break; + case 1: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = planeside * plane; + imgparms[0][3] = domain->boxlo[2]; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = planeside * plane; + imgparms[0][6] = domain->boxlo[2]; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = planeside * plane; + imgparms[0][9] = domain->boxhi[2]; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = planeside * plane; + imgparms[1][3] = domain->boxhi[2]; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = planeside * plane; + imgparms[1][6] = domain->boxhi[2]; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = planeside * plane; + imgparms[1][9] = domain->boxlo[2]; + break; + case 2: + imgparms[0][1] = domain->boxlo[0]; + imgparms[0][2] = domain->boxlo[1]; + imgparms[0][3] = planeside * plane; + imgparms[0][4] = domain->boxhi[0]; + imgparms[0][5] = domain->boxlo[1]; + imgparms[0][6] = planeside * plane; + imgparms[0][7] = domain->boxlo[0]; + imgparms[0][8] = domain->boxhi[1]; + imgparms[0][9] = planeside * plane; + imgparms[1][1] = domain->boxhi[0]; + imgparms[1][2] = domain->boxhi[1]; + imgparms[1][3] = planeside * plane; + imgparms[1][4] = domain->boxlo[0]; + imgparms[1][5] = domain->boxhi[1]; + imgparms[1][6] = planeside * plane; + imgparms[1][7] = domain->boxhi[0]; + imgparms[1][8] = domain->boxlo[1]; + imgparms[1][9] = planeside * plane; + break; + } } } @@ -983,7 +1017,10 @@ int FixIndent::image(int *&objs, double **&parms) else if (istyle == CYLINDER) return 1; else if (istyle == PLANE) - return 2; + if (domain->dimension == 2) + return 1; + else + return 2; else return 0; } diff --git a/src/fix_wall.cpp b/src/fix_wall.cpp index 5df065b899b..8b993d523a5 100644 --- a/src/fix_wall.cpp +++ b/src/fix_wall.cpp @@ -235,14 +235,25 @@ FixWall::FixWall(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), nwall eflag = 0; for (int m = 0; m <= nwall; m++) ewall[m] = 0.0; - // for rendering walls with dump image: two triangle objects per wall to draw - memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); - memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); - for (int m = 0; m < nwall; ++m) { - imgobjs[2 * m] = DumpImage::TRIANGLE; - imgobjs[2 * m + 1] = DumpImage::TRIANGLE; - imgparms[2 * m][0] = 1; // use color of first atom type by default - imgparms[2 * m + 1][0] = 1; // use color of first atom type by default + // for rendering walls with dump image. + if (domain->dimension == 2) { + // one cylinder object per wall to draw in 2d + memory->create(imgobjs, nwall, "fix_indent:imgobjs"); + memory->create(imgparms, nwall, 8, "fix_indent:imgparms"); + for (int m = 0; m < nwall; ++m) { + imgobjs[m] = DumpImage::CYLINDER; + imgparms[m][0] = 1; // use color of first atom type by default + } + } else { + // two triangle objects per wall to draw in 3d + memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); + memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); + for (int m = 0; m < nwall; ++m) { + imgobjs[2 * m] = DumpImage::TRIANGLE; + imgobjs[2 * m + 1] = DumpImage::TRIANGLE; + imgparms[2 * m][0] = 1; // use color of first atom type by default + imgparms[2 * m + 1][0] = 1; // use color of first atom type by default + } } } @@ -260,6 +271,9 @@ FixWall::~FixWall() delete[] fstr[m]; delete[] kstr[m]; } + + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -389,70 +403,100 @@ void FixWall::post_force(int vflag) } wall_particle(m, wallwhich[m], coord); - switch (wallwhich[m]) { - case XLO: // fallthrough - case XHI: - imgparms[2 * m][1] = coord; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = coord; - imgparms[2 * m][5] = domain->boxhi[1]; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = coord; - imgparms[2 * m][8] = domain->boxlo[1]; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = coord; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = coord; - imgparms[2 * m + 1][5] = domain->boxlo[1]; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = coord; - imgparms[2 * m + 1][8] = domain->boxhi[1]; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case YLO: // fallthrough - case YHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = coord; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = coord; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = coord; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = coord; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = coord; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = coord; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case ZLO: // fallthrough - case ZHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = coord; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = domain->boxlo[1]; - imgparms[2 * m][6] = coord; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = domain->boxhi[1]; - imgparms[2 * m][9] = coord; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = coord; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = domain->boxhi[1]; - imgparms[2 * m + 1][6] = coord; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = domain->boxlo[1]; - imgparms[2 * m + 1][9] = coord; - break; + if (domain->dimension == 2) { + // one cylinder for 2d. we "guess" the diameter by using sigma. can be adjusted with fparam2 + switch (wallwhich[m]) { + case XLO: // fallthrough + case XHI: + imgparms[m][1] = coord; + imgparms[m][2] = domain->boxlo[1]; + imgparms[m][3] = 0.0; + imgparms[m][4] = coord; + imgparms[m][5] = domain->boxhi[1]; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case YLO: // fallthrough + case YHI: + imgparms[m][1] = domain->boxlo[0]; + imgparms[m][2] = coord; + imgparms[m][3] = 0.0; + imgparms[m][4] = domain->boxhi[0]; + imgparms[m][5] = coord; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case ZLO: // fallthrough + case ZHI:; // no wall in z-direction allowed for 2d systems + break; + } + } else { + // two triangles for 3d + switch (wallwhich[m]) { + case XLO: // fallthrough + case XHI: + imgparms[2 * m][1] = coord; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = coord; + imgparms[2 * m][5] = domain->boxhi[1]; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = coord; + imgparms[2 * m][8] = domain->boxlo[1]; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = coord; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = coord; + imgparms[2 * m + 1][5] = domain->boxlo[1]; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = coord; + imgparms[2 * m + 1][8] = domain->boxhi[1]; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case YLO: // fallthrough + case YHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = coord; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = coord; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = coord; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = coord; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = coord; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = coord; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case ZLO: // fallthrough + case ZHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = coord; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = domain->boxlo[1]; + imgparms[2 * m][6] = coord; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = domain->boxhi[1]; + imgparms[2 * m][9] = coord; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = coord; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = domain->boxhi[1]; + imgparms[2 * m + 1][6] = coord; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = domain->boxlo[1]; + imgparms[2 * m + 1][9] = coord; + break; + } } } @@ -512,5 +556,9 @@ int FixWall::image(int *&objs, double **&parms) { objs = imgobjs; parms = imgparms; - return 2*nwall; + if (domain->dimension == 2) { + return nwall; + } else { + return 2 * nwall; + } } From 0a4db01139ddae7d5cf32268e330270730c5b823 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 22:44:56 -0500 Subject: [PATCH 12/54] add check if valid image data is available from fix for dump image --- src/dump_image.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 526dfd4ea8f..e8e8045f059 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -920,6 +920,15 @@ void DumpImage::init_style() fixptr = modify->get_fix_by_id(id_fix); if (!fixptr) error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", id_fix); + + // check if fix data for dump image is available at the required steps. + + int nfreq = fixptr->global_freq; + if (nfreq == 0) nfreq = fixptr->nevery; + if ((nfreq == 0) || (nevery % nfreq)) + error->all(FLERR, Error::NOLASTLINE, + "Dump {} and fix {} are not computed at compatible times{}", + style, fixptr->style, utils::errorurl(7)); } } From f7567cc6e1d342953c1b20abe1a67419dfcc2053 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 14 Dec 2025 22:55:11 -0500 Subject: [PATCH 13/54] fix typos --- doc/src/fix_indent.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 9b2c33862a0..1b7a72e9bb4 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -221,14 +221,14 @@ whether the cylinder is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. -The *fflag2* setting allows to adjust the radius of the rendered object -for spherical indenters, cylindrical indenders, and planar indenters in -2d systems. In many cases you want to use a value < 0 to reduce the -radius of the rendered object to that it does not obscure atoms close to -it. For a planar indenter in 2d systems, it should be set to a value > -0 or the indenter will not be visible since the diameter is set -internally to zero in that case due to lack of a suitable heuristic for -deriving a meaningful diameter. +The *fflag2* setting allows you to adjust the radius of the rendered +object for spherical indenters, cylindrical indenters, and planar +indenters in 2d systems. In many cases you want to use a value < 0 to +reduce the radius of the rendered object so that it does not obscure +atoms close to it. For a planar indenter in 2d systems, it should be +set to a value > 0 or the indenter will not be visible since the +diameter is set internally to zero in that case due to lack of a +suitable heuristic for deriving a meaningful diameter. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" From bf8a2ccc8eebd19ace30941f4a9c03dc0e6bbd54 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 14:16:33 -0500 Subject: [PATCH 14/54] support rendering of objects from multiple fixes --- doc/src/dump_image.rst | 32 ++++++-- src/dump_image.cpp | 168 ++++++++++++++++++----------------------- src/dump_image.h | 58 +++++++++----- 3 files changed, 137 insertions(+), 121 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 5ad827fecaf..9b571124dab 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -160,8 +160,9 @@ Syntax *color* args = name R G B name = name of color R,G,B = red/green/blue numeric values from 0.0 to 1.0 - *fcolor* args = color - color = name of color for fix objects + *fcolor* args = fix-ID color + fix-ID = ID of the fix + color = name of color for image objects provided by this fix *framerate* arg = fps fps = frames per second for movie *gmap* args = identical to *amap* args @@ -506,6 +507,10 @@ change this via the dump_modify command. ---------- +.. versionchanged:: TBD + + Support for several fix styles added and more flexible color selection + The *fix* keyword can be used with a :doc:`fix ` that produces objects to be drawn. Below is a list of supported fixes: @@ -520,15 +525,24 @@ objects to be drawn. Below is a list of supported fixes: * :doc:`fix wall/morse ` * :doc:`fix wall/table ` +The fix keyword may be used multiple time to include visualizations of +object from multiple fixes. The fix keyword is followed by the +:doc:`fix ID ` of the fix, the color style setting and two +numerical values *fflag1* and *fflag2*. + +The color style may be either *type*, *element*, or *const*. The first +two will use the same color as assigned to the corresonding atom type +and thus it depends on the fix which atom type it associates with any +object. Often this will be atom type 1. For the *const* type a +constant color will be used that can be changed with a *dump_modify +fcolor* command (see below). By default the constant color will be +"red" (same as the default color for atom type 1). + The *fflag1* and *fflag2* settings are numerical values which are used by *dump image* to adjust how the drawing of the objects communicated by the fix is done. See the documentation of the individual fixes for a description of what these parameters mean. -The only setting currently allowed for the *color* value is *type*, -which will color the fix objects in the same color as atom type 1. -By default this will be "red". - ---------- .. versionadded:: 10Sep2025 @@ -1035,8 +1049,10 @@ pre-defined color names with new RBG values. .. versionadded:: TBD The *fcolor* keyword sets the color of any image objects created by a -fix. The color name can be any of the 140 pre-defined colors (see -below) or a color name defined by the *dump_modify color* option. +fix. The first argument is the fix ID used with the *dump image fix* +command and the second argument is the color name. The color name can +be any of the 140 pre-defined colors (see below) or a color name defined +by the *dump_modify color* option. ---------- diff --git a/src/dump_image.cpp b/src/dump_image.cpp index e8e8045f059..1d7380f7790 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -58,6 +58,8 @@ #include #include +// clang-format on + // helper functions for generating triangle meshes namespace { @@ -204,8 +206,8 @@ void ellipsoid2wireframe(LAMMPS_NS::Image *img, int level, const double *color, } } -void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, - const double *center, const double *radius, LAMMPS_NS::Region *reg) +void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, const double *center, + const double *radius, LAMMPS_NS::Region *reg) { vec3 offset = {center[0], center[1], center[2]}; @@ -280,7 +282,7 @@ using MathConst::DEG2RAD; static constexpr double BIG = 1.0e20; -enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE }; +enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE, CONSTANT}; enum { STATIC, DYNAMIC }; enum { NO = 0, YES = 1, AUTO = 2 }; enum { FILLED, FRAME, POINTS }; @@ -293,8 +295,8 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : diamtype(nullptr), diamelement(nullptr), bdiamtype(nullptr), colortype(nullptr), colorelement(nullptr), bcolortype(nullptr), grid2d(nullptr), grid3d(nullptr), id_grid_compute(nullptr), id_grid_fix(nullptr), grid_compute(nullptr), grid_fix(nullptr), - gbuf(nullptr), avec_line(nullptr), avec_tri(nullptr), avec_body(nullptr), fixptr(nullptr), - id_fix(nullptr), fcolor(nullptr), image(nullptr), chooseghost(nullptr), bufcopy(nullptr) + gbuf(nullptr), avec_line(nullptr), avec_tri(nullptr), avec_body(nullptr), image(nullptr), + chooseghost(nullptr), bufcopy(nullptr) { if (binary || multiproc) error->all(FLERR, 4, "Invalid dump image filename {}", filename); @@ -307,6 +309,7 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : has_id = true; + // clang-format off // set filetype based on filename suffix if (utils::strmatch(filename, "\\.jpg$") || utils::strmatch(filename, "\\.JPG$") @@ -354,7 +357,7 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : atomflag = YES; gridflag = NO; - lineflag = triflag = bodyflag = fixflag = NO; + lineflag = triflag = bodyflag = NO; if (atom->nbondtypes == 0) bondflag = NO; else { @@ -475,13 +478,17 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : } else if (strcmp(arg[iarg],"fix") == 0) { if (iarg+5 > narg) utils::missing_cmd_args(FLERR,"dump image fix", error); - fixflag = YES; - delete[] id_fix; - id_fix = utils::strdup(arg[iarg+1]); + std::string id_fix = arg[iarg+1]; + auto *fixptr = modify->get_fix_by_id(id_fix); + if (!fixptr) error->all(FLERR, iarg+1, "Dump image fix ID {} does not exist", id_fix); + int fixcolor = TYPE; if (strcmp(arg[iarg+2],"type") == 0) fixcolor = TYPE; - else error->all(FLERR, iarg+2, "Dump image fix only supports color by type"); - fixflag1 = utils::numeric(FLERR,arg[iarg+3],false,lmp); - fixflag2 = utils::numeric(FLERR,arg[iarg+4],false,lmp); + else if (strcmp(arg[iarg+2],"element") == 0) fixcolor = ELEMENT; + else if (strcmp(arg[iarg+2],"const") == 0) fixcolor = CONSTANT; + else error->all(FLERR, iarg+2, "Unsupported color style for dump image fix {}", arg[iarg+2]); + double fixflag1 = utils::numeric(FLERR,arg[iarg+3],false,lmp); + double fixflag2 = utils::numeric(FLERR,arg[iarg+4],false,lmp); + fixes.emplace_back(id_fix, fixptr, fixcolor, fixflag1, fixflag2, image->color2rgb("red")); iarg += 5; } else if (strcmp(arg[iarg],"region") == 0) { @@ -665,7 +672,7 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : } else error->all(FLERR,"Unknown dump image keyword {}", arg[iarg]); } - // error checks and setup for lineflag, triflag, bodyflag, fixflag + // error checks and setup for lineflag, triflag, bodyflag if (lineflag) { avec_line = dynamic_cast(atom->style_match("line")); @@ -686,12 +693,6 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : extraflag = 0; if (lineflag || triflag || bodyflag) extraflag = 1; - if (fixflag) { - fixptr = modify->get_fix_by_id(id_fix); - if (!fixptr) - error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", id_fix); - } - // allocate image buffer now that image size is known image->buffers(); @@ -782,7 +783,6 @@ DumpImage::~DumpImage() delete[] id_grid_compute; delete[] id_grid_fix; - delete[] id_fix; } /* ---------------------------------------------------------------------- */ @@ -915,11 +915,12 @@ void DumpImage::init_style() error->all(FLERR, "Dump image autobond cutoff is larger than periodic domain"); } - // check if fix with visualization still exists - if (fixflag) { - fixptr = modify->get_fix_by_id(id_fix); + // check if fixes with visualization info still exist + for (auto &ifix : fixes) { + auto *fixptr = modify->get_fix_by_id(ifix.id); if (!fixptr) - error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", id_fix); + error->all(FLERR, Error::NOLASTLINE, "Fix ID {} for dump image does not exist", ifix.id); + ifix.ptr = fixptr; // check if fix data for dump image is available at the required steps. @@ -1216,8 +1217,8 @@ void DumpImage::create_image() int i,j,k,m,n,itype,atom1,atom2,imol,iatom,btype,ibonus,drawflag; tagint tagprev; double diameter,delx,dely,delz; - int *bodyvec,*fixvec; - double **bodyarray,**fixarray; + int *bodyvec; + double **bodyarray; double *color,*color1,*color2; double *p1,*p2,*p3; double pt1[3],pt2[3],pt3[3]; @@ -1693,81 +1694,56 @@ void DumpImage::create_image() } } - // render objects provided by a fix + // render objects provided by fixes - if (fixflag) { + for (const auto &ifix : fixes) { int tridraw = 0; int edgedraw = 0; if (domain->dimension == 3) { tridraw = 1; edgedraw = 1; - if ((int) fixflag1 == 2) tridraw = 0; - if ((int) fixflag1 == 1) edgedraw = 0; + if ((int) ifix.flag1 == 2) tridraw = 0; + if ((int) ifix.flag1 == 1) edgedraw = 0; } - n = fixptr->image(fixvec,fixarray); - if (fixvec && fixarray) { - for (i = 0; i < n; i++) { - if (fixvec[i] == SPHERE) { - if (fcolor) { - color = fcolor; - } else if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } else { - color = image->color2rgb("red"); - } - image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]+fixflag2); - } else if (fixvec[i] == LINE) { - if (fcolor) { - color = fcolor; - } else if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } else { - color = image->color2rgb("red"); - } - image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,fixflag1,3); - } else if (fixvec[i] == TRI) { - if (fcolor) { - color = fcolor; - } else if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } else { - color = image->color2rgb("red"); - } - p1 = &fixarray[i][1]; - p2 = &fixarray[i][4]; - p3 = &fixarray[i][7]; - if (tridraw) image->draw_triangle(p1,p2,p3,color); - if (edgedraw) { - image->draw_cylinder(p1,p2,color,fixflag2,3); - image->draw_cylinder(p2,p3,color,fixflag2,3); - image->draw_cylinder(p3,p1,color,fixflag2,3); - } - } else if (fixvec[i] == CYLINDER) { - if (fcolor) { - color = fcolor; - } else if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } else { - color = image->color2rgb("red"); - } - image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color, - fixarray[i][7]+fixflag2,(int)fixflag1); - } else if (fixvec[i] == TRIANGLE) { - if (fcolor) { - color = fcolor; - } else if (fixcolor == TYPE) { - itype = static_cast(fixarray[i][0]); - color = colortype[itype]; - } else { - color = image->color2rgb("red"); - } - image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); + int *fixvec = nullptr; + double **fixarray = nullptr; + n = ifix.ptr->image(fixvec,fixarray); + for (i = 0; i < n; i++) { + if (!fixvec || !fixarray) continue; + + // set color + if (ifix.colorstyle == TYPE) { + itype = static_cast(fixarray[i][0]); + color = colortype[itype]; + } else if (ifix.colorstyle == ELEMENT) { + itype = static_cast(fixarray[i][0]); + color = colorelement[itype]; + } else if (ifix.colorstyle == CONSTANT) { + color = ifix.rgb; + } else { + color = image->color2rgb("red"); + } + + if (fixvec[i] == SPHERE) { + image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]+ifix.flag2); + } else if (fixvec[i] == LINE) { + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,ifix.flag1,3); + } else if (fixvec[i] == TRI) { + p1 = &fixarray[i][1]; + p2 = &fixarray[i][4]; + p3 = &fixarray[i][7]; + if (tridraw) image->draw_triangle(p1,p2,p3,color); + if (edgedraw) { + image->draw_cylinder(p1,p2,color,ifix.flag2,3); + image->draw_cylinder(p2,p3,color,ifix.flag2,3); + image->draw_cylinder(p3,p1,color,ifix.flag2,3); } + } else if (fixvec[i] == CYLINDER) { + image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color, + fixarray[i][7]+ifix.flag2,(int)ifix.flag1); + } else if (fixvec[i] == TRIANGLE) { + image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); } } } @@ -2634,9 +2610,11 @@ int DumpImage::modify_param(int narg, char **arg) } if (strcmp(arg[0],"fcolor") == 0) { - if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); - fcolor = image->color2rgb(arg[1]); - return 2; + if (narg < 3) error->all(FLERR,"Illegal dump_modify command"); + for (auto &ifix : fixes) { + if (ifix.id == arg[1]) ifix.rgb = image->color2rgb(arg[2]); + } + return 3; } return 0; diff --git a/src/dump_image.h b/src/dump_image.h index af9b74b7730..2ec7ae9c343 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -23,20 +23,32 @@ DumpStyle(image,DumpImage); #include "dump_custom.h" namespace LAMMPS_NS { + +// forward declarations +class AtomVecBody; +class AtomVecLine; +class AtomVecTri; +class Compute; +class Fix; +class Grid2d; +class Grid3d; +class Image; +class LAMMPS; class Region; + class DumpImage : public DumpCustom { public: int multifile_override; // used by write_dump command enum { SPHERE, LINE, TRI, CYLINDER, TRIANGLE }; // used by some Body and Fix child classes - DumpImage(class LAMMPS *, int, char **); + DumpImage(LAMMPS *, int, char **); ~DumpImage() override; int pack_forward_comm(int, int *, double *, int, int *) override; void unpack_forward_comm(int, int, double *) override; protected: int filetype; - enum { PPM, JPG, PNG }; + enum { PPM, JPG, PNG }; // file type constants int atomflag; // 0/1 for draw atoms int acolor, adiam; // what determines color/diam of atoms @@ -51,9 +63,6 @@ class DumpImage : public DumpCustom { int bodyflag; // 0/1 for draw atoms as bodies int bodycolor; // what determines color of bodies double bodyflag1, bodyflag2; // user-specified params for drawing bodies - int fixflag; // 0/1 to draw what fix provides - int fixcolor; // what determines color of fix objects - double fixflag1, fixflag2; // user-specified params for fix objects int bondflag; // NO/YES/AUTO for drawing bonds int bcolor, bdiam; // what determines color/diam of bonds @@ -82,11 +91,11 @@ class DumpImage : public DumpCustom { double **colortype, **colorelement, **bcolortype; // per-type colors int gridflag; // 0/1 for draw grid cells - class Grid2d *grid2d; - class Grid3d *grid3d; + Grid2d *grid2d; + Grid3d *grid3d; char *id_grid_compute, *id_grid_fix; - class Compute *grid_compute; - class Fix *grid_fix; + Compute *grid_compute; + Fix *grid_fix; int grid_igrid, grid_idata, grid_index; int nxgrid, nygrid, nzgrid; int nxlo_in, nxhi_in, nylo_in, nyhi_in, nzlo_in, nzhi_in; @@ -94,18 +103,31 @@ class DumpImage : public DumpCustom { int ngrid, maxgrid; double gcorners[8][3]; - class AtomVecLine *avec_line; // ptrs to atom style (sub)classes - class AtomVecTri *avec_tri; - class AtomVecBody *avec_body; + AtomVecLine *avec_line; // ptrs to atom style (sub)classes + AtomVecTri *avec_tri; + AtomVecBody *avec_body; + + struct FixInfo { + FixInfo() = delete; + FixInfo(const std::string &_id, Fix *_ptr, int _colorstyle, double _flag1, double _flag2, + double *_rgb) : + id(_id), ptr(_ptr), colorstyle(_colorstyle), flag1(_flag1), flag2(_flag2), rgb(_rgb) + { + } + + Fix *ptr; + std::string id; + int colorstyle; + double flag1; + double flag2; + double *rgb; + }; - class Fix *fixptr; // ptr to Fix that provides image data - char *id_fix; - double *fcolor; // custom color choice for fix + std::vector fixes; - class Image *image; // class that renders each image + Image *image; // class that renders each image - class RegionInfo { - public: + struct RegionInfo { RegionInfo() = delete; RegionInfo(const std::string &_id, Region *_ptr, double *_color, int _style, double _diameter = 0.5, int _npoints = 0) : From e840a1480cba01308ce8b388997cbc5f2c433983 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 16:35:24 -0500 Subject: [PATCH 15/54] enable and apply clang-format --- src/MACHDYN/fix_smd_wall_surface.cpp | 270 +++++++++++++-------------- 1 file changed, 125 insertions(+), 145 deletions(-) diff --git a/src/MACHDYN/fix_smd_wall_surface.cpp b/src/MACHDYN/fix_smd_wall_surface.cpp index 25e76e1dab2..af348d8ee3c 100644 --- a/src/MACHDYN/fix_smd_wall_surface.cpp +++ b/src/MACHDYN/fix_smd_wall_surface.cpp @@ -1,4 +1,3 @@ -// clang-format off /* ---------------------------------------------------------------------- LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator https://www.lammps.org/, Sandia National Laboratories @@ -25,9 +24,9 @@ #include "error.h" #include "text_file_reader.h" +#include #include #include -#include using namespace LAMMPS_NS; using namespace FixConst; @@ -38,66 +37,60 @@ static constexpr double EPSILON = 1.0e-6; /* ---------------------------------------------------------------------- */ -FixSMDWallSurface::FixSMDWallSurface(LAMMPS *lmp, int narg, char **arg) : - Fix(lmp, narg, arg) { - - restart_global = 0; - restart_peratom = 0; - first = 1; - - //atom->add_callback(Atom::GROW); - //atom->add_callback(Atom::RESTART); - - if (narg != 6) - error->all(FLERR, "Illegal number of arguments for fix smd/wall_surface"); - - filename = strdup(arg[3]); - wall_particle_type = utils::inumeric(FLERR,arg[4],false,lmp); - wall_molecule_id = utils::inumeric(FLERR,arg[5],false,lmp); - if (wall_molecule_id < 65535) { - error->one(FLERR, "wall molcule id must be >= 65535\n"); - } - - if (comm->me == 0) { - printf("\n>>========>>========>>========>>========>>========>>========>>========>>========\n"); - printf("fix smd/wall_surface reads trianglulated surface from file: %s\n", filename); - printf("fix smd/wall_surface has particle type %d \n", wall_particle_type); - printf("fix smd/wall_surface has molecule id %d \n", wall_molecule_id); - printf(">>========>>========>>========>>========>>========>>========>>========>>========\n"); - } +FixSMDWallSurface::FixSMDWallSurface(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg) +{ + + restart_global = 0; + restart_peratom = 0; + first = 1; + + if (narg != 6) error->all(FLERR, "Illegal number of arguments for fix smd/wall_surface"); + + filename = strdup(arg[3]); + wall_particle_type = utils::inumeric(FLERR, arg[4], false, lmp); + wall_molecule_id = utils::inumeric(FLERR, arg[5], false, lmp); + if (wall_molecule_id < 65535) { error->one(FLERR, "wall molcule id must be >= 65535\n"); } + + if (comm->me == 0) { + printf("\n>>========>>========>>========>>========>>========>>========>>========>>========\n"); + printf("fix smd/wall_surface reads trianglulated surface from file: %s\n", filename); + printf("fix smd/wall_surface has particle type %d \n", wall_particle_type); + printf("fix smd/wall_surface has molecule id %d \n", wall_molecule_id); + printf(">>========>>========>>========>>========>>========>>========>>========>>========\n"); + } } /* ---------------------------------------------------------------------- */ -FixSMDWallSurface::~FixSMDWallSurface() { - free(filename); - filename = nullptr; - // unregister this fix so atom class doesn't invoke it any more - - //atom->delete_callback(id,Atom::GROW); - //atom->delete_callback(id,Atom::RESTART); +FixSMDWallSurface::~FixSMDWallSurface() +{ + free(filename); + filename = nullptr; + // unregister this fix so atom class doesn't invoke it any more } /* ---------------------------------------------------------------------- */ -int FixSMDWallSurface::setmask() { - int mask = 0; - return mask; +int FixSMDWallSurface::setmask() +{ + int mask = 0; + return mask; } /* ---------------------------------------------------------------------- */ -void FixSMDWallSurface::init() { - if (!first) - return; +void FixSMDWallSurface::init() +{ + if (!first) return; } /* ---------------------------------------------------------------------- For minimization: setup as with dynamics ------------------------------------------------------------------------- */ -void FixSMDWallSurface::min_setup(int vflag) { - setup(vflag); +void FixSMDWallSurface::min_setup(int vflag) +{ + setup(vflag); } /* ---------------------------------------------------------------------- @@ -105,92 +98,81 @@ void FixSMDWallSurface::min_setup(int vflag) { must be done in setup (not init) since fix init comes before neigh init ------------------------------------------------------------------------- */ -void FixSMDWallSurface::setup(int /*vflag*/) { - - if (!first) - return; - first = 0; - - // set bounds for my proc - // if periodic and I am lo/hi proc, adjust bounds by EPSILON - // ensures all data atoms will be owned even with round-off - - int triclinic = domain->triclinic; - - double epsilon[3]; - if (triclinic) - epsilon[0] = epsilon[1] = epsilon[2] = EPSILON; - else { - epsilon[0] = domain->prd[0] * EPSILON; - epsilon[1] = domain->prd[1] * EPSILON; - epsilon[2] = domain->prd[2] * EPSILON; - } - - if (triclinic == 0) { - sublo[0] = domain->sublo[0]; - subhi[0] = domain->subhi[0]; - sublo[1] = domain->sublo[1]; - subhi[1] = domain->subhi[1]; - sublo[2] = domain->sublo[2]; - subhi[2] = domain->subhi[2]; - } else { - sublo[0] = domain->sublo_lamda[0]; - subhi[0] = domain->subhi_lamda[0]; - sublo[1] = domain->sublo_lamda[1]; - subhi[1] = domain->subhi_lamda[1]; - sublo[2] = domain->sublo_lamda[2]; - subhi[2] = domain->subhi_lamda[2]; - } - - if (comm->layout != Comm::LAYOUT_TILED) { - if (domain->xperiodic) { - if (comm->myloc[0] == 0) - sublo[0] -= epsilon[0]; - if (comm->myloc[0] == comm->procgrid[0] - 1) - subhi[0] += epsilon[0]; - } - if (domain->yperiodic) { - if (comm->myloc[1] == 0) - sublo[1] -= epsilon[1]; - if (comm->myloc[1] == comm->procgrid[1] - 1) - subhi[1] += epsilon[1]; - } - if (domain->zperiodic) { - if (comm->myloc[2] == 0) - sublo[2] -= epsilon[2]; - if (comm->myloc[2] == comm->procgrid[2] - 1) - subhi[2] += epsilon[2]; - } - - } else { - if (domain->xperiodic) { - if (comm->mysplit[0][0] == 0.0) - sublo[0] -= epsilon[0]; - if (comm->mysplit[0][1] == 1.0) - subhi[0] += epsilon[0]; - } - if (domain->yperiodic) { - if (comm->mysplit[1][0] == 0.0) - sublo[1] -= epsilon[1]; - if (comm->mysplit[1][1] == 1.0) - subhi[1] += epsilon[1]; - } - if (domain->zperiodic) { - if (comm->mysplit[2][0] == 0.0) - sublo[2] -= epsilon[2]; - if (comm->mysplit[2][1] == 1.0) - subhi[2] += epsilon[2]; - } - } - - read_triangles(0); +void FixSMDWallSurface::setup(int /*vflag*/) +{ + + if (!first) return; + first = 0; + + // set bounds for my proc + // if periodic and I am lo/hi proc, adjust bounds by EPSILON + // ensures all data atoms will be owned even with round-off + + int triclinic = domain->triclinic; + + double epsilon[3]; + if (triclinic) + epsilon[0] = epsilon[1] = epsilon[2] = EPSILON; + else { + epsilon[0] = domain->prd[0] * EPSILON; + epsilon[1] = domain->prd[1] * EPSILON; + epsilon[2] = domain->prd[2] * EPSILON; + } + + if (triclinic == 0) { + sublo[0] = domain->sublo[0]; + subhi[0] = domain->subhi[0]; + sublo[1] = domain->sublo[1]; + subhi[1] = domain->subhi[1]; + sublo[2] = domain->sublo[2]; + subhi[2] = domain->subhi[2]; + } else { + sublo[0] = domain->sublo_lamda[0]; + subhi[0] = domain->subhi_lamda[0]; + sublo[1] = domain->sublo_lamda[1]; + subhi[1] = domain->subhi_lamda[1]; + sublo[2] = domain->sublo_lamda[2]; + subhi[2] = domain->subhi_lamda[2]; + } + + if (comm->layout != Comm::LAYOUT_TILED) { + if (domain->xperiodic) { + if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; + if (comm->myloc[0] == comm->procgrid[0] - 1) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; + if (comm->myloc[1] == comm->procgrid[1] - 1) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; + if (comm->myloc[2] == comm->procgrid[2] - 1) subhi[2] += epsilon[2]; + } + + } else { + if (domain->xperiodic) { + if (comm->mysplit[0][0] == 0.0) sublo[0] -= epsilon[0]; + if (comm->mysplit[0][1] == 1.0) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->mysplit[1][0] == 0.0) sublo[1] -= epsilon[1]; + if (comm->mysplit[1][1] == 1.0) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->mysplit[2][0] == 0.0) sublo[2] -= epsilon[2]; + if (comm->mysplit[2][1] == 1.0) subhi[2] += epsilon[2]; + } + } + + read_triangles(0); } /* ---------------------------------------------------------------------- size of atom nlocal's restart data ------------------------------------------------------------------------- */ -void FixSMDWallSurface::read_triangles(int pass) { +void FixSMDWallSurface::read_triangles(int pass) +{ double coord[3]; int nlocal_previous = atom->nlocal; @@ -201,8 +183,7 @@ void FixSMDWallSurface::read_triangles(int pass) { Vector3d normal, center; FILE *fp = fopen(filename, "r"); - if (fp == nullptr) - error->one(FLERR, "Cannot open file {}: {}", filename, utils::getsyserror()); + if (fp == nullptr) error->one(FLERR, "Cannot open file {}: {}", filename, utils::getsyserror()); if (comm->me == 0) { utils::logmesg(lmp, "\n>>========>>========>>========>>========>>========>>========\n"); @@ -216,43 +197,43 @@ void FixSMDWallSurface::read_triangles(int pass) { try { char *line = reader.next_line(); if (!line || !utils::strmatch(line, "^solid")) - throw TokenizerException("Invalid triangles file format",""); + throw TokenizerException("Invalid triangles file format", ""); if (comm->me == 0) - utils::logmesg(lmp, " reading STL object '{}' from {}\n", utils::trim(line+6), filename); + utils::logmesg(lmp, " reading STL object '{}' from {}\n", utils::trim(line + 6), filename); - while((line = reader.next_line())) { + while ((line = reader.next_line())) { // next line is facet line with 5 words auto values = utils::split_words(line); // otherwise stop reading - if ((values.size() != 5) || !utils::strmatch(values[0],"^facet")) break; + if ((values.size() != 5) || !utils::strmatch(values[0], "^facet")) break; normal << utils::numeric(FLERR, values[2], false, lmp), - utils::numeric(FLERR, values[3], false, lmp), - utils::numeric(FLERR, values[4], false, lmp); + utils::numeric(FLERR, values[3], false, lmp), + utils::numeric(FLERR, values[4], false, lmp); line = reader.next_line(2); if (!line || !utils::strmatch(line, "^ *outer *loop")) - throw TokenizerException("Error reading outer loop",""); + throw TokenizerException("Error reading outer loop", ""); for (int k = 0; k < 3; ++k) { line = reader.next_line(4); values = utils::split_words(line); - if ((values.size() != 4) || !utils::strmatch(values[0],"^vertex")) - throw TokenizerException("Error reading vertex",""); + if ((values.size() != 4) || !utils::strmatch(values[0], "^vertex")) + throw TokenizerException("Error reading vertex", ""); vert[k] << utils::numeric(FLERR, values[1], false, lmp), - utils::numeric(FLERR, values[2], false, lmp), - utils::numeric(FLERR, values[3], false, lmp); + utils::numeric(FLERR, values[2], false, lmp), + utils::numeric(FLERR, values[3], false, lmp); } line = reader.next_line(1); if (!line || !utils::strmatch(line, "^ *endloop")) - throw TokenizerException("Error reading endloop",""); + throw TokenizerException("Error reading endloop", ""); line = reader.next_line(1); if (!line || !utils::strmatch(line, "^ *endfacet")) - throw TokenizerException("Error reading endfacet",""); + throw TokenizerException("Error reading endfacet", ""); // now we have a normal and three vertices ... proceed with adding triangle @@ -271,8 +252,8 @@ void FixSMDWallSurface::read_triangles(int pass) { * ... radius is the mmaximal distance from triangle center to all vertices */ - if (center(0) >= sublo[0] && center(0) < subhi[0] && center(1) >= sublo[1] && center(1) < subhi[1] && center(2) >= sublo[2] - && center(2) < subhi[2]) { + if (center(0) >= sublo[0] && center(0) < subhi[0] && center(1) >= sublo[1] && + center(1) < subhi[1] && center(2) >= sublo[2] && center(2) < subhi[2]) { coord[0] = center(0); coord[1] = center(1); @@ -291,8 +272,8 @@ void FixSMDWallSurface::read_triangles(int pass) { double **smd_data_9 = atom->smd_data_9; double **x0 = atom->x0; - radius[ilocal] = r; //ilocal; - contact_radius[ilocal] = r; //ilocal; + radius[ilocal] = r; //ilocal; + contact_radius[ilocal] = r; //ilocal; mol[ilocal] = wall_molecule_id; type[ilocal] = wall_particle_type; x0[ilocal][0] = normal(0); @@ -319,8 +300,7 @@ void FixSMDWallSurface::read_triangles(int pass) { bigint nblocal = atom->nlocal; MPI_Allreduce(&nblocal, &atom->natoms, 1, MPI_LMP_BIGINT, MPI_SUM, world); - if (atom->natoms < 0 || atom->natoms >= MAXBIGINT) - error->all(FLERR, "Too many total atoms"); + if (atom->natoms < 0 || atom->natoms >= MAXBIGINT) error->all(FLERR, "Too many total atoms"); // add IDs for newly created atoms // check that atom IDs are valid From 0f3d67ec687cef2f023899420c53e1e3c944a98c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 17:30:31 -0500 Subject: [PATCH 16/54] add dump image support to fix smd/wall_surface --- src/MACHDYN/fix_smd_wall_surface.cpp | 54 ++++++++++++++++++++++++++-- src/MACHDYN/fix_smd_wall_surface.h | 7 ++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/MACHDYN/fix_smd_wall_surface.cpp b/src/MACHDYN/fix_smd_wall_surface.cpp index af348d8ee3c..4201427380d 100644 --- a/src/MACHDYN/fix_smd_wall_surface.cpp +++ b/src/MACHDYN/fix_smd_wall_surface.cpp @@ -21,7 +21,9 @@ #include "atom_vec.h" #include "comm.h" #include "domain.h" +#include "dump_image.h" #include "error.h" +#include "memory.h" #include "text_file_reader.h" #include @@ -37,7 +39,8 @@ static constexpr double EPSILON = 1.0e-6; /* ---------------------------------------------------------------------- */ -FixSMDWallSurface::FixSMDWallSurface(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg) +FixSMDWallSurface::FixSMDWallSurface(LAMMPS *lmp, int narg, char **arg) : + Fix(lmp, narg, arg), imgobjs(nullptr), imgparms(nullptr) { restart_global = 0; @@ -66,7 +69,10 @@ FixSMDWallSurface::~FixSMDWallSurface() { free(filename); filename = nullptr; - // unregister this fix so atom class doesn't invoke it any more + + // clean up dump image data + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -327,3 +333,47 @@ void FixSMDWallSurface::read_triangles(int pass) delete[] vert; fclose(fp); } + +/* ---------------------------------------------------------------------- + create list of visualization object for dump image + ------------------------------------------------------------------------- */ +int FixSMDWallSurface::image(int *&objs, double **&parms) +{ + const int nlocal = atom->nlocal; + const int * const type = atom->type; + const double * const * const verts = atom->smd_data_9; + + int numobjs = 0; + for (int i = 0; i < nlocal; ++i) + if (type[i] == wall_particle_type) ++numobjs; + + if (numobjs == 0) return 0; + + // reallocate storage + memory->destroy(imgobjs); + memory->destroy(imgparms); + memory->create(imgobjs, numobjs, "wall_surface:imgobjs"); + memory->create(imgparms, numobjs, 10, "wall_surface:imgobjs"); + + // copy tri objects + numobjs = 0; + for (int i = 0; i < nlocal; ++i) { + if (type[i] == wall_particle_type) { + imgobjs[numobjs] = DumpImage::TRI; + imgparms[numobjs][0] = wall_particle_type; + imgparms[numobjs][1] = verts[i][0]; + imgparms[numobjs][2] = verts[i][1]; + imgparms[numobjs][3] = verts[i][2]; + imgparms[numobjs][4] = verts[i][3]; + imgparms[numobjs][5] = verts[i][4]; + imgparms[numobjs][6] = verts[i][5]; + imgparms[numobjs][7] = verts[i][6]; + imgparms[numobjs][8] = verts[i][7]; + imgparms[numobjs][9] = verts[i][8]; + ++numobjs; + } + } + objs = imgobjs; + parms = imgparms; + return numobjs; +} diff --git a/src/MACHDYN/fix_smd_wall_surface.h b/src/MACHDYN/fix_smd_wall_surface.h index fc7478c52bd..609537961f6 100644 --- a/src/MACHDYN/fix_smd_wall_surface.h +++ b/src/MACHDYN/fix_smd_wall_surface.h @@ -37,12 +37,19 @@ class FixSMDWallSurface : public Fix { void read_triangles(int pass); + int image(int *&, double **&) override; + private: int first; // flag for first time initialization double sublo[3], subhi[3]; // epsilon-extended proc sub-box for adding atoms; char *filename; int wall_particle_type; int wall_molecule_id; + + // arrays for dump image rendering + + int *imgobjs; + double **imgparms; }; } // namespace LAMMPS_NS From 1df94f4802ecc4ae06077eafffa3aa2241b06ea6 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 17:30:48 -0500 Subject: [PATCH 17/54] add note for @sjplimp --- src/dump_image.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 1d7380f7790..b559989b472 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1728,6 +1728,8 @@ void DumpImage::create_image() if (fixvec[i] == SPHERE) { image->draw_sphere(&fixarray[i][1],color,fixarray[i][4]+ifix.flag2); } else if (fixvec[i] == LINE) { + // @sjplimp for consistency this should be: + // image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,ifix.flag2,ifix.flag1); image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,ifix.flag1,3); } else if (fixvec[i] == TRI) { p1 = &fixarray[i][1]; From 08b5086e0567984650aeb1762fa734579202ddc7 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 17:31:19 -0500 Subject: [PATCH 18/54] update docs --- doc/src/dump_image.rst | 3 ++- doc/src/fix_smd_wall_surface.rst | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 9b571124dab..cdbfb387b67 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -515,6 +515,7 @@ The *fix* keyword can be used with a :doc:`fix ` that produces objects to be drawn. Below is a list of supported fixes: * :doc:`fix indent ` +* :doc:`fix smd/wall_surface ` * :doc:`fix wall/lj93 ` * :doc:`fix wall/lj126 ` * :doc:`fix wall/lj1043 ` @@ -531,7 +532,7 @@ object from multiple fixes. The fix keyword is followed by the numerical values *fflag1* and *fflag2*. The color style may be either *type*, *element*, or *const*. The first -two will use the same color as assigned to the corresonding atom type +two will use the same color as assigned to the corresponding atom type and thus it depends on the fix which atom type it associates with any object. Often this will be atom type 1. For the *const* type a constant color will be used that can be changed with a *dump_modify diff --git a/doc/src/fix_smd_wall_surface.rst b/doc/src/fix_smd_wall_surface.rst index 203bdb0ca51..a615514988a 100644 --- a/doc/src/fix_smd_wall_surface.rst +++ b/doc/src/fix_smd_wall_surface.rst @@ -50,6 +50,22 @@ directory. See `this PDF guide `_ to use Smooth Mach Dynamics in LAMMPS. +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +Fix *smd/wall\_surface* supports the *fix* keyword of :doc:`dump image +`. The fix will pass geometry information about the wall +particles to *dump image* so that they be included in the rendered +image. + +The *fflag1* setting of *dump image fix* determine whether the wall will +be rendered as triangles (1) or as a mesh of cylinders (2) or both (3). + +The *fflag2* setting determines the diameter of the cylinders for an +*fflag1* setting of either 2 or 3. + Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" From ffe32101d41176a4afb7f4ed7b872d8f4848e134 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 17:42:40 -0500 Subject: [PATCH 19/54] improve comment --- src/MACHDYN/fix_smd_wall_surface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MACHDYN/fix_smd_wall_surface.cpp b/src/MACHDYN/fix_smd_wall_surface.cpp index 4201427380d..645eb3ab973 100644 --- a/src/MACHDYN/fix_smd_wall_surface.cpp +++ b/src/MACHDYN/fix_smd_wall_surface.cpp @@ -355,7 +355,7 @@ int FixSMDWallSurface::image(int *&objs, double **&parms) memory->create(imgobjs, numobjs, "wall_surface:imgobjs"); memory->create(imgparms, numobjs, 10, "wall_surface:imgobjs"); - // copy tri objects + // copy local tri object info numobjs = 0; for (int i = 0; i < nlocal; ++i) { if (type[i] == wall_particle_type) { From 6207eb66585f593cb1dc233de9eac0730eb2aff0 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 18:35:37 -0500 Subject: [PATCH 20/54] fix copy-n-modify bug --- src/dump_image.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index b559989b472..755faf268b7 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1534,9 +1534,9 @@ void DumpImage::create_image() if (adiam == NUMERIC) { diameter = adiamvalue; } else if (adiam == TYPE) { - diameter = MIN(diamtype[type[atom1]],diamtype[type[atom1]]); + diameter = MIN(diamtype[type[atom1]],diamtype[type[atom2]]); } else if (adiam == ELEMENT) { - diameter = MIN(diamelement[type[atom1]],diamelement[type[atom1]]); + diameter = MIN(diamelement[type[atom2]],diamelement[type[atom2]]); } else if (adiam == ATTRIBUTE) { diameter = MIN(bufcopy[atom1][1],bufcopy[atom2][1]); } From 4a257924d82dd25855fdc02886c0235ae390a975 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 19:31:28 -0500 Subject: [PATCH 21/54] add support for rendering bonds from fixes --- src/dump_image.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++---- src/dump_image.h | 11 ++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 755faf268b7..d36b79fb312 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -359,12 +359,14 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : gridflag = NO; lineflag = triflag = bodyflag = NO; - if (atom->nbondtypes == 0) bondflag = NO; - else { + bdiamvalue = 0.5; + bcolor = ATOM; + bdiam = NUMERIC; + bdiamvalue = 0.5; + if (atom->nbondtypes == 0) { + bondflag = NO; + } else { bondflag = YES; - bcolor = ATOM; - bdiam = NUMERIC; - bdiamvalue = 0.5; } cflag = STATIC; @@ -1746,6 +1748,57 @@ void DumpImage::create_image() fixarray[i][7]+ifix.flag2,(int)ifix.flag1); } else if (fixvec[i] == TRIANGLE) { image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); + } else if (fixvec[i] == BOND) { + int type1 = static_cast(fixarray[i][0]); + int type2 = static_cast(fixarray[i][1]); + double *color1; + double *color2; + if (ifix.colorstyle == TYPE) { + color1 = colortype[type1]; + color2 = colortype[type2]; + } else if (ifix.colorstyle == ELEMENT) { + color1 = colorelement[type1]; + color2 = colorelement[type2]; + } else if (ifix.colorstyle == CONSTANT) { + color1 = ifix.rgb; + color2 = ifix.rgb; + } else { + color1 = image->color2rgb("white"); + color2 = image->color2rgb("white"); + } + + double diameter = 0.5; + if (bdiam == ATOM) { + if (adiam == NUMERIC) { + diameter = adiamvalue; + } else if (adiam == TYPE) { + diameter = MIN(diamtype[type1],diamtype[type2]); + } else if (adiam == ELEMENT) { + diameter = MIN(diamelement[type1],diamelement[type2]); + } else if (adiam == ATTRIBUTE) { + diameter = MIN(bufcopy[atom1][1],bufcopy[atom2][1]); + } + } else { + diameter = bdiamvalue; + } + + // draw bond cylinder in 2 pieces + + int capflag = ifix.flag1 ? 3 : 0; + delx = fixarray[i][5] - fixarray[i][2]; + dely = fixarray[i][6] - fixarray[i][3]; + delz = fixarray[i][7] - fixarray[i][4]; + + domain->minimum_image(FLERR,delx,dely,delz); + double xmid[3]; + xmid[0] = fixarray[i][2] + 0.5*delx; + xmid[1] = fixarray[i][3] + 0.5*dely; + xmid[2] = fixarray[i][4] + 0.5*delz; + image->draw_cylinder(&fixarray[i][2],xmid,color1,diameter,capflag); + xmid[0] = fixarray[i][5] - 0.5*delx; + xmid[1] = fixarray[i][6] - 0.5*dely; + xmid[2] = fixarray[i][7] - 0.5*delz; + image->draw_cylinder(xmid,&fixarray[i][5],color2,diameter,capflag); } } } diff --git a/src/dump_image.h b/src/dump_image.h index 2ec7ae9c343..f3219b56ccf 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -38,8 +38,15 @@ class Region; class DumpImage : public DumpCustom { public: - int multifile_override; // used by write_dump command - enum { SPHERE, LINE, TRI, CYLINDER, TRIANGLE }; // used by some Body and Fix child classes + int multifile_override; // used by write_dump command + enum { + SPHERE, // a single sphere with radius provided + LINE, // a cylinder with diameter given through fflag2 + TRI, // a surface mesh as triangles or cylinder mesh based on fflag1, fflag2 set diameter + CYLINDER, // a cylinder with diameter given by fix, fflag1 choose caps, fflag2 adjusts diameter + TRIANGLE, // a regular triangle, no settings apply + BOND // two connected cylinders with bond diameter, colored by atom types, fflag1 sets cap + }; // used by some Body and Fix child classes DumpImage(LAMMPS *, int, char **); ~DumpImage() override; From 15a84ce7e7c4e10453865d61f427b94008bed42c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 20:29:55 -0500 Subject: [PATCH 22/54] remove dead code --- src/dump_image.cpp | 2 -- src/fix.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index d36b79fb312..5b5e8c639a4 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -359,7 +359,6 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : gridflag = NO; lineflag = triflag = bodyflag = NO; - bdiamvalue = 0.5; bcolor = ATOM; bdiam = NUMERIC; bdiamvalue = 0.5; @@ -927,7 +926,6 @@ void DumpImage::init_style() // check if fix data for dump image is available at the required steps. int nfreq = fixptr->global_freq; - if (nfreq == 0) nfreq = fixptr->nevery; if ((nfreq == 0) || (nevery % nfreq)) error->all(FLERR, Error::NOLASTLINE, "Dump {} and fix {} are not computed at compatible times{}", diff --git a/src/fix.cpp b/src/fix.cpp index c91990897f9..88d467e8d8e 100644 --- a/src/fix.cpp +++ b/src/fix.cpp @@ -84,7 +84,7 @@ Fix::Fix(LAMMPS *lmp, int /*narg*/, char **arg) : scalar_flag = vector_flag = array_flag = 0; extscalar = extvector = extarray = -1; peratom_flag = local_flag = pergrid_flag = 0; - global_freq = local_freq = peratom_freq = pergrid_freq = -1; + local_freq = peratom_freq = pergrid_freq = -1; size_vector_variable = size_array_rows_variable = 0; comm_forward = comm_reverse = comm_border = 0; From f00a3f292d6f9e017ac9c397391eda2c9b57ffe1 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 20:44:31 -0500 Subject: [PATCH 23/54] add support for dump image to fix reaxff/bonds --- doc/src/fix_reaxff_bonds.rst | 23 +++++++++++ src/MACHDYN/fix_smd_wall_surface.cpp | 2 +- src/REAXFF/fix_reaxff_bonds.cpp | 61 +++++++++++++++++++++++++++- src/REAXFF/fix_reaxff_bonds.h | 9 +++- src/dump_image.cpp | 2 + 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/doc/src/fix_reaxff_bonds.rst b/doc/src/fix_reaxff_bonds.rst index 748fadf23d4..3cff97ae540 100644 --- a/doc/src/fix_reaxff_bonds.rst +++ b/doc/src/fix_reaxff_bonds.rst @@ -67,6 +67,29 @@ timestep numbers have the same length by adding leading zeroes (e.g. 00010 for a pad value of 5). The default pad value is 0, i.e. no leading zeroes. +.. versionadded:: TBD + +If the filename is "NULL", then no output is created. This can be +useful when using fix *reaxff/bonds* in combination with :doc:`dump +image fix ` keyword to visualize the bonds computed by +the ReaxFF force field. + +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +Fix *reaxff/bonds* supports the *fix* keyword of :doc:`dump image +`. The fix will pass geometry information about the bonds +computed by the :doc:`ReaxFF pair style ` to *dump image* +so that they can be included in the rendered image. + +The *fflag1* setting of *dump image fix* determines whether the bonds +will be capped with spheres (1) or not (0). + +The *fflag2* setting allows to adjust diameter of the cylinders for the +bonds. By default, a diameter of 0.5 length units will be used. + ---------- Restart, fix_modify, output, run start/stop, minimize info diff --git a/src/MACHDYN/fix_smd_wall_surface.cpp b/src/MACHDYN/fix_smd_wall_surface.cpp index 645eb3ab973..8510f722cb2 100644 --- a/src/MACHDYN/fix_smd_wall_surface.cpp +++ b/src/MACHDYN/fix_smd_wall_surface.cpp @@ -353,7 +353,7 @@ int FixSMDWallSurface::image(int *&objs, double **&parms) memory->destroy(imgobjs); memory->destroy(imgparms); memory->create(imgobjs, numobjs, "wall_surface:imgobjs"); - memory->create(imgparms, numobjs, 10, "wall_surface:imgobjs"); + memory->create(imgparms, numobjs, 10, "wall_surface:imgparms"); // copy local tri object info numobjs = 0; diff --git a/src/REAXFF/fix_reaxff_bonds.cpp b/src/REAXFF/fix_reaxff_bonds.cpp index 74c1b67bbae..f2b1412f48a 100644 --- a/src/REAXFF/fix_reaxff_bonds.cpp +++ b/src/REAXFF/fix_reaxff_bonds.cpp @@ -21,6 +21,7 @@ #include "atom.h" #include "comm.h" #include "error.h" +#include "dump_image.h" #include "force.h" #include "memory.h" #include "neigh_list.h" @@ -40,7 +41,7 @@ using namespace ReaxFF; FixReaxFFBonds::FixReaxFFBonds(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), neighid(nullptr), abo(nullptr), fp(nullptr), lists(nullptr), - reaxff(nullptr), list(nullptr) + reaxff(nullptr), list(nullptr), imgobjs(nullptr), imgparms(nullptr) { if (narg != 5) error->all(FLERR, Error::NOPOINTER, "Fix reaxff/bonds expected 5 arguments but got {}", narg); @@ -50,9 +51,11 @@ FixReaxFFBonds::FixReaxFFBonds(LAMMPS *lmp, int narg, char **arg) : multifile = 0; padflag = 0; first_flag = true; + numobjs = 0; nevery = utils::inumeric(FLERR,arg[3],false,lmp); if (nevery <= 0) error->all(FLERR, 3, "Illegal fix reaxff/bonds nevery value {}", nevery); + global_freq = nevery; filename = arg[4]; if (filename.rfind('*') != std::string::npos) multifile = 1; @@ -71,6 +74,10 @@ FixReaxFFBonds::~FixReaxFFBonds() destroy(); if (fp) fclose(fp); + + // clean up dump image data + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -140,6 +147,9 @@ void FixReaxFFBonds::Output_ReaxFF_Bonds() } numbonds = FindBond(); + // no file output with NULL file name. + if (filename == "NULL") return; + // allocate a temporary buffer for the snapshot info MPI_Allreduce(&numbonds,&numbonds_max,1,MPI_INT,MPI_MAX,world); MPI_Allreduce(&nlocal,&nlocal_max,1,MPI_INT,MPI_MAX,world); @@ -155,7 +165,6 @@ void FixReaxFFBonds::Output_ReaxFF_Bonds() RecvBuffer(buf, nbuf, nbuf_local, nlocal_tot, numbonds_max); memory->destroy(buf); - } /* ---------------------------------------------------------------------- */ @@ -174,6 +183,7 @@ int FixReaxFFBonds::FindBond() tagint *tag = atom->tag; int numbonds = 0; + numobjs = 0; for (ii = 0; ii < inum; ii++) { i = ilist[ii]; @@ -192,6 +202,7 @@ int FixReaxFFBonds::FindBond() } } numneigh[i] = nj; + numobjs += nj; if (nj > numbonds) numbonds = nj; } return numbonds; @@ -359,3 +370,49 @@ double FixReaxFFBonds::memory_usage() return bytes; } + +/* ---------------------------------------------------------------------- */ + +int FixReaxFFBonds::image(int *&objs, double **&parms) +{ + if (atom->map_style == Atom::MAP_NONE) + error->all(FLERR, Error::NOLASTLINE, + "Using fix reaxff/bonds with dump image requires an atom map"); + + if (!numobjs) return 0; + memory->destroy(imgobjs); + memory->destroy(imgparms); + memory->create(imgobjs, numobjs, "reaxff/bonds:imgobjs"); + memory->create(imgparms, numobjs, 8, "reaxff/bonds:imgparms"); + + const int nlocal = atom->nlocal; + const int *type = atom->type; + const double * const * const x = atom->x; + + int inum = reaxff->list->inum; + int *ilist = reaxff->list->ilist; + + int n = 0; + for (int ii = 0; ii < inum; ++ii) { + int i = ilist[ii]; + for (int jj = 0; jj < numneigh[i]; ++jj) { + int j = atom->map(neighid[i][jj]); + j = domain->closest_image(i,j); + if (j < 0) continue; + imgobjs[n] = DumpImage::BOND; + imgparms[n][0] = type[i]; + imgparms[n][1] = type[j]; + imgparms[n][2] = x[i][0]; + imgparms[n][3] = x[i][1]; + imgparms[n][4] = x[i][2]; + imgparms[n][5] = x[j][0]; + imgparms[n][6] = x[j][1]; + imgparms[n][7] = x[j][2]; + ++n; + } + } + + objs = imgobjs; + parms = imgparms; + return n; +} diff --git a/src/REAXFF/fix_reaxff_bonds.h b/src/REAXFF/fix_reaxff_bonds.h index 1cc912be391..29cd309b530 100644 --- a/src/REAXFF/fix_reaxff_bonds.h +++ b/src/REAXFF/fix_reaxff_bonds.h @@ -33,6 +33,8 @@ class FixReaxFFBonds : public Fix { void setup(int) override; void end_of_step() override; + int image(int *&, double **&) override; + protected: int nmax, compressed, multifile, padflag; int *numneigh; @@ -54,8 +56,13 @@ class FixReaxFFBonds : public Fix { struct _reax_list *lists; class PairReaxFF *reaxff; class NeighList *list; + + // arrays for dump image rendering + + int numobjs; + int *imgobjs; + double **imgparms; }; } // namespace LAMMPS_NS - #endif #endif diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 5b5e8c639a4..31c2c7745f2 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1779,6 +1779,8 @@ void DumpImage::create_image() } else { diameter = bdiamvalue; } + // bond diameter adjustment from dump image command line + diameter += ifix.flag2; // draw bond cylinder in 2 pieces From e73b1015c8bd685a45e64405ba28a5212de0a81c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 15 Dec 2025 22:49:34 -0500 Subject: [PATCH 24/54] add dump image fix support to reflective walls --- doc/src/dump_image.rst | 2 + doc/src/fix_wall.rst | 6 +- doc/src/fix_wall_reflect.rst | 24 ++++ doc/src/fix_wall_reflect_stochastic.rst | 24 ++++ src/KOKKOS/fix_wall_reflect_kokkos.cpp | 3 + src/fix_wall.cpp | 4 +- src/fix_wall_reflect.cpp | 150 +++++++++++++++++++++++- src/fix_wall_reflect.h | 5 + 8 files changed, 215 insertions(+), 3 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index cdbfb387b67..63ba736e3f9 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -524,6 +524,8 @@ objects to be drawn. Below is a list of supported fixes: * :doc:`fix wall/harmonic/outside ` * :doc:`fix wall/lepton ` * :doc:`fix wall/morse ` +* :doc:`fix wall/reflect ` +* :doc:`fix wall/reflect/stochastic ` * :doc:`fix wall/table ` The fix keyword may be used multiple time to include visualizations of diff --git a/doc/src/fix_wall.rst b/doc/src/fix_wall.rst index 568ffdf87f1..1d23112cf11 100644 --- a/doc/src/fix_wall.rst +++ b/doc/src/fix_wall.rst @@ -497,6 +497,8 @@ if you want the interpolation tables of length Ntable to match exactly what is in the tabulated file (with effectively no preliminary interpolation), you should set Ntable = Nfile. +----------------- + Dump image info """"""""""""""" @@ -517,7 +519,9 @@ means both ends are capped. The *fflag2* setting allows to adjust the radius of the rendered cylinder. It should be set to a value > 0 or the cylinder will not be visible since the diameter is set internally to zero due to lack of a suitable heuristic for deriving a meaningful -diameter for all types of walls. +diameter for all types of walls and unit settings. + +----------------- Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/src/fix_wall_reflect.rst b/doc/src/fix_wall_reflect.rst index c2f4c3a6ab8..cfd53cec9a1 100644 --- a/doc/src/fix_wall_reflect.rst +++ b/doc/src/fix_wall_reflect.rst @@ -139,6 +139,30 @@ perturbation on the particles: ---------- +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +These wall fixes support the *fix* keyword of :doc:`dump image +`. The fixes will pass geometry information about the walls +to *dump image* so that the walls will be included in the rendered +image. Please note, that for :doc:`2d systems `, a wall +rendered as a plane would be invisible and it is thus rendered as a +cylinder. + +The *fflag1* setting and the *fflag2* setting of *dump image fix* are +only relevant for 2d systems. The *fflag1* setting determines whether +the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +means the lower end is capped, 2 means the upper end is capped, and 3 +means both ends are capped. The *fflag2* setting allows to adjust the +radius of the rendered cylinder. It should be set to a value > 0 or the +cylinder will not be visible since the diameter is set internally to +zero due to lack of a suitable heuristic for deriving a meaningful +diameter for all types of walls and unit settings. + +---------- + .. include:: accel_styles.rst ---------- diff --git a/doc/src/fix_wall_reflect_stochastic.rst b/doc/src/fix_wall_reflect_stochastic.rst index 5a93950a5da..3f904ca98e3 100644 --- a/doc/src/fix_wall_reflect_stochastic.rst +++ b/doc/src/fix_wall_reflect_stochastic.rst @@ -97,6 +97,30 @@ previously used to define the lattice spacings. ---------- +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +This wall fix supports the *fix* keyword of :doc:`dump image +`. The fix will pass geometry information about the walls +to *dump image* so that the walls will be included in the rendered +image. Please note, that for :doc:`2d systems `, a wall +rendered as a plane would be invisible and it is thus rendered as a +cylinder. + +The *fflag1* setting and the *fflag2* setting of *dump image fix* are +only relevant for 2d systems. The *fflag1* setting determines whether +the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +means the lower end is capped, 2 means the upper end is capped, and 3 +means both ends are capped. The *fflag2* setting allows to adjust the +radius of the rendered cylinder. It should be set to a value > 0 or the +cylinder will not be visible since the diameter is set internally to +zero due to lack of a suitable heuristic for deriving a meaningful +diameter for all types of walls and unit settings. + +---------- + Restrictions """""""""""" diff --git a/src/KOKKOS/fix_wall_reflect_kokkos.cpp b/src/KOKKOS/fix_wall_reflect_kokkos.cpp index 21a7b84aef1..6ae96559243 100644 --- a/src/KOKKOS/fix_wall_reflect_kokkos.cpp +++ b/src/KOKKOS/fix_wall_reflect_kokkos.cpp @@ -66,6 +66,9 @@ void FixWallReflectKokkos::post_integrate() dim = wallwhich[m] / 2; side = wallwhich[m] % 2; + // record wall graphics objects for dump image + wall_update_objs(m,wallwhich[m],coord); + copymode = 1; Kokkos::parallel_for(Kokkos::RangePolicy(0,nlocal),*this); copymode = 0; diff --git a/src/fix_wall.cpp b/src/fix_wall.cpp index 8b993d523a5..5b64ff78134 100644 --- a/src/fix_wall.cpp +++ b/src/fix_wall.cpp @@ -403,8 +403,10 @@ void FixWall::post_force(int vflag) } wall_particle(m, wallwhich[m], coord); + + // record wall info for dump image if (domain->dimension == 2) { - // one cylinder for 2d. we "guess" the diameter by using sigma. can be adjusted with fparam2 + // one cylinder for 2d. diameter is zero and can be set with fparam2 switch (wallwhich[m]) { case XLO: // fallthrough case XHI: diff --git a/src/fix_wall_reflect.cpp b/src/fix_wall_reflect.cpp index 0169644e4ac..f388cb44fc4 100644 --- a/src/fix_wall_reflect.cpp +++ b/src/fix_wall_reflect.cpp @@ -17,9 +17,11 @@ #include "atom.h" #include "comm.h" #include "domain.h" +#include "dump_image.h" #include "error.h" #include "input.h" #include "lattice.h" +#include "memory.h" #include "modify.h" #include "update.h" #include "variable.h" @@ -32,7 +34,7 @@ using namespace FixConst; /* ---------------------------------------------------------------------- */ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : - Fix(lmp, narg, arg), nwall(0), varflag(0) + Fix(lmp, narg, arg), nwall(0), varflag(0), imgobjs(nullptr), imgparms(nullptr) { if (narg < 4) utils::missing_cmd_args(FLERR, "fix wall/reflect", error); @@ -139,6 +141,27 @@ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : varflag = 0; for (int m = 0; m < nwall; m++) if (wallstyle[m] == VARIABLE) varflag = 1; + + // for rendering walls with dump image. + if (domain->dimension == 2) { + // one cylinder object per wall to draw in 2d + memory->create(imgobjs, nwall, "fix_indent:imgobjs"); + memory->create(imgparms, nwall, 8, "fix_indent:imgparms"); + for (int m = 0; m < nwall; ++m) { + imgobjs[m] = DumpImage::CYLINDER; + imgparms[m][0] = 1; // use color of first atom type by default + } + } else { + // two triangle objects per wall to draw in 3d + memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); + memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); + for (int m = 0; m < nwall; ++m) { + imgobjs[2 * m] = DumpImage::TRIANGLE; + imgobjs[2 * m + 1] = DumpImage::TRIANGLE; + imgparms[2 * m][0] = 1; // use color of first atom type by default + imgparms[2 * m + 1][0] = 1; // use color of first atom type by default + } + } } /* ---------------------------------------------------------------------- */ @@ -149,6 +172,9 @@ FixWallReflect::~FixWallReflect() for (int m = 0; m < nwall; m++) if (wallstyle[m] == VARIABLE) delete [] varstr[m]; + + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -202,6 +228,9 @@ void FixWallReflect::post_integrate() } else coord = coord0[m]; wall_particle(m,wallwhich[m],coord); + + // record wall graphics objects for dump image + wall_update_objs(m,wallwhich[m],coord); } if (varflag) modify->addstep_compute(update->ntimestep + 1); @@ -239,3 +268,122 @@ void FixWallReflect::wall_particle(int /* m */, int which, double coord) } } } + +/* ---------------------------------------------------------------------- + update wall graphics object infor for dump image +------------------------------------------------------------------------- */ + +void FixWallReflect::wall_update_objs(int m, int which, double coord) +{ + if (domain->dimension == 2) { + // one cylinder for 2d. diameter is zero and can be set with fparam2 + switch (which) { + case XLO: // fallthrough + case XHI: + imgparms[m][1] = coord; + imgparms[m][2] = domain->boxlo[1]; + imgparms[m][3] = 0.0; + imgparms[m][4] = coord; + imgparms[m][5] = domain->boxhi[1]; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case YLO: // fallthrough + case YHI: + imgparms[m][1] = domain->boxlo[0]; + imgparms[m][2] = coord; + imgparms[m][3] = 0.0; + imgparms[m][4] = domain->boxhi[0]; + imgparms[m][5] = coord; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case ZLO: // fallthrough + case ZHI:; // no wall in z-direction allowed for 2d systems + break; + } + } else { + // two triangles for 3d + switch (which) { + case XLO: // fallthrough + case XHI: + imgparms[2 * m][1] = coord; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = coord; + imgparms[2 * m][5] = domain->boxhi[1]; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = coord; + imgparms[2 * m][8] = domain->boxlo[1]; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = coord; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = coord; + imgparms[2 * m + 1][5] = domain->boxlo[1]; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = coord; + imgparms[2 * m + 1][8] = domain->boxhi[1]; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case YLO: // fallthrough + case YHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = coord; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = coord; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = coord; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = coord; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = coord; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = coord; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case ZLO: // fallthrough + case ZHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = coord; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = domain->boxlo[1]; + imgparms[2 * m][6] = coord; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = domain->boxhi[1]; + imgparms[2 * m][9] = coord; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = coord; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = domain->boxhi[1]; + imgparms[2 * m + 1][6] = coord; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = domain->boxlo[1]; + imgparms[2 * m + 1][9] = coord; + break; + } + } +} + +/* ---------------------------------------------------------------------- + provide graphics information to dump image to render wall as plane + data has been copied to dedicated storage during fix indent execution +------------------------------------------------------------------------- */ + +int FixWallReflect::image(int *&objs, double **&parms) +{ + objs = imgobjs; + parms = imgparms; + if (domain->dimension == 2) { + return nwall; + } else { + return 2 * nwall; + } +} diff --git a/src/fix_wall_reflect.h b/src/fix_wall_reflect.h index 05acfe94cf1..d246faffe3a 100644 --- a/src/fix_wall_reflect.h +++ b/src/fix_wall_reflect.h @@ -35,6 +35,8 @@ class FixWallReflect : public Fix { void init() override; void post_integrate() override; + int image(int *&, double **&) override; + protected: int nwall; int wallwhich[6], wallstyle[6]; @@ -43,8 +45,11 @@ class FixWallReflect : public Fix { int varindex[6]; int varflag; double xscale, yscale, zscale; + int *imgobjs; + double **imgparms; virtual void wall_particle(int m, int which, double coord); + void wall_update_objs(int m, int which, double coord); }; } // namespace LAMMPS_NS From 35fd125608cc6ea6115dd900cc5cb59ebad7c1ee Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 00:43:12 -0500 Subject: [PATCH 25/54] avoid segfault for unknown color with dump modify fcolor --- src/dump_image.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 31c2c7745f2..91b70a8d353 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -928,7 +928,7 @@ void DumpImage::init_style() int nfreq = fixptr->global_freq; if ((nfreq == 0) || (nevery % nfreq)) error->all(FLERR, Error::NOLASTLINE, - "Dump {} and fix {} are not computed at compatible times{}", + "Dump {} and fix {} are not executed at compatible timesteps {}", style, fixptr->style, utils::errorurl(7)); } } @@ -2666,8 +2666,10 @@ int DumpImage::modify_param(int narg, char **arg) if (strcmp(arg[0],"fcolor") == 0) { if (narg < 3) error->all(FLERR,"Illegal dump_modify command"); + auto *color = image->color2rgb(arg[2]); + if (!color) error->all(FLERR, "Unknown color for dump_modify fcolor: {}", arg[2]); for (auto &ifix : fixes) { - if (ifix.id == arg[1]) ifix.rgb = image->color2rgb(arg[2]); + if (ifix.id == arg[1]) ifix.rgb = color; } return 3; } From 5cd1d319b045476bd5c1da21d1a7f088484ed14f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 00:49:20 -0500 Subject: [PATCH 26/54] draft implementation of fix graphics --- src/fix_graphics.cpp | 198 +++++++++++++++++++++++++++++++++++++++++++ src/fix_graphics.h | 89 +++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 src/fix_graphics.cpp create mode 100644 src/fix_graphics.h diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp new file mode 100644 index 00000000000..6b4044da3a8 --- /dev/null +++ b/src/fix_graphics.cpp @@ -0,0 +1,198 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + LAMMPS development team: developers@lammps.org + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#include "fix_graphics.h" + +#include "comm.h" +#include "domain.h" +#include "dump_image.h" +#include "error.h" +#include "input.h" +#include "lattice.h" +#include "memory.h" +#include "modify.h" +#include "update.h" +#include "variable.h" + +#include + +using namespace LAMMPS_NS; +using namespace FixConst; + +enum { SPHERE, ARROW, PROGBAR }; +enum { X, Y, Z }; + +/* ---------------------------------------------------------------------- */ + +FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : + Fix(lmp, narg, arg), imgobjs(nullptr), imgparms(nullptr) +{ + if (narg < 4) utils::missing_cmd_args(FLERR, "fix graphics", error); + + // parse mandatory arg + + nevery = utils::inumeric(FLERR, arg[3], false, lmp); + if (nevery <= 0) error->all(FLERR, 3, "Illegal fix graphics nevery value {}", nevery); + global_freq = nevery; + + numobjs = 0; + varflag = 0; + + int iarg = 4; + while (iarg < narg) { + if (strcmp(arg[iarg], "sphere") == 0) { + if (iarg + 6 > narg) utils::missing_cmd_args(FLERR, "fix graphics sphere", error); + SphereItem sphere{SPHERE, 1, {0.0, 0.0, 0.0}, 0.0, 0, 0, 0, 0, -1, -1, -1, -1}; + sphere.type = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); + if (strstr(arg[iarg + 2], "v_") == arg[iarg + 2]) { + varflag = 1; + sphere.xstr = utils::strdup(arg[iarg + 2] + 2); + } else + sphere.pos[0] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); + if (strstr(arg[iarg + 3], "v_") == arg[iarg + 3]) { + varflag = 1; + sphere.ystr = utils::strdup(arg[iarg + 3] + 2); + } else + sphere.pos[1] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); + if (strstr(arg[iarg + 4], "v_") == arg[iarg + 4]) { + varflag = 1; + sphere.zstr = utils::strdup(arg[iarg + 4] + 2); + } else + sphere.pos[2] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + if (strstr(arg[iarg + 5], "v_") == arg[iarg + 5]) { + varflag = 1; + sphere.dstr = utils::strdup(arg[iarg + 5] + 2); + } else + sphere.diameter = utils::numeric(FLERR, arg[iarg + 5], false, lmp); + GraphicsItem g; + g.sphere = sphere; + items.emplace_back(g); + ++numobjs; + iarg += 6; + } else { + error->all(FLERR, iarg, "Unknown fix graphics keyword {}", arg[iarg]); + } + } + memory->create(imgobjs, numobjs, "fix_graphics:imgobjs"); + memory->create(imgparms, numobjs, 10, "fix_graphics:imgparms"); +} + +/* ---------------------------------------------------------------------- */ + +FixGraphics::~FixGraphics() +{ + memory->destroy(imgobjs); + memory->destroy(imgparms); +} + +/* ---------------------------------------------------------------------- */ + +int FixGraphics::setmask() +{ + return END_OF_STEP; +} + +/* ---------------------------------------------------------------------- */ + +void FixGraphics::init() +{ + int n = 0; + for (auto &gi : items) { + if (gi.style == SPHERE) { + imgobjs[n] = DumpImage::SPHERE; + imgparms[n][0] = gi.sphere.type; + if (gi.sphere.xstr) { + int ivar = input->variable->find(gi.sphere.xstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.sphere.xstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.sphere.xstr); + gi.sphere.xvar = ivar; + } + if (gi.sphere.ystr) { + int ivar = input->variable->find(gi.sphere.ystr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.sphere.ystr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.sphere.ystr); + gi.sphere.yvar = ivar; + } + if (gi.sphere.zstr) { + int ivar = input->variable->find(gi.sphere.zstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.sphere.zstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.sphere.zstr); + gi.sphere.zvar = ivar; + } + if (gi.sphere.dstr) { + int ivar = input->variable->find(gi.sphere.dstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.sphere.dstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.sphere.dstr); + gi.sphere.dvar = ivar; + } + ++n; + } else if (gi.style == ARROW) { + } else if (gi.style == PROGBAR) { + } + } +} + +/* ---------------------------------------------------------------------- */ + +void FixGraphics::end_of_step() +{ + // evaluate variable if necessary, wrap with clear/add + + if (varflag) modify->clearstep_compute(); + + int n = 0; + for (auto &gi : items) { + if (gi.style == SPHERE) { + if (gi.sphere.xstr) gi.sphere.pos[0] = input->variable->compute_equal(gi.sphere.xvar); + if (gi.sphere.ystr) gi.sphere.pos[1] = input->variable->compute_equal(gi.sphere.yvar); + if (gi.sphere.zstr) gi.sphere.pos[2] = input->variable->compute_equal(gi.sphere.zvar); + if (gi.sphere.dstr) gi.sphere.diameter = input->variable->compute_equal(gi.sphere.dvar); + imgparms[n][1] = gi.sphere.pos[0]; + imgparms[n][2] = gi.sphere.pos[1]; + imgparms[n][3] = gi.sphere.pos[2]; + imgparms[n][4] = gi.sphere.diameter; + ++n; + } else if (gi.style == ARROW) { + } else if (gi.style == PROGBAR) { + } + } + + if (varflag) modify->addstep_compute((update->ntimestep / nevery) * nevery + nevery); +} + +/* ---------------------------------------------------------------------- + provide graphics information to dump image +------------------------------------------------------------------------- */ + +int FixGraphics::image(int *&objs, double **&parms) +{ + objs = imgobjs; + parms = imgparms; + return numobjs; +} diff --git a/src/fix_graphics.h b/src/fix_graphics.h new file mode 100644 index 00000000000..9cdd89dc627 --- /dev/null +++ b/src/fix_graphics.h @@ -0,0 +1,89 @@ +/* -*- c++ -*- ---------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + LAMMPS development team: developers@lammps.org + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#ifdef FIX_CLASS +// clang-format off +FixStyle(graphics,FixGraphics); +// clang-format on +#else + +#ifndef LMP_FIX_GRAPHICS_H +#define LMP_FIX_GRAPHICS_H + +#include "fix.h" + +namespace LAMMPS_NS { + +class FixGraphics : public Fix { + public: + FixGraphics(class LAMMPS *, int, char **); + ~FixGraphics() override; + int setmask() override; + void init() override; + void end_of_step() override; + + int image(int *&, double **&) override; + + protected: + int varflag; + int numobjs; + int *imgobjs; + double **imgparms; + + int numitems; + struct SphereItem { + int style; + int type; + double pos[3]; + double diameter; + char *xstr, *ystr, *zstr, *dstr; + int xvar, yvar, zvar, dvar; + }; + + struct ArrowItem { + int style; + int type; + int dim; + int tics; + double pos[3]; + double dir[3]; + double diameter; + double length; + char *xstr, *ystr, *zstr, *dxstr, *dystr, *dzstr, *lstr; + }; + + struct ProgbarItem { + int style; + int type; + int dim; + int tics; + double pos[3]; + double diameter; + double length; + char *pstr; + }; + + union GraphicsItem { + int style; + SphereItem sphere; + ArrowItem arrow; + ProgbarItem progbar; + }; + + std::vector items; +}; + +} // namespace LAMMPS_NS + +#endif +#endif From e546ac499b75b691c4a6c066cce6699f23e10585 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 12:01:08 -0500 Subject: [PATCH 27/54] input radius instead of diameter for consistency with other LAMMPS commands describing spheres --- src/fix_graphics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index 6b4044da3a8..c6d5ec3d6c3 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -73,7 +73,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : varflag = 1; sphere.dstr = utils::strdup(arg[iarg + 5] + 2); } else - sphere.diameter = utils::numeric(FLERR, arg[iarg + 5], false, lmp); + sphere.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 5], false, lmp); GraphicsItem g; g.sphere = sphere; items.emplace_back(g); @@ -172,7 +172,7 @@ void FixGraphics::end_of_step() if (gi.sphere.xstr) gi.sphere.pos[0] = input->variable->compute_equal(gi.sphere.xvar); if (gi.sphere.ystr) gi.sphere.pos[1] = input->variable->compute_equal(gi.sphere.yvar); if (gi.sphere.zstr) gi.sphere.pos[2] = input->variable->compute_equal(gi.sphere.zvar); - if (gi.sphere.dstr) gi.sphere.diameter = input->variable->compute_equal(gi.sphere.dvar); + if (gi.sphere.dstr) gi.sphere.diameter = 2.0 * input->variable->compute_equal(gi.sphere.dvar); imgparms[n][1] = gi.sphere.pos[0]; imgparms[n][2] = gi.sphere.pos[1]; imgparms[n][3] = gi.sphere.pos[2]; From f7728505c16273e903c0f335841d6fc01d58ddb4 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 12:11:19 -0500 Subject: [PATCH 28/54] wrap around type index for fix object coloring if the type exceeds the number of available types --- src/dump_image.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 91b70a8d353..90bf6867d3b 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -282,7 +282,7 @@ using MathConst::DEG2RAD; static constexpr double BIG = 1.0e20; -enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE, CONSTANT}; +enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE, CONSTANT }; enum { STATIC, DYNAMIC }; enum { NO = 0, YES = 1, AUTO = 2 }; enum { FILLED, FRAME, POINTS }; @@ -1708,16 +1708,17 @@ void DumpImage::create_image() int *fixvec = nullptr; double **fixarray = nullptr; + const int ntypes = atom->ntypes; n = ifix.ptr->image(fixvec,fixarray); for (i = 0; i < n; i++) { if (!fixvec || !fixarray) continue; // set color if (ifix.colorstyle == TYPE) { - itype = static_cast(fixarray[i][0]); + itype = static_cast(fixarray[i][0] - 1.0) % ntypes + 1; color = colortype[itype]; } else if (ifix.colorstyle == ELEMENT) { - itype = static_cast(fixarray[i][0]); + itype = static_cast(fixarray[i][0] - 1.0) % ntypes + 1; color = colorelement[itype]; } else if (ifix.colorstyle == CONSTANT) { color = ifix.rgb; @@ -1747,8 +1748,8 @@ void DumpImage::create_image() } else if (fixvec[i] == TRIANGLE) { image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); } else if (fixvec[i] == BOND) { - int type1 = static_cast(fixarray[i][0]); - int type2 = static_cast(fixarray[i][1]); + int type1 = static_cast(fixarray[i][0] - 1.0) % ntypes + 1; + int type2 = static_cast(fixarray[i][1] - 1.0) % ntypes + 1; double *color1; double *color2; if (ifix.colorstyle == TYPE) { From 6ede15644b2eadeda3b0659e3240a1d650a517ff Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 13:45:01 -0500 Subject: [PATCH 29/54] add documentation for fix graphics command --- doc/src/Commands_fix.rst | 1 + doc/src/fix.rst | 1 + doc/src/fix_graphics.rst | 138 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 doc/src/fix_graphics.rst diff --git a/doc/src/Commands_fix.rst b/doc/src/Commands_fix.rst index c1e730a37e0..c810d5b0334 100644 --- a/doc/src/Commands_fix.rst +++ b/doc/src/Commands_fix.rst @@ -83,6 +83,7 @@ OPT. * :doc:`gjf ` * :doc:`gld ` * :doc:`gle ` + * :doc:`graphics ` * :doc:`gravity (ko) ` * :doc:`grem ` * :doc:`halt ` diff --git a/doc/src/fix.rst b/doc/src/fix.rst index cda13dcf7c3..2a71968ba1e 100644 --- a/doc/src/fix.rst +++ b/doc/src/fix.rst @@ -262,6 +262,7 @@ accelerated styles exist. * :doc:`gjf ` - statistically correct Langevin temperature control using the GJ methods * :doc:`gld ` - generalized Langevin dynamics integrator * :doc:`gle ` - generalized Langevin equation thermostat +* :doc:`graphics ` - add graphics objects to :doc:`dump image ` output * :doc:`gravity ` - add gravity to atoms in a granular simulation * :doc:`grem ` - implements the generalized replica exchange method * :doc:`halt ` - terminate a dynamics run or minimization diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst new file mode 100644 index 00000000000..158e2dda940 --- /dev/null +++ b/doc/src/fix_graphics.rst @@ -0,0 +1,138 @@ +.. index:: fix graphics + +fix graphics command +==================== + +Syntax +"""""" + +.. code-block:: LAMMPS + + fix ID group-ID graphics Nevery keyword args ... + +* ID, group-ID are documented in :doc:`fix ` command +* graphics = style name of this fix command +* Nevery = update graphics information every this many time steps +* one or more keyword/args pairs may be appended +* keyword = *sphere* or *units* + + .. parsed-literal:: + + *sphere* args = type x y z R + type = an atom type value to select the color of the sphere + x, y, z = position of the center of the sphere (distance units) + R = sphere radius (distance units) + any of x, y, z, R can be a variable (see below) + +Examples +"""""""" + +.. code-block:: LAMMPS + + fix 1 all graphics 100 sphere 0.0 0.0 15.0 3.0 sphere 0.0 0.0 5.0 1.0 + fix 1 all graphics 1000 sphere v_x v_y 0.0 v_radius + +Description +""""""""""" + +This fix allows to add arbitrary objects to images rendered with +:doc:`dump image ` using the *fix* keyword. + +The *Nevery* keyword determines how often the graphics object data is +updated. This should be the same value as the corresponding *N* +parameter of the :doc:`dump ` image command. LAMMPS will stop +with an error message if the settings for this fix and the dump command +are not compatible. + +The *type* quantity determines the color of the object. Its represents +an *atom* type and the object will be colored the same as the +corresponding atom type when the *type* coloring scheme is used in the +:doc:`dump image fix ` command is used. The color may also +be that of the atom type's element or just a globally set constant color +for *all* objects of this fix instance, which can be changed using a +:doc:`dump modify fcolor ` command. + +Available graphics objects are (see above for exact command line syntax): + +- *sphere* - a sphere defined by its center location and its radius + +Most of the quantities defining a graphics object can be specified as an +equal-style :doc:`variable `, namely *x*, *y*, *z*, or *R* for +a *sphere*. If any of these values is a variable, it should be +specified as `v_name`, where `name` is the variable name. In this case, +the variable will be evaluated each *Nevery* timestep, and its value used +to define the indenter geometry. + +Note that equal-style variables can specify formulas with various +mathematical functions, and include :doc:`thermo_style ` +command keywords for the simulation box parameters and timestep and +elapsed time. Thus it is easy to specify graphics object properties +like position, orientation, radius or more that change as a function of +time or span consecutive runs in a continuous fashion. For the latter, +see the *start* and *stop* keywords of the :doc:`run ` command and +the *elaplong* keyword of :doc:`thermo_style custom ` for +details. + +For example, if a sphere's x-position is specified as v_x, then this +variable definition will keep it's center at a relative position in the +simulation box, 1/4 of the way from the left edge to the right edge, +even if the box size changes: + +.. code-block:: LAMMPS + + variable x equal "xlo + 0.25*lx" + +Similarly, either of these variable definitions will move the sphere +from an initial position at 2.5 at a constant velocity of 5: + +.. code-block:: LAMMPS + + variable x equal "2.5 + 5*elaplong*dt" + variable x equal vdisplace(2.5,5) + +If a sphere's radius is specified as v_r, then these variable +definitions will grow the size of the sphere at a specified rate. + +.. code-block:: LAMMPS + + variable r0 equal 0.0 + variable rate equal 1.0 + variable r equal "v_r0 + step*dt*v_rate" + +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +Fix graphics is designed to be used with the *fix* keyword of :doc:`dump +image `. The fix will pass geometry information about the +objects listed on the command line to *dump image* so that they are +included in the rendered image. + +.. The *fflag1* setting of *dump image fix* has no impact on rendering a + +.. The *fflag2* setting allows you to adjust the radius of the rendered + + +Restart, fix_modify, output, run start/stop, minimize info +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +No information about this fix is written to :doc:`binary restart files +`. + +None of the :doc:`fix_modify ` options apply to this fix. + +Restrictions +"""""""""""" + +none + +Related commands +"""""""""""""""" + +none + +Default +""""""" + +none From 2f61af66847995655e630e60091b8ee64300dbba Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 20:36:08 -0500 Subject: [PATCH 30/54] add cylinder object to fix graphics --- doc/src/fix_graphics.rst | 23 ++++-- src/fix_graphics.cpp | 155 +++++++++++++++++++++++++++++++++++++-- src/fix_graphics.h | 16 +++- 3 files changed, 180 insertions(+), 14 deletions(-) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index 158e2dda940..57de6913403 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -14,7 +14,7 @@ Syntax * graphics = style name of this fix command * Nevery = update graphics information every this many time steps * one or more keyword/args pairs may be appended -* keyword = *sphere* or *units* +* keyword = *sphere* or *cylinder* .. parsed-literal:: @@ -22,15 +22,20 @@ Syntax type = an atom type value to select the color of the sphere x, y, z = position of the center of the sphere (distance units) R = sphere radius (distance units) - any of x, y, z, R can be a variable (see below) + any of x, y, z, and R can be a variable (see below) + *cylinder* args = type x1 y1 z1 x2 y2 z2 R + type = an atom type value to select the color of the sphere + x1, y1, z1, x2, y2, z2 = positions of the centers at the two ends of the cylinder (distance units) + R = cylinder radius (distance units) + any of x1, y1, z1, x2, y2, z2, and R can be a variable (see below) Examples """""""" .. code-block:: LAMMPS - fix 1 all graphics 100 sphere 0.0 0.0 15.0 3.0 sphere 0.0 0.0 5.0 1.0 - fix 1 all graphics 1000 sphere v_x v_y 0.0 v_radius + fix 1 all graphics 100 sphere 1 0.0 0.0 15.0 3.0 sphere 2 0.0 0.0 5.0 1.0 + fix 1 all graphics 1000 sphere 1 v_x v_y 0.0 v_radius cylinder 1 v_x v_y 0.0 v_x v_y 10.0 3.0 Description """"""""""" @@ -55,13 +60,15 @@ for *all* objects of this fix instance, which can be changed using a Available graphics objects are (see above for exact command line syntax): - *sphere* - a sphere defined by its center location and its radius - +- *cylinder* - a cylinder defined by its two center endpoints and its radius + Most of the quantities defining a graphics object can be specified as an equal-style :doc:`variable `, namely *x*, *y*, *z*, or *R* for -a *sphere*. If any of these values is a variable, it should be +a *sphere* or namely *x1*, *y1*, *z1*, *x2*, *y2*, *z2*, or *R* for a +*cylinder*. If any of these values is a variable, it should be specified as `v_name`, where `name` is the variable name. In this case, -the variable will be evaluated each *Nevery* timestep, and its value used -to define the indenter geometry. +the variable will be evaluated each *Nevery* timestep, and its value +used to define the indenter geometry. Note that equal-style variables can specify formulas with various mathematical functions, and include :doc:`thermo_style ` diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index c6d5ec3d6c3..6b2bc60d475 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -25,11 +25,12 @@ #include "variable.h" #include +#include using namespace LAMMPS_NS; using namespace FixConst; -enum { SPHERE, ARROW, PROGBAR }; +enum { SPHERE, CYLINDER, ARROW, PROGBAR }; enum { X, Y, Z }; /* ---------------------------------------------------------------------- */ @@ -52,7 +53,10 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : while (iarg < narg) { if (strcmp(arg[iarg], "sphere") == 0) { if (iarg + 6 > narg) utils::missing_cmd_args(FLERR, "fix graphics sphere", error); - SphereItem sphere{SPHERE, 1, {0.0, 0.0, 0.0}, 0.0, 0, 0, 0, 0, -1, -1, -1, -1}; + // clang-format off + SphereItem sphere{SPHERE, 1, {0.0, 0.0, 0.0}, 0.0, nullptr, nullptr, nullptr, nullptr, + -1, -1, -1, -1}; + // clang-format on sphere.type = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); if (strstr(arg[iarg + 2], "v_") == arg[iarg + 2]) { varflag = 1; @@ -74,11 +78,55 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : sphere.dstr = utils::strdup(arg[iarg + 5] + 2); } else sphere.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 5], false, lmp); - GraphicsItem g; - g.sphere = sphere; - items.emplace_back(g); + items.emplace_back(std::move(sphere)); ++numobjs; iarg += 6; + } else if (strcmp(arg[iarg], "cylinder") == 0) { + if (iarg + 9 > narg) utils::missing_cmd_args(FLERR, "fix graphics cylinder", error); + // clang-format off + CylinderItem cylinder{CYLINDER, 1, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + -1, -1, -1, -1, -1, -1, -1}; + // clang-format on + cylinder.type = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); + if (strstr(arg[iarg + 2], "v_") == arg[iarg + 2]) { + varflag = 1; + cylinder.x1str = utils::strdup(arg[iarg + 2] + 2); + } else + cylinder.pos1[0] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); + if (strstr(arg[iarg + 3], "v_") == arg[iarg + 3]) { + varflag = 1; + cylinder.y1str = utils::strdup(arg[iarg + 3] + 2); + } else + cylinder.pos1[1] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); + if (strstr(arg[iarg + 4], "v_") == arg[iarg + 4]) { + varflag = 1; + cylinder.z1str = utils::strdup(arg[iarg + 4] + 2); + } else + cylinder.pos1[2] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + if (strstr(arg[iarg + 5], "v_") == arg[iarg + 5]) { + varflag = 1; + cylinder.x2str = utils::strdup(arg[iarg + 5] + 2); + } else + cylinder.pos2[0] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + if (strstr(arg[iarg + 6], "v_") == arg[iarg + 6]) { + varflag = 1; + cylinder.y2str = utils::strdup(arg[iarg + 6] + 2); + } else + cylinder.pos2[1] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); + if (strstr(arg[iarg + 7], "v_") == arg[iarg + 7]) { + varflag = 1; + cylinder.z2str = utils::strdup(arg[iarg + 7] + 2); + } else + cylinder.pos2[2] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + if (strstr(arg[iarg + 8], "v_") == arg[iarg + 8]) { + varflag = 1; + cylinder.dstr = utils::strdup(arg[iarg + 8] + 2); + } else + cylinder.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 8], false, lmp); + items.emplace_back(std::move(cylinder)); + ++numobjs; + iarg += 9; } else { error->all(FLERR, iarg, "Unknown fix graphics keyword {}", arg[iarg]); } @@ -152,6 +200,80 @@ void FixGraphics::init() gi.sphere.dvar = ivar; } ++n; + } else if (gi.style == CYLINDER) { + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.cylinder.type; + if (gi.cylinder.x1str) { + int ivar = input->variable->find(gi.cylinder.x1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.x1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.x1str); + gi.cylinder.x1var = ivar; + } + if (gi.cylinder.y1str) { + int ivar = input->variable->find(gi.cylinder.y1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.y1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.y1str); + gi.cylinder.y1var = ivar; + } + if (gi.cylinder.z1str) { + int ivar = input->variable->find(gi.cylinder.z1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.z1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.z1str); + gi.cylinder.z1var = ivar; + } + if (gi.cylinder.x2str) { + int ivar = input->variable->find(gi.cylinder.x2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.x2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.x2str); + gi.cylinder.x2var = ivar; + } + if (gi.cylinder.y2str) { + int ivar = input->variable->find(gi.cylinder.y2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.y2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.y2str); + gi.cylinder.y2var = ivar; + } + if (gi.cylinder.z2str) { + int ivar = input->variable->find(gi.cylinder.z2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.z2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.z2str); + gi.cylinder.z2var = ivar; + } + if (gi.cylinder.dstr) { + int ivar = input->variable->find(gi.cylinder.dstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.cylinder.dstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.cylinder.dstr); + gi.cylinder.dvar = ivar; + } + ++n; } else if (gi.style == ARROW) { } else if (gi.style == PROGBAR) { } @@ -178,6 +300,29 @@ void FixGraphics::end_of_step() imgparms[n][3] = gi.sphere.pos[2]; imgparms[n][4] = gi.sphere.diameter; ++n; + } else if (gi.style == CYLINDER) { + if (gi.cylinder.x1str) + gi.cylinder.pos1[0] = input->variable->compute_equal(gi.cylinder.x1var); + if (gi.cylinder.y1str) + gi.cylinder.pos1[1] = input->variable->compute_equal(gi.cylinder.y1var); + if (gi.cylinder.z1str) + gi.cylinder.pos1[2] = input->variable->compute_equal(gi.cylinder.z1var); + if (gi.cylinder.x2str) + gi.cylinder.pos2[0] = input->variable->compute_equal(gi.cylinder.x2var); + if (gi.cylinder.y2str) + gi.cylinder.pos2[1] = input->variable->compute_equal(gi.cylinder.y2var); + if (gi.cylinder.z2str) + gi.cylinder.pos2[2] = input->variable->compute_equal(gi.cylinder.z2var); + if (gi.cylinder.dstr) + gi.cylinder.diameter = 2.0 * input->variable->compute_equal(gi.cylinder.dvar); + imgparms[n][1] = gi.cylinder.pos1[0]; + imgparms[n][2] = gi.cylinder.pos1[1]; + imgparms[n][3] = gi.cylinder.pos1[2]; + imgparms[n][4] = gi.cylinder.pos2[0]; + imgparms[n][5] = gi.cylinder.pos2[1]; + imgparms[n][6] = gi.cylinder.pos2[2]; + imgparms[n][7] = gi.cylinder.diameter; + ++n; } else if (gi.style == ARROW) { } else if (gi.style == PROGBAR) { } diff --git a/src/fix_graphics.h b/src/fix_graphics.h index 9cdd89dc627..070e62890ce 100644 --- a/src/fix_graphics.h +++ b/src/fix_graphics.h @@ -40,7 +40,6 @@ class FixGraphics : public Fix { int *imgobjs; double **imgparms; - int numitems; struct SphereItem { int style; int type; @@ -50,6 +49,16 @@ class FixGraphics : public Fix { int xvar, yvar, zvar, dvar; }; + struct CylinderItem { + int style; + int type; + double pos1[3]; + double pos2[3]; + double diameter; + char *x1str, *y1str, *z1str, *x2str, *y2str, *z2str, *dstr; + int x1var, y1var, z1var, x2var, y2var, z2var, dvar; + }; + struct ArrowItem { int style; int type; @@ -74,8 +83,13 @@ class FixGraphics : public Fix { }; union GraphicsItem { + GraphicsItem() = delete; + GraphicsItem(const SphereItem &s) : sphere(s) {} + GraphicsItem(const CylinderItem &c) : cylinder(c) {} + int style; SphereItem sphere; + CylinderItem cylinder; ArrowItem arrow; ProgbarItem progbar; }; From 4f0a5dacf4b3bd353f51c08fbdff5ebb632de713 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 16 Dec 2025 23:21:49 -0500 Subject: [PATCH 31/54] add draft/partial versions of arrow and progress bar --- doc/src/fix_graphics.rst | 22 ++- src/dump_image.h | 2 +- src/fix_graphics.cpp | 282 +++++++++++++++++++++++++++++++++++---- src/fix_graphics.h | 19 ++- 4 files changed, 287 insertions(+), 38 deletions(-) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index 57de6913403..ace0e57fabc 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -14,7 +14,7 @@ Syntax * graphics = style name of this fix command * Nevery = update graphics information every this many time steps * one or more keyword/args pairs may be appended -* keyword = *sphere* or *cylinder* +* keyword = *sphere* or *cylinder* or *arrow* or *progbar* .. parsed-literal:: @@ -28,6 +28,22 @@ Syntax x1, y1, z1, x2, y2, z2 = positions of the centers at the two ends of the cylinder (distance units) R = cylinder radius (distance units) any of x1, y1, z1, x2, y2, z2, and R can be a variable (see below) + *arrow* args = type x1 y1 z1 x2 y2 z2 R ratio + type = an atom type value to select the color of the sphere + x1, y1, z1, x2, y2, z2 = positions of the centers at the tip and the bottom of the arrow (distance units) + R = cylinder radius (distance units) + ratio = tip to body ratio (unitless) + any of x1, y1, z1, x2, y2, z2, and R can be a variable (see below) + *progbar* args = type1 type2 dim x y z length ratio R tics + type1 = an atom type value to select the color of the progress bar body and the tics + type2 = an atom type value to select the color of the progress indicator + dim = *x* or *y* or *z*, direction of the progress bar + x, y, z = position of the progress bar center (distance units) + length = length of progress bar (distance units) + ratio = progress status (unitless) + R = cylinder radius (distance units) + tics = number of tics (unitless) + only the progress ratio value can be a variable (see below) Examples """""""" @@ -55,12 +71,14 @@ corresponding atom type when the *type* coloring scheme is used in the :doc:`dump image fix ` command is used. The color may also be that of the atom type's element or just a globally set constant color for *all* objects of this fix instance, which can be changed using a -:doc:`dump modify fcolor ` command. +:doc:`dump modify fcolor ` command. For the *progbar* +object *two* atom type values must be specified. Available graphics objects are (see above for exact command line syntax): - *sphere* - a sphere defined by its center location and its radius - *cylinder* - a cylinder defined by its two center endpoints and its radius +- *arrow* - a cylinder with a cone at one side Most of the quantities defining a graphics object can be specified as an equal-style :doc:`variable `, namely *x*, *y*, *z*, or *R* for diff --git a/src/dump_image.h b/src/dump_image.h index f3219b56ccf..c2e6a65abc2 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -42,7 +42,7 @@ class DumpImage : public DumpCustom { enum { SPHERE, // a single sphere with radius provided LINE, // a cylinder with diameter given through fflag2 - TRI, // a surface mesh as triangles or cylinder mesh based on fflag1, fflag2 set diameter + TRI, // a surface mesh as triangles or cylinder mesh based on fflag1, fflag2 sets diameter CYLINDER, // a cylinder with diameter given by fix, fflag1 choose caps, fflag2 adjusts diameter TRIANGLE, // a regular triangle, no settings apply BOND // two connected cylinders with bond diameter, colored by atom types, fflag1 sets cap diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index 6b2bc60d475..e9d5146dc0c 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -19,6 +19,7 @@ #include "error.h" #include "input.h" #include "lattice.h" +#include "math_extra.h" #include "memory.h" #include "modify.h" #include "update.h" @@ -31,7 +32,44 @@ using namespace LAMMPS_NS; using namespace FixConst; enum { SPHERE, CYLINDER, ARROW, PROGBAR }; -enum { X, Y, Z }; +enum { X = 0, Y, Z }; + +/* ---------------------------------------------------------------------- */ + +FixGraphics::GraphicsItem::~GraphicsItem() +{ + switch (style) { + case SPHERE: + delete[] sphere.xstr; + delete[] sphere.ystr; + delete[] sphere.zstr; + delete[] sphere.dstr; + break; + case CYLINDER: + delete[] cylinder.x1str; + delete[] cylinder.y1str; + delete[] cylinder.z1str; + delete[] cylinder.x2str; + delete[] cylinder.y2str; + delete[] cylinder.z2str; + delete[] cylinder.dstr; + break; + case ARROW: + delete[] arrow.x1str; + delete[] arrow.y1str; + delete[] arrow.z1str; + delete[] arrow.x2str; + delete[] arrow.y2str; + delete[] arrow.z2str; + delete[] arrow.dstr; + break; + case PROGBAR: + delete[] progbar.pstr; + break; + default:; // do nothing + break; + } +} /* ---------------------------------------------------------------------- */ @@ -62,17 +100,17 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : varflag = 1; sphere.xstr = utils::strdup(arg[iarg + 2] + 2); } else - sphere.pos[0] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); + sphere.pos[X] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); if (strstr(arg[iarg + 3], "v_") == arg[iarg + 3]) { varflag = 1; sphere.ystr = utils::strdup(arg[iarg + 3] + 2); } else - sphere.pos[1] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); + sphere.pos[Y] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); if (strstr(arg[iarg + 4], "v_") == arg[iarg + 4]) { varflag = 1; sphere.zstr = utils::strdup(arg[iarg + 4] + 2); } else - sphere.pos[2] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + sphere.pos[Z] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); if (strstr(arg[iarg + 5], "v_") == arg[iarg + 5]) { varflag = 1; sphere.dstr = utils::strdup(arg[iarg + 5] + 2); @@ -93,32 +131,32 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : varflag = 1; cylinder.x1str = utils::strdup(arg[iarg + 2] + 2); } else - cylinder.pos1[0] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); + cylinder.pos1[X] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); if (strstr(arg[iarg + 3], "v_") == arg[iarg + 3]) { varflag = 1; cylinder.y1str = utils::strdup(arg[iarg + 3] + 2); } else - cylinder.pos1[1] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); + cylinder.pos1[Y] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); if (strstr(arg[iarg + 4], "v_") == arg[iarg + 4]) { varflag = 1; cylinder.z1str = utils::strdup(arg[iarg + 4] + 2); } else - cylinder.pos1[2] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + cylinder.pos1[Z] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); if (strstr(arg[iarg + 5], "v_") == arg[iarg + 5]) { varflag = 1; cylinder.x2str = utils::strdup(arg[iarg + 5] + 2); } else - cylinder.pos2[0] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + cylinder.pos2[X] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); if (strstr(arg[iarg + 6], "v_") == arg[iarg + 6]) { varflag = 1; cylinder.y2str = utils::strdup(arg[iarg + 6] + 2); } else - cylinder.pos2[1] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); + cylinder.pos2[Y] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); if (strstr(arg[iarg + 7], "v_") == arg[iarg + 7]) { varflag = 1; cylinder.z2str = utils::strdup(arg[iarg + 7] + 2); } else - cylinder.pos2[2] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + cylinder.pos2[Z] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); if (strstr(arg[iarg + 8], "v_") == arg[iarg + 8]) { varflag = 1; cylinder.dstr = utils::strdup(arg[iarg + 8] + 2); @@ -127,6 +165,90 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : items.emplace_back(std::move(cylinder)); ++numobjs; iarg += 9; + } else if (strcmp(arg[iarg], "arrow") == 0) { + if (iarg + 9 > narg) utils::missing_cmd_args(FLERR, "fix graphics arrow", error); + // clang-format off + ArrowItem arrow{ARROW, 1, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.1, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + -1, -1, -1, -1, -1, -1, -1}; + // clang-format on + arrow.type = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); + if (strstr(arg[iarg + 2], "v_") == arg[iarg + 2]) { + varflag = 1; + arrow.x1str = utils::strdup(arg[iarg + 2] + 2); + } else + arrow.tip[X] = utils::numeric(FLERR, arg[iarg + 2], false, lmp); + if (strstr(arg[iarg + 3], "v_") == arg[iarg + 3]) { + varflag = 1; + arrow.y1str = utils::strdup(arg[iarg + 3] + 2); + } else + arrow.tip[Y] = utils::numeric(FLERR, arg[iarg + 3], false, lmp); + if (strstr(arg[iarg + 4], "v_") == arg[iarg + 4]) { + varflag = 1; + arrow.z1str = utils::strdup(arg[iarg + 4] + 2); + } else + arrow.tip[Z] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + if (strstr(arg[iarg + 5], "v_") == arg[iarg + 5]) { + varflag = 1; + arrow.x2str = utils::strdup(arg[iarg + 5] + 2); + } else + arrow.bot[X] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + if (strstr(arg[iarg + 6], "v_") == arg[iarg + 6]) { + varflag = 1; + arrow.y2str = utils::strdup(arg[iarg + 6] + 2); + } else + arrow.bot[Y] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); + if (strstr(arg[iarg + 7], "v_") == arg[iarg + 7]) { + varflag = 1; + arrow.z2str = utils::strdup(arg[iarg + 7] + 2); + } else + arrow.bot[Z] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + if (strstr(arg[iarg + 8], "v_") == arg[iarg + 8]) { + varflag = 1; + arrow.dstr = utils::strdup(arg[iarg + 8] + 2); + } else + arrow.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 8], false, lmp); + arrow.ratio = utils::numeric(FLERR, arg[iarg + 9], false, lmp); + if ((arrow.ratio < 0.1) || (arrow.ratio > 0.5)) + error->all(FLERR, iarg + 9, "Arrow tip ratio must be between 0.1 and 0.5"); + items.emplace_back(std::move(arrow)); + numobjs += 2; + iarg += 10; + } else if (strcmp(arg[iarg], "progbar") == 0) { + if (iarg + 11 > narg) utils::missing_cmd_args(FLERR, "fix graphics progbar", error); + // clang-format off + ProgbarItem progbar{PROGBAR, 1, 2, Y, 0, {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, nullptr, -1}; + // clang-format on + progbar.type1 = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); + progbar.type2 = utils::inumeric(FLERR, arg[iarg + 2], false, lmp); + if (strcmp(arg[iarg + 3], "x") == 0) { + progbar.dim = X; + } else if (strcmp(arg[iarg + 3], "y") == 0) { + progbar.dim = Y; + } else if (strcmp(arg[iarg + 3], "z") == 0) { + progbar.dim = Z; + } else { + error->all(FLERR, iarg + 3, "Unsupported progress bar dimension string {}", arg[iarg + 3]); + } + progbar.tics = utils::inumeric(FLERR, arg[iarg + 4], false, lmp); + if ((progbar.tics < 0) || (progbar.tics > 10)) + error->all(FLERR, iarg + 4, "Unsupported number of progress bar tics {}", arg[iarg + 4]); + progbar.pos[X] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); + progbar.pos[Y] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); + progbar.pos[Z] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + progbar.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 8], false, lmp); + progbar.length = utils::numeric(FLERR, arg[iarg + 9], false, lmp); + if ((progbar.length <= 0.0) || (progbar.length > domain->prd[progbar.dim])) + error->all(FLERR, iarg + 9, "Illegal progress bar length {}", arg[iarg + 9]); + if (strstr(arg[iarg + 10], "v_") == arg[iarg + 10]) { + varflag = 1; + progbar.pstr = utils::strdup(arg[iarg + 10] + 2); + } else { + progbar.progress = utils::numeric(FLERR, arg[iarg + 10], false, lmp); + } + items.emplace_back(std::move(progbar)); + numobjs += 2 + progbar.tics; + iarg += 11; } else { error->all(FLERR, iarg, "Unknown fix graphics keyword {}", arg[iarg]); } @@ -275,9 +397,86 @@ void FixGraphics::init() } ++n; } else if (gi.style == ARROW) { + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.arrow.type; + if (gi.arrow.x1str) { + int ivar = input->variable->find(gi.arrow.x1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.x1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.x1str); + gi.arrow.x1var = ivar; + } + if (gi.arrow.y1str) { + int ivar = input->variable->find(gi.arrow.y1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.y1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.y1str); + gi.arrow.y1var = ivar; + } + if (gi.arrow.z1str) { + int ivar = input->variable->find(gi.arrow.z1str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.z1str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.z1str); + gi.arrow.z1var = ivar; + } + if (gi.arrow.x2str) { + int ivar = input->variable->find(gi.arrow.x2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.x2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.x2str); + gi.arrow.x2var = ivar; + } + if (gi.arrow.y2str) { + int ivar = input->variable->find(gi.arrow.y2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.y2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.y2str); + gi.arrow.y2var = ivar; + } + if (gi.arrow.z2str) { + int ivar = input->variable->find(gi.arrow.z2str); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.z2str); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.z2str); + gi.arrow.z2var = ivar; + } + if (gi.arrow.dstr) { + int ivar = input->variable->find(gi.arrow.dstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.arrow.dstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.arrow.dstr); + gi.arrow.dvar = ivar; + } + ++n; + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.arrow.type; + ++n; } else if (gi.style == PROGBAR) { } } + end_of_step(); } /* ---------------------------------------------------------------------- */ @@ -291,39 +490,66 @@ void FixGraphics::end_of_step() int n = 0; for (auto &gi : items) { if (gi.style == SPHERE) { - if (gi.sphere.xstr) gi.sphere.pos[0] = input->variable->compute_equal(gi.sphere.xvar); - if (gi.sphere.ystr) gi.sphere.pos[1] = input->variable->compute_equal(gi.sphere.yvar); - if (gi.sphere.zstr) gi.sphere.pos[2] = input->variable->compute_equal(gi.sphere.zvar); + if (gi.sphere.xstr) gi.sphere.pos[X] = input->variable->compute_equal(gi.sphere.xvar); + if (gi.sphere.ystr) gi.sphere.pos[Y] = input->variable->compute_equal(gi.sphere.yvar); + if (gi.sphere.zstr) gi.sphere.pos[Z] = input->variable->compute_equal(gi.sphere.zvar); if (gi.sphere.dstr) gi.sphere.diameter = 2.0 * input->variable->compute_equal(gi.sphere.dvar); - imgparms[n][1] = gi.sphere.pos[0]; - imgparms[n][2] = gi.sphere.pos[1]; - imgparms[n][3] = gi.sphere.pos[2]; + imgparms[n][1] = gi.sphere.pos[X]; + imgparms[n][2] = gi.sphere.pos[Y]; + imgparms[n][3] = gi.sphere.pos[Z]; imgparms[n][4] = gi.sphere.diameter; ++n; } else if (gi.style == CYLINDER) { if (gi.cylinder.x1str) - gi.cylinder.pos1[0] = input->variable->compute_equal(gi.cylinder.x1var); + gi.cylinder.pos1[X] = input->variable->compute_equal(gi.cylinder.x1var); if (gi.cylinder.y1str) - gi.cylinder.pos1[1] = input->variable->compute_equal(gi.cylinder.y1var); + gi.cylinder.pos1[Y] = input->variable->compute_equal(gi.cylinder.y1var); if (gi.cylinder.z1str) - gi.cylinder.pos1[2] = input->variable->compute_equal(gi.cylinder.z1var); + gi.cylinder.pos1[Z] = input->variable->compute_equal(gi.cylinder.z1var); if (gi.cylinder.x2str) - gi.cylinder.pos2[0] = input->variable->compute_equal(gi.cylinder.x2var); + gi.cylinder.pos2[X] = input->variable->compute_equal(gi.cylinder.x2var); if (gi.cylinder.y2str) - gi.cylinder.pos2[1] = input->variable->compute_equal(gi.cylinder.y2var); + gi.cylinder.pos2[Y] = input->variable->compute_equal(gi.cylinder.y2var); if (gi.cylinder.z2str) - gi.cylinder.pos2[2] = input->variable->compute_equal(gi.cylinder.z2var); + gi.cylinder.pos2[Z] = input->variable->compute_equal(gi.cylinder.z2var); if (gi.cylinder.dstr) gi.cylinder.diameter = 2.0 * input->variable->compute_equal(gi.cylinder.dvar); - imgparms[n][1] = gi.cylinder.pos1[0]; - imgparms[n][2] = gi.cylinder.pos1[1]; - imgparms[n][3] = gi.cylinder.pos1[2]; - imgparms[n][4] = gi.cylinder.pos2[0]; - imgparms[n][5] = gi.cylinder.pos2[1]; - imgparms[n][6] = gi.cylinder.pos2[2]; + imgparms[n][1] = gi.cylinder.pos1[X]; + imgparms[n][2] = gi.cylinder.pos1[Y]; + imgparms[n][3] = gi.cylinder.pos1[Z]; + imgparms[n][4] = gi.cylinder.pos2[X]; + imgparms[n][5] = gi.cylinder.pos2[Y]; + imgparms[n][6] = gi.cylinder.pos2[Z]; imgparms[n][7] = gi.cylinder.diameter; ++n; } else if (gi.style == ARROW) { + if (gi.arrow.x1str) gi.arrow.tip[X] = input->variable->compute_equal(gi.arrow.x1var); + if (gi.arrow.y1str) gi.arrow.tip[Y] = input->variable->compute_equal(gi.arrow.y1var); + if (gi.arrow.z1str) gi.arrow.tip[Z] = input->variable->compute_equal(gi.arrow.z1var); + if (gi.arrow.x2str) gi.arrow.bot[X] = input->variable->compute_equal(gi.arrow.x2var); + if (gi.arrow.y2str) gi.arrow.bot[Y] = input->variable->compute_equal(gi.arrow.y2var); + if (gi.arrow.z2str) gi.arrow.bot[Z] = input->variable->compute_equal(gi.arrow.z2var); + if (gi.arrow.dstr) gi.arrow.diameter = 2.0 * input->variable->compute_equal(gi.arrow.dvar); + + double mid[3], vec[3]; + MathExtra::sub3(gi.arrow.bot, gi.arrow.tip, vec); + MathExtra::scaleadd3(gi.arrow.ratio, vec, gi.arrow.tip, mid); + imgparms[n][1] = gi.arrow.tip[X]; + imgparms[n][2] = gi.arrow.tip[Y]; + imgparms[n][3] = gi.arrow.tip[Z]; + imgparms[n][4] = mid[X]; + imgparms[n][5] = mid[Y]; + imgparms[n][6] = mid[Z]; + imgparms[n][7] = gi.arrow.diameter * (1.0 + 5.0 * gi.arrow.ratio); + ++n; + imgparms[n][1] = mid[X]; + imgparms[n][2] = mid[Y]; + imgparms[n][3] = mid[Z]; + imgparms[n][4] = gi.arrow.bot[X]; + imgparms[n][5] = gi.arrow.bot[Y]; + imgparms[n][6] = gi.arrow.bot[Z]; + imgparms[n][7] = gi.arrow.diameter; + ++n; } else if (gi.style == PROGBAR) { } } diff --git a/src/fix_graphics.h b/src/fix_graphics.h index 070e62890ce..4a9954d0b7d 100644 --- a/src/fix_graphics.h +++ b/src/fix_graphics.h @@ -62,30 +62,35 @@ class FixGraphics : public Fix { struct ArrowItem { int style; int type; - int dim; - int tics; - double pos[3]; - double dir[3]; + double tip[3]; + double bot[3]; double diameter; - double length; - char *xstr, *ystr, *zstr, *dxstr, *dystr, *dzstr, *lstr; + double ratio; + char *x1str, *y1str, *z1str, *x2str, *y2str, *z2str, *dstr; + int x1var, y1var, z1var, x2var, y2var, z2var, dvar; }; struct ProgbarItem { int style; - int type; + int type1; + int type2; int dim; int tics; double pos[3]; double diameter; double length; + double progress; char *pstr; + int pvar; }; union GraphicsItem { GraphicsItem() = delete; GraphicsItem(const SphereItem &s) : sphere(s) {} GraphicsItem(const CylinderItem &c) : cylinder(c) {} + GraphicsItem(const ArrowItem &a) : arrow(a) {} + GraphicsItem(const ProgbarItem &p) : progbar(p) {} + ~GraphicsItem(); int style; SphereItem sphere; From dd010d9f8268e474ca062198cb6f57a31c0f5acb Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 02:04:47 -0500 Subject: [PATCH 32/54] complete progress bar implementation --- doc/src/fix_graphics.rst | 19 +++- src/fix_graphics.cpp | 210 +++++++++++++++++++++++++++++---------- src/fix_graphics.h | 1 - 3 files changed, 173 insertions(+), 57 deletions(-) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index ace0e57fabc..96003d35a58 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -34,14 +34,14 @@ Syntax R = cylinder radius (distance units) ratio = tip to body ratio (unitless) any of x1, y1, z1, x2, y2, z2, and R can be a variable (see below) - *progbar* args = type1 type2 dim x y z length ratio R tics + *progbar* args = type1 type2 dim x y z length R ratio tics type1 = an atom type value to select the color of the progress bar body and the tics type2 = an atom type value to select the color of the progress indicator dim = *x* or *y* or *z*, direction of the progress bar x, y, z = position of the progress bar center (distance units) length = length of progress bar (distance units) - ratio = progress status (unitless) R = cylinder radius (distance units) + ratio = progress status (unitless) tics = number of tics (unitless) only the progress ratio value can be a variable (see below) @@ -52,6 +52,7 @@ Examples fix 1 all graphics 100 sphere 1 0.0 0.0 15.0 3.0 sphere 2 0.0 0.0 5.0 1.0 fix 1 all graphics 1000 sphere 1 v_x v_y 0.0 v_radius cylinder 1 v_x v_y 0.0 v_x v_y 10.0 3.0 + fix 2 all graphics 100 progbar 3 1 z 0.012 -0.012 0.0025 0.03 0.0003 v_prog 10 Description """"""""""" @@ -79,8 +80,9 @@ Available graphics objects are (see above for exact command line syntax): - *sphere* - a sphere defined by its center location and its radius - *cylinder* - a cylinder defined by its two center endpoints and its radius - *arrow* - a cylinder with a cone at one side +- *progbar* - progress bar a long a selected axis and with optional tick marks -Most of the quantities defining a graphics object can be specified as an +Many of the quantities defining a graphics object can be specified as an equal-style :doc:`variable `, namely *x*, *y*, *z*, or *R* for a *sphere* or namely *x1*, *y1*, *z1*, *x2*, *y2*, *z2*, or *R* for a *cylinder*. If any of these values is a variable, it should be @@ -134,9 +136,16 @@ image `. The fix will pass geometry information about the objects listed on the command line to *dump image* so that they are included in the rendered image. -.. The *fflag1* setting of *dump image fix* has no impact on rendering a +The *fflag1* setting of *dump image fix* determines whether cylinder +elements are capped with spheres: 0 means no caps, 1 means the lower +end is capped, 2 means the upper end is capped, and 3 means both ends +are capped. This applies to the *cylinder* object and also to the +body of the *arrow* object and the elements of the *progbar* object. -.. The *fflag2* setting allows you to adjust the radius of the rendered +The *fflag2* setting allows you to adjust the radius of the rendered +sphere and cylinder items comprising the objects. Since the radius of +these objects is an input parameter for this fix, it is recommended to +set this flag to 0.0. Restart, fix_modify, output, run start/stop, minimize info diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index e9d5146dc0c..b10540ba38c 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -36,43 +36,6 @@ enum { X = 0, Y, Z }; /* ---------------------------------------------------------------------- */ -FixGraphics::GraphicsItem::~GraphicsItem() -{ - switch (style) { - case SPHERE: - delete[] sphere.xstr; - delete[] sphere.ystr; - delete[] sphere.zstr; - delete[] sphere.dstr; - break; - case CYLINDER: - delete[] cylinder.x1str; - delete[] cylinder.y1str; - delete[] cylinder.z1str; - delete[] cylinder.x2str; - delete[] cylinder.y2str; - delete[] cylinder.z2str; - delete[] cylinder.dstr; - break; - case ARROW: - delete[] arrow.x1str; - delete[] arrow.y1str; - delete[] arrow.z1str; - delete[] arrow.x2str; - delete[] arrow.y2str; - delete[] arrow.z2str; - delete[] arrow.dstr; - break; - case PROGBAR: - delete[] progbar.pstr; - break; - default:; // do nothing - break; - } -} - -/* ---------------------------------------------------------------------- */ - FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), imgobjs(nullptr), imgparms(nullptr) { @@ -230,22 +193,22 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : } else { error->all(FLERR, iarg + 3, "Unsupported progress bar dimension string {}", arg[iarg + 3]); } - progbar.tics = utils::inumeric(FLERR, arg[iarg + 4], false, lmp); - if ((progbar.tics < 0) || (progbar.tics > 10)) - error->all(FLERR, iarg + 4, "Unsupported number of progress bar tics {}", arg[iarg + 4]); - progbar.pos[X] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); - progbar.pos[Y] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); - progbar.pos[Z] = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + progbar.pos[X] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + progbar.pos[Y] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); + progbar.pos[Z] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); + progbar.length = utils::numeric(FLERR, arg[iarg + 7], false, lmp); + if ((progbar.length <= 0.0) || (progbar.length > 2.0*domain->prd[progbar.dim])) + error->all(FLERR, iarg + 7, "Illegal progress bar length {}", arg[iarg + 7]); progbar.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 8], false, lmp); - progbar.length = utils::numeric(FLERR, arg[iarg + 9], false, lmp); - if ((progbar.length <= 0.0) || (progbar.length > domain->prd[progbar.dim])) - error->all(FLERR, iarg + 9, "Illegal progress bar length {}", arg[iarg + 9]); - if (strstr(arg[iarg + 10], "v_") == arg[iarg + 10]) { + if (strstr(arg[iarg + 9], "v_") == arg[iarg + 9]) { varflag = 1; - progbar.pstr = utils::strdup(arg[iarg + 10] + 2); + progbar.pstr = utils::strdup(arg[iarg + 9] + 2); } else { - progbar.progress = utils::numeric(FLERR, arg[iarg + 10], false, lmp); + progbar.progress = utils::numeric(FLERR, arg[iarg + 9], false, lmp); } + progbar.tics = utils::inumeric(FLERR, arg[iarg + 10], false, lmp); + if ((progbar.tics < 0) || (progbar.tics > 20)) + error->all(FLERR, iarg + 10, "Unsupported number of progress bar tics {}", arg[iarg + 10]); items.emplace_back(std::move(progbar)); numobjs += 2 + progbar.tics; iarg += 11; @@ -254,13 +217,47 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : } } memory->create(imgobjs, numobjs, "fix_graphics:imgobjs"); - memory->create(imgparms, numobjs, 10, "fix_graphics:imgparms"); + memory->create(imgparms, numobjs, 8, "fix_graphics:imgparms"); } /* ---------------------------------------------------------------------- */ FixGraphics::~FixGraphics() { + for (auto &gi : items) { + switch (gi.style) { + case SPHERE: + delete[] gi.sphere.xstr; + delete[] gi.sphere.ystr; + delete[] gi.sphere.zstr; + delete[] gi.sphere.dstr; + break; + case CYLINDER: + delete[] gi.cylinder.x1str; + delete[] gi.cylinder.y1str; + delete[] gi.cylinder.z1str; + delete[] gi.cylinder.x2str; + delete[] gi.cylinder.y2str; + delete[] gi.cylinder.z2str; + delete[] gi.cylinder.dstr; + break; + case ARROW: + delete[] gi.arrow.x1str; + delete[] gi.arrow.y1str; + delete[] gi.arrow.z1str; + delete[] gi.arrow.x2str; + delete[] gi.arrow.y2str; + delete[] gi.arrow.z2str; + delete[] gi.arrow.dstr; + break; + case PROGBAR: + delete[] gi.progbar.pstr; + break; + default:; // do nothing + break; + } + } + memory->destroy(imgobjs); memory->destroy(imgparms); } @@ -474,6 +471,101 @@ void FixGraphics::init() imgparms[n][0] = gi.arrow.type; ++n; } else if (gi.style == PROGBAR) { + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.progbar.type1; + imgparms[n][1] = gi.progbar.pos[X]; + imgparms[n][2] = gi.progbar.pos[Y]; + imgparms[n][3] = gi.progbar.pos[Z]; + imgparms[n][4] = gi.progbar.pos[X]; + imgparms[n][5] = gi.progbar.pos[Y]; + imgparms[n][6] = gi.progbar.pos[Z]; + imgparms[n][7] = gi.progbar.diameter; + switch (gi.progbar.dim) { + case X: + imgparms[n][1] -= 0.5 * gi.progbar.length; + imgparms[n][4] += 0.5 * gi.progbar.length; + break; + case Y: + imgparms[n][2] -= 0.5 * gi.progbar.length; + imgparms[n][5] += 0.5 * gi.progbar.length; + break; + case Z: + imgparms[n][3] -= 0.5 * gi.progbar.length; + imgparms[n][6] += 0.5 * gi.progbar.length; + break; + default:; // do nothing + } + ++n; + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.progbar.type2; + imgparms[n][1] = gi.progbar.pos[X]; + imgparms[n][2] = gi.progbar.pos[Y]; + imgparms[n][3] = gi.progbar.pos[Z]; + imgparms[n][4] = gi.progbar.pos[X]; + imgparms[n][5] = gi.progbar.pos[Y]; + imgparms[n][6] = gi.progbar.pos[Z]; + imgparms[n][7] = 0.75 * gi.progbar.diameter; + switch (gi.progbar.dim) { + case X: + imgparms[n][1] -= 0.5 * gi.progbar.length; + imgparms[n][4] -= 0.5 * gi.progbar.length; + imgparms[n][3] += 0.2 * gi.progbar.diameter; + imgparms[n][6] += 0.2 * gi.progbar.diameter; + break; + case Y: + imgparms[n][2] -= 0.5 * gi.progbar.length; + imgparms[n][5] -= 0.5 * gi.progbar.length; + imgparms[n][1] += 0.15 * gi.progbar.diameter; + imgparms[n][4] += 0.15 * gi.progbar.diameter; + break; + case Z: + imgparms[n][3] -= 0.5 * gi.progbar.length; + imgparms[n][6] -= 0.5 * gi.progbar.length; + imgparms[n][1] += 0.15 * gi.progbar.diameter; + imgparms[n][4] += 0.15 * gi.progbar.diameter; + break; + default: + break; + } + ++n; + double delta = gi.progbar.length / (double) (gi.progbar.tics - 1); + double lo = gi.progbar.pos[gi.progbar.dim] - 0.5 * gi.progbar.length; + for (int i = 0; i < gi.progbar.tics; ++i) { + imgobjs[n] = DumpImage::CYLINDER; + imgparms[n][0] = gi.progbar.type1; + imgparms[n][1] = gi.progbar.pos[X]; + imgparms[n][2] = gi.progbar.pos[Y]; + imgparms[n][3] = gi.progbar.pos[Z]; + imgparms[n][4] = gi.progbar.pos[X]; + imgparms[n][5] = gi.progbar.pos[Y]; + imgparms[n][6] = gi.progbar.pos[Z]; + imgparms[n][7] = 1.1 * gi.progbar.diameter; + switch (gi.progbar.dim) { + case X: + imgparms[n][1] = lo + delta * i - 0.05 * delta; + imgparms[n][4] = lo + delta * i + 0.05 * delta; + break; + case Y: + imgparms[n][2] = lo + delta * i - 0.05 * delta; + imgparms[n][5] = lo + delta * i + 0.05 * delta; + break; + case Z: + imgparms[n][3] = lo + delta * i - 0.05 * delta; + imgparms[n][6] = lo + delta * i + 0.05 * delta; + break; + } + ++n; + } + if (gi.progbar.pstr) { + int ivar = input->variable->find(gi.progbar.pstr); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Variable name {} for fix graphics does not exist", + gi.progbar.pstr); + if (input->variable->equalstyle(ivar) == 0) + error->all(FLERR, Error::NOLASTLINE, + "Fix graphics variable {} is not equal-style variable", gi.progbar.pstr); + gi.progbar.pvar = ivar; + } } } end_of_step(); @@ -551,9 +643,25 @@ void FixGraphics::end_of_step() imgparms[n][7] = gi.arrow.diameter; ++n; } else if (gi.style == PROGBAR) { + ++n; + if (gi.progbar.pstr) gi.progbar.progress = input->variable->compute_equal(gi.progbar.pvar); + switch (gi.progbar.dim) { + case X: + imgparms[n][1] = gi.progbar.pos[X] + (gi.progbar.progress - 0.5) * gi.progbar.length; + break; + case Y: + imgparms[n][2] = gi.progbar.pos[Y] + (gi.progbar.progress - 0.5) * gi.progbar.length; + break; + case Z: + imgparms[n][3] = gi.progbar.pos[Z] + (gi.progbar.progress - 0.5) * gi.progbar.length; + break; + default: + break; + } + ++n; + n += gi.progbar.tics; } } - if (varflag) modify->addstep_compute((update->ntimestep / nevery) * nevery + nevery); } diff --git a/src/fix_graphics.h b/src/fix_graphics.h index 4a9954d0b7d..0ec79ebdc21 100644 --- a/src/fix_graphics.h +++ b/src/fix_graphics.h @@ -90,7 +90,6 @@ class FixGraphics : public Fix { GraphicsItem(const CylinderItem &c) : cylinder(c) {} GraphicsItem(const ArrowItem &a) : arrow(a) {} GraphicsItem(const ProgbarItem &p) : progbar(p) {} - ~GraphicsItem(); int style; SphereItem sphere; From 4e0d42bf6b5e7bec7b899689542f39aaa5e12774 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 03:09:45 -0500 Subject: [PATCH 33/54] add missing .. versionadded:: tag --- doc/src/fix_graphics.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index 96003d35a58..b67039293bb 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -57,6 +57,8 @@ Examples Description """"""""""" +.. versionadded:: TBD + This fix allows to add arbitrary objects to images rendered with :doc:`dump image ` using the *fix* keyword. From c00437429f7c5fe5cb13abd6f9c7669880f9db76 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 03:23:42 -0500 Subject: [PATCH 34/54] implement fixes suggested by GitHub Copilot --- src/dump_image.cpp | 2 +- src/fix_graphics.cpp | 4 ++-- src/fix_wall_reflect.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 90bf6867d3b..75cced78d2a 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -1536,7 +1536,7 @@ void DumpImage::create_image() } else if (adiam == TYPE) { diameter = MIN(diamtype[type[atom1]],diamtype[type[atom2]]); } else if (adiam == ELEMENT) { - diameter = MIN(diamelement[type[atom2]],diamelement[type[atom2]]); + diameter = MIN(diamelement[type[atom1]],diamelement[type[atom2]]); } else if (adiam == ATTRIBUTE) { diameter = MIN(bufcopy[atom1][1],bufcopy[atom2][1]); } diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index b10540ba38c..b1f637e2224 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -109,7 +109,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : varflag = 1; cylinder.x2str = utils::strdup(arg[iarg + 5] + 2); } else - cylinder.pos2[X] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + cylinder.pos2[X] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); if (strstr(arg[iarg + 6], "v_") == arg[iarg + 6]) { varflag = 1; cylinder.y2str = utils::strdup(arg[iarg + 6] + 2); @@ -155,7 +155,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : varflag = 1; arrow.x2str = utils::strdup(arg[iarg + 5] + 2); } else - arrow.bot[X] = utils::numeric(FLERR, arg[iarg + 4], false, lmp); + arrow.bot[X] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); if (strstr(arg[iarg + 6], "v_") == arg[iarg + 6]) { varflag = 1; arrow.y2str = utils::strdup(arg[iarg + 6] + 2); diff --git a/src/fix_wall_reflect.cpp b/src/fix_wall_reflect.cpp index f388cb44fc4..e3c453a2dc1 100644 --- a/src/fix_wall_reflect.cpp +++ b/src/fix_wall_reflect.cpp @@ -270,7 +270,7 @@ void FixWallReflect::wall_particle(int /* m */, int which, double coord) } /* ---------------------------------------------------------------------- - update wall graphics object infor for dump image + update wall graphics object info for dump image ------------------------------------------------------------------------- */ void FixWallReflect::wall_update_objs(int m, int which, double coord) From 09423898fa5d1f13ff49aad2ce9fe24b686e2edf Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 11:21:28 -0500 Subject: [PATCH 35/54] grammar --- doc/src/fix_graphics.rst | 2 +- doc/src/fix_indent.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index b67039293bb..3ec94fabbc2 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -103,7 +103,7 @@ the *elaplong* keyword of :doc:`thermo_style custom ` for details. For example, if a sphere's x-position is specified as v_x, then this -variable definition will keep it's center at a relative position in the +variable definition will keep its center at a relative position in the simulation box, 1/4 of the way from the left edge to the right edge, even if the box size changes: diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 1b7a72e9bb4..3fcaddd3b06 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -137,9 +137,9 @@ fashion. For the latter, see the *start* and *stop* keywords of the :doc:`thermo_style custom ` for details. For example, if a spherical indenter's x-position is specified as v_x, -then this variable definition will keep it's center at a relative -position in the simulation box, 1/4 of the way from the left edge to -the right edge, even if the box size changes: +then this variable definition will keep its center at a relative +position in the simulation box, 1/4 of the way from the left edge to the +right edge, even if the box size changes: .. code-block:: LAMMPS From 3f956fcca814cf4e911411a5b07f69ba9fdd0f46 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 13:32:34 -0500 Subject: [PATCH 36/54] update fix graphics docs and add example --- doc/src/JPG/fix-graphics-example.png | Bin 0 -> 101132 bytes doc/src/fix_graphics.rst | 43 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 doc/src/JPG/fix-graphics-example.png diff --git a/doc/src/JPG/fix-graphics-example.png b/doc/src/JPG/fix-graphics-example.png new file mode 100644 index 0000000000000000000000000000000000000000..d9b1d4a5cbfa34f72f65b5b86612995eb1af6018 GIT binary patch literal 101132 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 Q`Um%^DQPQK$XkT|9~W<~egFUf literal 0 HcmV?d00001 diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index 3ec94fabbc2..2a3628b2e5f 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -81,9 +81,19 @@ Available graphics objects are (see above for exact command line syntax): - *sphere* - a sphere defined by its center location and its radius - *cylinder* - a cylinder defined by its two center endpoints and its radius -- *arrow* - a cylinder with a cone at one side +- *arrow* - a cylinder with a cone at one side (see note below) - *progbar* - progress bar a long a selected axis and with optional tick marks +.. admonition:: Work in progress notice + :class: note + + The *arrow* object is currently composed of two cylinders since the + :doc:`dump image ` render implementation is missing a + primitive to render a cone. This fix will be updated when that + functionality becomes available. + +---------------------- + Many of the quantities defining a graphics object can be specified as an equal-style :doc:`variable `, namely *x*, *y*, *z*, or *R* for a *sphere* or namely *x1*, *y1*, *z1*, *x2*, *y2*, *z2*, or *R* for a @@ -149,6 +159,37 @@ sphere and cylinder items comprising the objects. Since the radius of these objects is an input parameter for this fix, it is recommended to set this flag to 0.0. +.. figure:: JPG/fix-graphics-example.png + :figclass: align-center + + Example of graphics objects rendered with *fix graphics* with *fflag1* setting of 0 (left) and 3 (right) + +These images were created with the following input file: + +.. code-block:: LAMMPS + + units si + region simulation_box block -0.01 0.01 -0.01 0.01 -0.01 0.01 units box + create_box 4 simulation_box + mass * 1 + + variable xpos equal 0.004*sin(PI*step/1000) + variable ypos equal 0.004*cos(PI*step/1000) + variable zpos equal 5.0*v_xpos + variable prog equal (step)/10000.0 + fix gra all graphics 50 sphere 1 v_xpos v_ypos -0.009 0.002 & + sphere 1 0.01 -0.005 0.01 0.005 & + progbar 3 1 z 0.012 -0.012 0.002 0.02 0.0005 v_prog 10 & + cylinder 4 0.01 -0.005 -0.01 0.01 -0.005 0.01 0.005 & + arrow 2 v_xpos v_ypos 0.0 v_xpos v_ypos 0.01 0.0005 0.2 + + dump viz all image 100 myimage2-*.ppm type type size 600 600 zoom 1.24872 & + shiny 0.2 fsaa yes ssao yes 4539 0.6 box no 0.01 axes no 0.5 0.025 & + fix gra type 0 0 view 75 5 + dump_modify viz pad 9 backcolor black acolor 3 gray + + run 2000 + Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" From c15eefb9941352f3705a32dd0d3236c07e763a32 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 15:46:56 -0500 Subject: [PATCH 37/54] consolidate the code to render planar walls and constants into FixWall This removes some duplicated code and makes certain that XLO/XHI and alike are defined consistently. This prepares for adding image info for walls to fix wall/gran --- src/EXTRA-FIX/fix_wall_reflect_stochastic.cpp | 25 +-- src/KOKKOS/fix_wall_reflect_kokkos.cpp | 7 +- src/fix_wall.cpp | 201 +++++++++--------- src/fix_wall.h | 1 + src/fix_wall_reflect.cpp | 134 ++---------- src/fix_wall_reflect.h | 2 - 6 files changed, 137 insertions(+), 233 deletions(-) diff --git a/src/EXTRA-FIX/fix_wall_reflect_stochastic.cpp b/src/EXTRA-FIX/fix_wall_reflect_stochastic.cpp index 42bb1980495..d07a25409a4 100644 --- a/src/EXTRA-FIX/fix_wall_reflect_stochastic.cpp +++ b/src/EXTRA-FIX/fix_wall_reflect_stochastic.cpp @@ -23,6 +23,7 @@ #include "comm.h" #include "domain.h" #include "error.h" +#include "fix_wall.h" #include "force.h" #include "lattice.h" #include "random_mars.h" @@ -79,12 +80,12 @@ FixWallReflectStochastic(LAMMPS *lmp, int narg, char **arg) : error->all(FLERR,"Illegal fix wall/reflect/stochastic command"); int newwall; - if (strcmp(arg[iarg],"xlo") == 0) newwall = XLO; - else if (strcmp(arg[iarg],"xhi") == 0) newwall = XHI; - else if (strcmp(arg[iarg],"ylo") == 0) newwall = YLO; - else if (strcmp(arg[iarg],"yhi") == 0) newwall = YHI; - else if (strcmp(arg[iarg],"zlo") == 0) newwall = ZLO; - else if (strcmp(arg[iarg],"zhi") == 0) newwall = ZHI; + if (strcmp(arg[iarg],"xlo") == 0) newwall = FixWall::XLO; + else if (strcmp(arg[iarg],"xhi") == 0) newwall = FixWall::XHI; + else if (strcmp(arg[iarg],"ylo") == 0) newwall = FixWall::YLO; + else if (strcmp(arg[iarg],"yhi") == 0) newwall = FixWall::YHI; + else if (strcmp(arg[iarg],"zlo") == 0) newwall = FixWall::ZLO; + else if (strcmp(arg[iarg],"zhi") == 0) newwall = FixWall::ZHI; for (int m = 0; (m < nwall) && (m < 6); m++) if (newwall == wallwhich[m]) @@ -139,19 +140,19 @@ FixWallReflectStochastic(LAMMPS *lmp, int narg, char **arg) : if (nwall == 0) error->all(FLERR,"Illegal fix wall command"); for (int m = 0; m < nwall; m++) { - if ((wallwhich[m] == XLO || wallwhich[m] == XHI) && domain->xperiodic) + if ((wallwhich[m] == FixWall::XLO || wallwhich[m] == FixWall::XHI) && domain->xperiodic) error->all(FLERR,"Cannot use fix wall/reflect/stochastic " "in periodic dimension"); - if ((wallwhich[m] == YLO || wallwhich[m] == YHI) && domain->yperiodic) + if ((wallwhich[m] == FixWall::YLO || wallwhich[m] == FixWall::YHI) && domain->yperiodic) error->all(FLERR,"Cannot use fix wall/reflect/stochastic " "in periodic dimension"); - if ((wallwhich[m] == ZLO || wallwhich[m] == ZHI) && domain->zperiodic) + if ((wallwhich[m] == FixWall::ZLO || wallwhich[m] == FixWall::ZHI) && domain->zperiodic) error->all(FLERR,"Cannot use fix wall/reflect/stochastic " "in periodic dimension"); } for (int m = 0; m < nwall; m++) - if ((wallwhich[m] == ZLO || wallwhich[m] == ZHI) && domain->dimension == 2) + if ((wallwhich[m] == FixWall::ZLO || wallwhich[m] == FixWall::ZHI) && domain->dimension == 2) error->all(FLERR, "Cannot use fix wall/reflect/stochastic zlo/zhi " "for a 2d simulation"); @@ -171,8 +172,8 @@ FixWallReflectStochastic(LAMMPS *lmp, int narg, char **arg) : for (int m = 0; m < nwall; m++) { if (wallstyle[m] != CONSTANT) continue; - if (wallwhich[m] < YLO) coord0[m] *= xscale; - else if (wallwhich[m] < ZLO) coord0[m] *= yscale; + if (wallwhich[m] < FixWall::YLO) coord0[m] *= xscale; + else if (wallwhich[m] < FixWall::ZLO) coord0[m] *= yscale; else coord0[m] *= zscale; } } diff --git a/src/KOKKOS/fix_wall_reflect_kokkos.cpp b/src/KOKKOS/fix_wall_reflect_kokkos.cpp index 6ae96559243..4e5262fcb58 100644 --- a/src/KOKKOS/fix_wall_reflect_kokkos.cpp +++ b/src/KOKKOS/fix_wall_reflect_kokkos.cpp @@ -17,6 +17,7 @@ #include "atom_kokkos.h" #include "atom_masks.h" #include "input.h" +#include "fix_wall.h" #include "modify.h" #include "update.h" #include "variable.h" @@ -58,8 +59,8 @@ void FixWallReflectKokkos::post_integrate() for (int m = 0; m < nwall; m++) { if (wallstyle[m] == VARIABLE) { coord = input->variable->compute_equal(varindex[m]); - if (wallwhich[m] < YLO) coord *= xscale; - else if (wallwhich[m] < ZLO) coord *= yscale; + if (wallwhich[m] < FixWall::YLO) coord *= xscale; + else if (wallwhich[m] < FixWall::ZLO) coord *= yscale; else coord *= zscale; } else coord = coord0[m]; @@ -67,7 +68,7 @@ void FixWallReflectKokkos::post_integrate() side = wallwhich[m] % 2; // record wall graphics objects for dump image - wall_update_objs(m,wallwhich[m],coord); + FixWall::update_image_plane(m, wallwhich[m], coord, imgparms, domain); copymode = 1; Kokkos::parallel_for(Kokkos::RangePolicy(0,nlocal),*this); diff --git a/src/fix_wall.cpp b/src/fix_wall.cpp index 5b64ff78134..3b5b206a897 100644 --- a/src/fix_wall.cpp +++ b/src/fix_wall.cpp @@ -31,6 +31,107 @@ using namespace FixConst; static const char *wallpos[] = {"xlo", "xhi", "ylo", "yhi", "zlo", "zhi"}; +// record wall info for dump image + +void FixWall::update_image_plane(int m, int which, double coord, double **imgparms, Domain *domain) +{ + if (domain->dimension == 2) { + // one cylinder for 2d. diameter is zero and can be set with fparam2 + switch (which) { + case XLO: // fallthrough + case XHI: + imgparms[m][1] = coord; + imgparms[m][2] = domain->boxlo[1]; + imgparms[m][3] = 0.0; + imgparms[m][4] = coord; + imgparms[m][5] = domain->boxhi[1]; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case YLO: // fallthrough + case YHI: + imgparms[m][1] = domain->boxlo[0]; + imgparms[m][2] = coord; + imgparms[m][3] = 0.0; + imgparms[m][4] = domain->boxhi[0]; + imgparms[m][5] = coord; + imgparms[m][6] = 0.0; + imgparms[m][7] = 0.0; + break; + case ZLO: // fallthrough + case ZHI:; // no wall in z-direction allowed for 2d systems + break; + } + } else { + // two triangles for 3d + switch (which) { + case XLO: // fallthrough + case XHI: + imgparms[2 * m][1] = coord; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = coord; + imgparms[2 * m][5] = domain->boxhi[1]; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = coord; + imgparms[2 * m][8] = domain->boxlo[1]; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = coord; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = coord; + imgparms[2 * m + 1][5] = domain->boxlo[1]; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = coord; + imgparms[2 * m + 1][8] = domain->boxhi[1]; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case YLO: // fallthrough + case YHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = coord; + imgparms[2 * m][3] = domain->boxlo[2]; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = coord; + imgparms[2 * m][6] = domain->boxlo[2]; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = coord; + imgparms[2 * m][9] = domain->boxhi[2]; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = coord; + imgparms[2 * m + 1][3] = domain->boxhi[2]; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = coord; + imgparms[2 * m + 1][6] = domain->boxhi[2]; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = coord; + imgparms[2 * m + 1][9] = domain->boxlo[2]; + break; + case ZLO: // fallthrough + case ZHI: + imgparms[2 * m][1] = domain->boxlo[0]; + imgparms[2 * m][2] = domain->boxlo[1]; + imgparms[2 * m][3] = coord; + imgparms[2 * m][4] = domain->boxhi[0]; + imgparms[2 * m][5] = domain->boxlo[1]; + imgparms[2 * m][6] = coord; + imgparms[2 * m][7] = domain->boxlo[0]; + imgparms[2 * m][8] = domain->boxhi[1]; + imgparms[2 * m][9] = coord; + imgparms[2 * m + 1][1] = domain->boxhi[0]; + imgparms[2 * m + 1][2] = domain->boxhi[1]; + imgparms[2 * m + 1][3] = coord; + imgparms[2 * m + 1][4] = domain->boxlo[0]; + imgparms[2 * m + 1][5] = domain->boxhi[1]; + imgparms[2 * m + 1][6] = coord; + imgparms[2 * m + 1][7] = domain->boxhi[0]; + imgparms[2 * m + 1][8] = domain->boxlo[1]; + imgparms[2 * m + 1][9] = coord; + break; + } + } +} + /* ---------------------------------------------------------------------- */ FixWall::FixWall(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), nwall(0) @@ -351,7 +452,7 @@ void FixWall::min_setup(int vflag) /* ---------------------------------------------------------------------- only called if fldflag set, in place of post_force -------------------------------------------------------------------------- */ + ------------------------------------------------------------------------- */ void FixWall::pre_force(int vflag) { @@ -404,104 +505,8 @@ void FixWall::post_force(int vflag) wall_particle(m, wallwhich[m], coord); - // record wall info for dump image - if (domain->dimension == 2) { - // one cylinder for 2d. diameter is zero and can be set with fparam2 - switch (wallwhich[m]) { - case XLO: // fallthrough - case XHI: - imgparms[m][1] = coord; - imgparms[m][2] = domain->boxlo[1]; - imgparms[m][3] = 0.0; - imgparms[m][4] = coord; - imgparms[m][5] = domain->boxhi[1]; - imgparms[m][6] = 0.0; - imgparms[m][7] = 0.0; - break; - case YLO: // fallthrough - case YHI: - imgparms[m][1] = domain->boxlo[0]; - imgparms[m][2] = coord; - imgparms[m][3] = 0.0; - imgparms[m][4] = domain->boxhi[0]; - imgparms[m][5] = coord; - imgparms[m][6] = 0.0; - imgparms[m][7] = 0.0; - break; - case ZLO: // fallthrough - case ZHI:; // no wall in z-direction allowed for 2d systems - break; - } - } else { - // two triangles for 3d - switch (wallwhich[m]) { - case XLO: // fallthrough - case XHI: - imgparms[2 * m][1] = coord; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = coord; - imgparms[2 * m][5] = domain->boxhi[1]; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = coord; - imgparms[2 * m][8] = domain->boxlo[1]; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = coord; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = coord; - imgparms[2 * m + 1][5] = domain->boxlo[1]; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = coord; - imgparms[2 * m + 1][8] = domain->boxhi[1]; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case YLO: // fallthrough - case YHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = coord; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = coord; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = coord; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = coord; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = coord; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = coord; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case ZLO: // fallthrough - case ZHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = coord; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = domain->boxlo[1]; - imgparms[2 * m][6] = coord; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = domain->boxhi[1]; - imgparms[2 * m][9] = coord; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = coord; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = domain->boxhi[1]; - imgparms[2 * m + 1][6] = coord; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = domain->boxlo[1]; - imgparms[2 * m + 1][9] = coord; - break; - } - } + update_image_plane(m, wallwhich[m], coord, imgparms, domain); } - if (varflag) modify->addstep_compute(update->ntimestep + 1); } diff --git a/src/fix_wall.h b/src/fix_wall.h index f7b8d4bcae3..682d9785e3e 100644 --- a/src/fix_wall.h +++ b/src/fix_wall.h @@ -47,6 +47,7 @@ class FixWall : public Fix { virtual void precompute(int) = 0; virtual void wall_particle(int, int, double) = 0; + static void update_image_plane(int, int, double, double **, class Domain *); protected: double epsilon[6], sigma[6], alpha[6], cutoff[6]; diff --git a/src/fix_wall_reflect.cpp b/src/fix_wall_reflect.cpp index e3c453a2dc1..4e133f656d4 100644 --- a/src/fix_wall_reflect.cpp +++ b/src/fix_wall_reflect.cpp @@ -19,6 +19,7 @@ #include "domain.h" #include "dump_image.h" #include "error.h" +#include "fix_wall.h" #include "input.h" #include "lattice.h" #include "memory.h" @@ -57,12 +58,12 @@ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : if (iarg+2 > narg) error->all(FLERR, "Illegal fix wall/reflect {} command: missing argument(s)", arg[iarg]); int newwall; - if (strcmp(arg[iarg],"xlo") == 0) newwall = XLO; - else if (strcmp(arg[iarg],"xhi") == 0) newwall = XHI; - else if (strcmp(arg[iarg],"ylo") == 0) newwall = YLO; - else if (strcmp(arg[iarg],"yhi") == 0) newwall = YHI; - else if (strcmp(arg[iarg],"zlo") == 0) newwall = ZLO; - else if (strcmp(arg[iarg],"zhi") == 0) newwall = ZHI; + if (strcmp(arg[iarg],"xlo") == 0) newwall = FixWall::XLO; + else if (strcmp(arg[iarg],"xhi") == 0) newwall = FixWall::XHI; + else if (strcmp(arg[iarg],"ylo") == 0) newwall = FixWall::YLO; + else if (strcmp(arg[iarg],"yhi") == 0) newwall = FixWall::YHI; + else if (strcmp(arg[iarg],"zlo") == 0) newwall = FixWall::ZLO; + else if (strcmp(arg[iarg],"zhi") == 0) newwall = FixWall::ZHI; for (int m = 0; (m < nwall) && (m < 6); m++) if (newwall == wallwhich[m]) @@ -101,16 +102,16 @@ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : if (nwall == 0) utils::missing_cmd_args(FLERR, "fix wall/reflect", error); for (int m = 0; m < nwall; m++) { - if ((wallwhich[m] == XLO || wallwhich[m] == XHI) && domain->xperiodic) + if ((wallwhich[m] == FixWall::XLO || wallwhich[m] == FixWall::XHI) && domain->xperiodic) error->all(FLERR,"Cannot use fix wall/reflect in periodic dimension x"); - if ((wallwhich[m] == YLO || wallwhich[m] == YHI) && domain->yperiodic) + if ((wallwhich[m] == FixWall::YLO || wallwhich[m] == FixWall::YHI) && domain->yperiodic) error->all(FLERR,"Cannot use fix wall/reflect in periodic dimension y"); - if ((wallwhich[m] == ZLO || wallwhich[m] == ZHI) && domain->zperiodic) + if ((wallwhich[m] == FixWall::ZLO || wallwhich[m] == FixWall::ZHI) && domain->zperiodic) error->all(FLERR,"Cannot use fix wall/reflect in periodic dimension z"); } for (int m = 0; m < nwall; m++) - if ((wallwhich[m] == ZLO || wallwhich[m] == ZHI) && domain->dimension == 2) + if ((wallwhich[m] == FixWall::ZLO || wallwhich[m] == FixWall::ZHI) && domain->dimension == 2) error->all(FLERR, "Cannot use fix wall/reflect zlo/zhi for a 2d simulation"); @@ -130,8 +131,8 @@ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : for (int m = 0; m < nwall; m++) { if (wallstyle[m] != CONSTANT) continue; - if (wallwhich[m] < YLO) coord0[m] *= xscale; - else if (wallwhich[m] < ZLO) coord0[m] *= yscale; + if (wallwhich[m] < FixWall::YLO) coord0[m] *= xscale; + else if (wallwhich[m] < FixWall::ZLO) coord0[m] *= yscale; else coord0[m] *= zscale; } } @@ -222,15 +223,15 @@ void FixWallReflect::post_integrate() for (int m = 0; m < nwall; m++) { if (wallstyle[m] == VARIABLE) { coord = input->variable->compute_equal(varindex[m]); - if (wallwhich[m] < YLO) coord *= xscale; - else if (wallwhich[m] < ZLO) coord *= yscale; + if (wallwhich[m] < FixWall::YLO) coord *= xscale; + else if (wallwhich[m] < FixWall::ZLO) coord *= yscale; else coord *= zscale; } else coord = coord0[m]; wall_particle(m,wallwhich[m],coord); // record wall graphics objects for dump image - wall_update_objs(m,wallwhich[m],coord); + FixWall::update_image_plane(m, wallwhich[m], coord, imgparms, domain); } if (varflag) modify->addstep_compute(update->ntimestep + 1); @@ -269,109 +270,6 @@ void FixWallReflect::wall_particle(int /* m */, int which, double coord) } } -/* ---------------------------------------------------------------------- - update wall graphics object info for dump image -------------------------------------------------------------------------- */ - -void FixWallReflect::wall_update_objs(int m, int which, double coord) -{ - if (domain->dimension == 2) { - // one cylinder for 2d. diameter is zero and can be set with fparam2 - switch (which) { - case XLO: // fallthrough - case XHI: - imgparms[m][1] = coord; - imgparms[m][2] = domain->boxlo[1]; - imgparms[m][3] = 0.0; - imgparms[m][4] = coord; - imgparms[m][5] = domain->boxhi[1]; - imgparms[m][6] = 0.0; - imgparms[m][7] = 0.0; - break; - case YLO: // fallthrough - case YHI: - imgparms[m][1] = domain->boxlo[0]; - imgparms[m][2] = coord; - imgparms[m][3] = 0.0; - imgparms[m][4] = domain->boxhi[0]; - imgparms[m][5] = coord; - imgparms[m][6] = 0.0; - imgparms[m][7] = 0.0; - break; - case ZLO: // fallthrough - case ZHI:; // no wall in z-direction allowed for 2d systems - break; - } - } else { - // two triangles for 3d - switch (which) { - case XLO: // fallthrough - case XHI: - imgparms[2 * m][1] = coord; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = coord; - imgparms[2 * m][5] = domain->boxhi[1]; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = coord; - imgparms[2 * m][8] = domain->boxlo[1]; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = coord; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = coord; - imgparms[2 * m + 1][5] = domain->boxlo[1]; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = coord; - imgparms[2 * m + 1][8] = domain->boxhi[1]; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case YLO: // fallthrough - case YHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = coord; - imgparms[2 * m][3] = domain->boxlo[2]; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = coord; - imgparms[2 * m][6] = domain->boxlo[2]; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = coord; - imgparms[2 * m][9] = domain->boxhi[2]; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = coord; - imgparms[2 * m + 1][3] = domain->boxhi[2]; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = coord; - imgparms[2 * m + 1][6] = domain->boxhi[2]; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = coord; - imgparms[2 * m + 1][9] = domain->boxlo[2]; - break; - case ZLO: // fallthrough - case ZHI: - imgparms[2 * m][1] = domain->boxlo[0]; - imgparms[2 * m][2] = domain->boxlo[1]; - imgparms[2 * m][3] = coord; - imgparms[2 * m][4] = domain->boxhi[0]; - imgparms[2 * m][5] = domain->boxlo[1]; - imgparms[2 * m][6] = coord; - imgparms[2 * m][7] = domain->boxlo[0]; - imgparms[2 * m][8] = domain->boxhi[1]; - imgparms[2 * m][9] = coord; - imgparms[2 * m + 1][1] = domain->boxhi[0]; - imgparms[2 * m + 1][2] = domain->boxhi[1]; - imgparms[2 * m + 1][3] = coord; - imgparms[2 * m + 1][4] = domain->boxlo[0]; - imgparms[2 * m + 1][5] = domain->boxhi[1]; - imgparms[2 * m + 1][6] = coord; - imgparms[2 * m + 1][7] = domain->boxhi[0]; - imgparms[2 * m + 1][8] = domain->boxlo[1]; - imgparms[2 * m + 1][9] = coord; - break; - } - } -} - /* ---------------------------------------------------------------------- provide graphics information to dump image to render wall as plane data has been copied to dedicated storage during fix indent execution diff --git a/src/fix_wall_reflect.h b/src/fix_wall_reflect.h index d246faffe3a..8f8e8efd07d 100644 --- a/src/fix_wall_reflect.h +++ b/src/fix_wall_reflect.h @@ -26,7 +26,6 @@ namespace LAMMPS_NS { class FixWallReflect : public Fix { public: - enum { XLO = 0, XHI = 1, YLO = 2, YHI = 3, ZLO = 4, ZHI = 5 }; enum { NONE = 0, EDGE, CONSTANT, VARIABLE }; FixWallReflect(class LAMMPS *, int, char **); @@ -49,7 +48,6 @@ class FixWallReflect : public Fix { double **imgparms; virtual void wall_particle(int m, int which, double coord); - void wall_update_objs(int m, int which, double coord); }; } // namespace LAMMPS_NS From 3fcc428a72da46e07a53e494ee806595bddafabf Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 15:47:49 -0500 Subject: [PATCH 38/54] add dump image support to fix wall/gran mostly reusing and adapting to FixWall code --- doc/src/dump_image.rst | 7 +- doc/src/fix_wall_gran.rst | 26 ++++++ src/GRANULAR/fix_wall_gran.cpp | 152 ++++++++++++++++++++++++++++++--- src/GRANULAR/fix_wall_gran.h | 8 ++ 4 files changed, 177 insertions(+), 16 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 63ba736e3f9..39fa297c97d 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -520,6 +520,7 @@ objects to be drawn. Below is a list of supported fixes: * :doc:`fix wall/lj126 ` * :doc:`fix wall/lj1043 ` * :doc:`fix wall/colloid ` +* :doc:`fix wall/gran ` * :doc:`fix wall/harmonic ` * :doc:`fix wall/harmonic/outside ` * :doc:`fix wall/lepton ` @@ -528,9 +529,9 @@ objects to be drawn. Below is a list of supported fixes: * :doc:`fix wall/reflect/stochastic ` * :doc:`fix wall/table ` -The fix keyword may be used multiple time to include visualizations of -object from multiple fixes. The fix keyword is followed by the -:doc:`fix ID ` of the fix, the color style setting and two +The fix keyword may be used multiple times to include visualizations of +graphics objects from multiple fixes. The fix keyword is followed by +the :doc:`fix ID ` of the fix, the color style setting and two numerical values *fflag1* and *fflag2*. The color style may be either *type*, *element*, or *const*. The first diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index 81a411ffc87..c23619bc860 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -261,6 +261,32 @@ No parameter of this fix can be used with the *start/stop* keywords of the :doc:`run ` command. This fix is not invoked during :doc:`energy minimization `. +----------------- + +Dump image info +""""""""""""""" + +.. versionadded:: TBD + +This fix supports the *fix* keyword of :doc:`dump image `. +The fix will pass geometry information about *xplane*\, *yplane*\, and +*zplane style walls to *dump image* so that the walls will be included +in the rendered image. Please note, that for :doc:`2d systems +`, a wall rendered as a plane would be invisible and it is +thus rendered as a cylinder. + +The *fflag1* setting and the *fflag2* setting of *dump image fix* are +only relevant for 2d systems. The *fflag1* setting determines whether +the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +means the lower end is capped, 2 means the upper end is capped, and 3 +means both ends are capped. The *fflag2* setting allows to adjust the +radius of the rendered cylinder. It should be set to a value > 0 or the +cylinder will not be visible since the diameter is set internally to +zero due to lack of a suitable heuristic for deriving a meaningful +diameter for all types of walls and unit settings. + +----------------- + Restrictions """""""""""" diff --git a/src/GRANULAR/fix_wall_gran.cpp b/src/GRANULAR/fix_wall_gran.cpp index d58ce011f96..0fe4114589a 100644 --- a/src/GRANULAR/fix_wall_gran.cpp +++ b/src/GRANULAR/fix_wall_gran.cpp @@ -23,7 +23,9 @@ #include "granular_model.h" #include "gran_sub_mod.h" #include "domain.h" +#include "dump_image.h" #include "error.h" +#include "fix_wall.h" #include "input.h" #include "math_const.h" #include "math_extra.h" @@ -54,7 +56,7 @@ enum {NONE,CONSTANT,EQUAL}; FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), idregion(nullptr), tstr(nullptr), history_one(nullptr), - fix_rigid(nullptr), mass_rigid(nullptr) + fix_rigid(nullptr), mass_rigid(nullptr), imgobjs(nullptr), imgparms(nullptr) { if (narg < 4) utils::missing_cmd_args(FLERR,"fix wall/gran", error); @@ -128,41 +130,71 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : // wallstyle args + numwalls = 0; if (iarg >= narg) error->all(FLERR, "Illegal fix wall/gran command"); if (strcmp(arg[iarg],"xplane") == 0) { if (narg < iarg+3) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = XPLANE; - if (strcmp(arg[iarg+1],"NULL") == 0) lo = -BIG; - else lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); - if (strcmp(arg[iarg+2],"NULL") == 0) hi = BIG; - else hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + numwalls = 0; + if (strcmp(arg[iarg+1],"NULL") == 0) { + lo = -BIG; + } else { + lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); + ++numwalls; + } + if (strcmp(arg[iarg+2],"NULL") == 0) { + hi = BIG; + } else { + hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + ++numwalls; + } iarg += 3; } else if (strcmp(arg[iarg],"yplane") == 0) { if (narg < iarg+3) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = YPLANE; - if (strcmp(arg[iarg+1],"NULL") == 0) lo = -BIG; - else lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); - if (strcmp(arg[iarg+2],"NULL") == 0) hi = BIG; - else hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + numwalls = 0; + if (strcmp(arg[iarg+1],"NULL") == 0) { + lo = -BIG; + } else { + lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); + ++numwalls; + } + if (strcmp(arg[iarg+2],"NULL") == 0) { + hi = BIG; + } else { + hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + ++numwalls; + } iarg += 3; } else if (strcmp(arg[iarg],"zplane") == 0) { if (narg < iarg+3) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = ZPLANE; - if (strcmp(arg[iarg+1],"NULL") == 0) lo = -BIG; - else lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); - if (strcmp(arg[iarg+2],"NULL") == 0) hi = BIG; - else hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + numwalls = 0; + if (strcmp(arg[iarg+1],"NULL") == 0) { + lo = -BIG; + } else { + lo = utils::numeric(FLERR,arg[iarg+1],false,lmp); + ++numwalls; + } + if (strcmp(arg[iarg+2],"NULL") == 0) { + hi = BIG; + } else { + hi = utils::numeric(FLERR,arg[iarg+2],false,lmp); + ++numwalls; + } iarg += 3; } else if (strcmp(arg[iarg],"zcylinder") == 0) { if (narg < iarg+2) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = ZCYLINDER; + numwalls = 1; lo = hi = 0.0; cylradius = utils::numeric(FLERR,arg[iarg+1],false,lmp); iarg += 2; } else if (strcmp(arg[iarg],"region") == 0) { if (narg < iarg+2) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = REGION; + numwalls = 0; delete[] idregion; idregion = utils::strdup(arg[iarg+1]); iarg += 2; @@ -271,6 +303,37 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : } time_origin = update->ntimestep; + + // for rendering walls with dump image. + if ((wallstyle == XPLANE) || (wallstyle == YPLANE) || (wallstyle == ZPLANE)) { + if (domain->dimension == 2) { + // one cylinder object per wall to draw in 2d + memory->create(imgobjs, numwalls, "fix_indent:imgobjs"); + memory->create(imgparms, numwalls, 8, "fix_indent:imgparms"); + for (int m = 0; m < numwalls; ++m) { + imgobjs[m] = DumpImage::CYLINDER; + imgparms[m][0] = 1; // use color of first atom type by default + } + } else { + // two triangle objects per wall to draw in 3d + memory->create(imgobjs, 2 * numwalls, "fix_indent:imgobjs"); + memory->create(imgparms, 2 * numwalls, 10, "fix_indent:imgparms"); + for (int m = 0; m < numwalls; ++m) { + imgobjs[2 * m] = DumpImage::TRIANGLE; + imgobjs[2 * m + 1] = DumpImage::TRIANGLE; + imgparms[2 * m][0] = 1; // use color of first atom type by default + imgparms[2 * m + 1][0] = 1; // use color of first atom type by default + } + } + } else if (wallstyle == ZCYLINDER) { + // one cylinder object per wall to draw + memory->create(imgobjs, numwalls, "fix_indent:imgobjs"); + memory->create(imgparms, numwalls, 8, "fix_indent:imgparms"); + for (int m = 0; m < numwalls; ++m) { + imgobjs[m] = DumpImage::CYLINDER; + imgparms[m][0] = 1; // use color of first atom type by default + } + } } /* ---------------------------------------------------------------------- */ @@ -291,6 +354,9 @@ FixWallGran::~FixWallGran() delete[] idregion; memory->destroy(history_one); memory->destroy(mass_rigid); + + memory->destroy(imgobjs); + memory->destroy(imgparms); } /* ---------------------------------------------------------------------- */ @@ -553,6 +619,37 @@ void FixWallGran::post_force(int /*vflag*/) array_atom[i][8 + n] = model->svector[n]; } } + + // update wall image information + int m = 0; + if (wallstyle == XPLANE) { + if (lo != -BIG) { + FixWall::update_image_plane(m, FixWall::XLO, wlo, imgparms, domain); + ++m; + } + if (hi != BIG) { + FixWall::update_image_plane(m, FixWall::XHI, whi, imgparms, domain); + ++m; + } + } else if (wallstyle == YPLANE) { + if (lo != -BIG) { + FixWall::update_image_plane(m, FixWall::YLO, wlo, imgparms, domain); + ++m; + } + if (hi != BIG) { + FixWall::update_image_plane(m, FixWall::YHI, whi, imgparms, domain); + ++m; + } + } else if (wallstyle == ZPLANE) { + if (lo != -BIG) { + FixWall::update_image_plane(m, FixWall::ZLO, wlo, imgparms, domain); + ++m; + } + if (hi != BIG) { + FixWall::update_image_plane(m, FixWall::ZHI, whi, imgparms, domain); + ++m; + } + } } /* ---------------------------------------------------------------------- */ @@ -731,3 +828,32 @@ void FixWallGran::reset_dt() dt = update->dt; model->dt = dt; } + +/* ---------------------------------------------------------------------- + provide graphics information to dump image to render wall as plane + data has been copied to dedicated storage during fix indent execution +------------------------------------------------------------------------- */ + +int FixWallGran::image(int *&objs, double **&parms) +{ + objs = imgobjs; + parms = imgparms; + switch (wallstyle) { + case XPLANE: // fallthrough + case YPLANE: // fallthrough + case ZPLANE: + if (domain->dimension == 2) { + return numwalls; + } else { + return 2 * numwalls; + } + break; + case ZCYLINDER: + return 0; + break; + case REGION: // can visualize region directly + return 0; + break; + } + return 0; +} diff --git a/src/GRANULAR/fix_wall_gran.h b/src/GRANULAR/fix_wall_gran.h index e3d35fcc36a..413878a588a 100644 --- a/src/GRANULAR/fix_wall_gran.h +++ b/src/GRANULAR/fix_wall_gran.h @@ -50,6 +50,8 @@ class FixWallGran : public Fix { int maxsize_restart() override; void reset_dt() override; + int image(int *&, double **&) override; + // for granular model choices class Granular_NS::GranularModel *model; @@ -82,6 +84,12 @@ class FixWallGran : public Fix { double *mass_rigid; // rigid mass for owned+ghost atoms int nmax; // allocated size of mass_rigid + // dump image data + + int numwalls; + int *imgobjs; + double **imgparms; + // store particle interactions int nsvector; From 4a3f6486234b9f2bf458da2447e40f7497beacd2 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 16:58:08 -0500 Subject: [PATCH 39/54] cosmetic changes --- doc/src/fix_wall_gran.rst | 2 +- src/GRANULAR/fix_wall_gran.cpp | 12 ++++++------ src/fix_wall.cpp | 8 ++++---- src/fix_wall_reflect.cpp | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index c23619bc860..99ee1e83400 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -270,7 +270,7 @@ Dump image info This fix supports the *fix* keyword of :doc:`dump image `. The fix will pass geometry information about *xplane*\, *yplane*\, and -*zplane style walls to *dump image* so that the walls will be included +*zplane* style walls to *dump image* so that the walls will be included in the rendered image. Please note, that for :doc:`2d systems `, a wall rendered as a plane would be invisible and it is thus rendered as a cylinder. diff --git a/src/GRANULAR/fix_wall_gran.cpp b/src/GRANULAR/fix_wall_gran.cpp index 0fe4114589a..d4c99d95482 100644 --- a/src/GRANULAR/fix_wall_gran.cpp +++ b/src/GRANULAR/fix_wall_gran.cpp @@ -308,16 +308,16 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : if ((wallstyle == XPLANE) || (wallstyle == YPLANE) || (wallstyle == ZPLANE)) { if (domain->dimension == 2) { // one cylinder object per wall to draw in 2d - memory->create(imgobjs, numwalls, "fix_indent:imgobjs"); - memory->create(imgparms, numwalls, 8, "fix_indent:imgparms"); + memory->create(imgobjs, numwalls, "fix_wall:imgobjs"); + memory->create(imgparms, numwalls, 8, "fix_wall:imgparms"); for (int m = 0; m < numwalls; ++m) { imgobjs[m] = DumpImage::CYLINDER; imgparms[m][0] = 1; // use color of first atom type by default } } else { // two triangle objects per wall to draw in 3d - memory->create(imgobjs, 2 * numwalls, "fix_indent:imgobjs"); - memory->create(imgparms, 2 * numwalls, 10, "fix_indent:imgparms"); + memory->create(imgobjs, 2 * numwalls, "fix_wall:imgobjs"); + memory->create(imgparms, 2 * numwalls, 10, "fix_wall:imgparms"); for (int m = 0; m < numwalls; ++m) { imgobjs[2 * m] = DumpImage::TRIANGLE; imgobjs[2 * m + 1] = DumpImage::TRIANGLE; @@ -327,8 +327,8 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : } } else if (wallstyle == ZCYLINDER) { // one cylinder object per wall to draw - memory->create(imgobjs, numwalls, "fix_indent:imgobjs"); - memory->create(imgparms, numwalls, 8, "fix_indent:imgparms"); + memory->create(imgobjs, numwalls, "fix_wall:imgobjs"); + memory->create(imgparms, numwalls, 8, "fix_wall:imgparms"); for (int m = 0; m < numwalls; ++m) { imgobjs[m] = DumpImage::CYLINDER; imgparms[m][0] = 1; // use color of first atom type by default diff --git a/src/fix_wall.cpp b/src/fix_wall.cpp index 3b5b206a897..93ebc0c0fc4 100644 --- a/src/fix_wall.cpp +++ b/src/fix_wall.cpp @@ -339,16 +339,16 @@ FixWall::FixWall(LAMMPS *lmp, int narg, char **arg) : Fix(lmp, narg, arg), nwall // for rendering walls with dump image. if (domain->dimension == 2) { // one cylinder object per wall to draw in 2d - memory->create(imgobjs, nwall, "fix_indent:imgobjs"); - memory->create(imgparms, nwall, 8, "fix_indent:imgparms"); + memory->create(imgobjs, nwall, "fix_wall:imgobjs"); + memory->create(imgparms, nwall, 8, "fix_wall:imgparms"); for (int m = 0; m < nwall; ++m) { imgobjs[m] = DumpImage::CYLINDER; imgparms[m][0] = 1; // use color of first atom type by default } } else { // two triangle objects per wall to draw in 3d - memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); - memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); + memory->create(imgobjs, 2 * nwall, "fix_wall:imgobjs"); + memory->create(imgparms, 2 * nwall, 10, "fix_wall:imgparms"); for (int m = 0; m < nwall; ++m) { imgobjs[2 * m] = DumpImage::TRIANGLE; imgobjs[2 * m + 1] = DumpImage::TRIANGLE; diff --git a/src/fix_wall_reflect.cpp b/src/fix_wall_reflect.cpp index 4e133f656d4..43b9bd51f44 100644 --- a/src/fix_wall_reflect.cpp +++ b/src/fix_wall_reflect.cpp @@ -146,16 +146,16 @@ FixWallReflect::FixWallReflect(LAMMPS *lmp, int narg, char **arg) : // for rendering walls with dump image. if (domain->dimension == 2) { // one cylinder object per wall to draw in 2d - memory->create(imgobjs, nwall, "fix_indent:imgobjs"); - memory->create(imgparms, nwall, 8, "fix_indent:imgparms"); + memory->create(imgobjs, nwall, "fix_wall_reflect:imgobjs"); + memory->create(imgparms, nwall, 8, "fix_wall_reflect:imgparms"); for (int m = 0; m < nwall; ++m) { imgobjs[m] = DumpImage::CYLINDER; imgparms[m][0] = 1; // use color of first atom type by default } } else { // two triangle objects per wall to draw in 3d - memory->create(imgobjs, 2 * nwall, "fix_indent:imgobjs"); - memory->create(imgparms, 2 * nwall, 10, "fix_indent:imgparms"); + memory->create(imgobjs, 2 * nwall, "fix_wall_reflect:imgobjs"); + memory->create(imgparms, 2 * nwall, 10, "fix_wall_reflect:imgparms"); for (int m = 0; m < nwall; ++m) { imgobjs[2 * m] = DumpImage::TRIANGLE; imgobjs[2 * m + 1] = DumpImage::TRIANGLE; From 71ea35f06aade090e3ea4a5335db882139d3867d Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 17:30:10 -0500 Subject: [PATCH 40/54] bracket progress value to [0,1] rather than throwing an error --- src/fix_graphics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index b1f637e2224..3040a0a0139 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -645,6 +645,8 @@ void FixGraphics::end_of_step() } else if (gi.style == PROGBAR) { ++n; if (gi.progbar.pstr) gi.progbar.progress = input->variable->compute_equal(gi.progbar.pvar); + // bracket into [0.0;1.0] rather than throwing an error for just a viz item + gi.progbar.progress = std::max(std::min(gi.progbar.progress, 1.0), 0.0); switch (gi.progbar.dim) { case X: imgparms[n][1] = gi.progbar.pos[X] + (gi.progbar.progress - 0.5) * gi.progbar.length; From a604c03067071da347d62cc0041ae6036f49f6b1 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 17:30:24 -0500 Subject: [PATCH 41/54] apply clang-format --- src/fix_graphics.cpp | 84 ++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index 3040a0a0139..b54f5d0a1fc 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -197,7 +197,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : progbar.pos[Y] = utils::numeric(FLERR, arg[iarg + 5], false, lmp); progbar.pos[Z] = utils::numeric(FLERR, arg[iarg + 6], false, lmp); progbar.length = utils::numeric(FLERR, arg[iarg + 7], false, lmp); - if ((progbar.length <= 0.0) || (progbar.length > 2.0*domain->prd[progbar.dim])) + if ((progbar.length <= 0.0) || (progbar.length > 2.0 * domain->prd[progbar.dim])) error->all(FLERR, iarg + 7, "Illegal progress bar length {}", arg[iarg + 7]); progbar.diameter = 2.0 * utils::numeric(FLERR, arg[iarg + 8], false, lmp); if (strstr(arg[iarg + 9], "v_") == arg[iarg + 9]) { @@ -226,35 +226,35 @@ FixGraphics::~FixGraphics() { for (auto &gi : items) { switch (gi.style) { - case SPHERE: - delete[] gi.sphere.xstr; - delete[] gi.sphere.ystr; - delete[] gi.sphere.zstr; - delete[] gi.sphere.dstr; - break; - case CYLINDER: - delete[] gi.cylinder.x1str; - delete[] gi.cylinder.y1str; - delete[] gi.cylinder.z1str; - delete[] gi.cylinder.x2str; - delete[] gi.cylinder.y2str; - delete[] gi.cylinder.z2str; - delete[] gi.cylinder.dstr; - break; - case ARROW: - delete[] gi.arrow.x1str; - delete[] gi.arrow.y1str; - delete[] gi.arrow.z1str; - delete[] gi.arrow.x2str; - delete[] gi.arrow.y2str; - delete[] gi.arrow.z2str; - delete[] gi.arrow.dstr; - break; - case PROGBAR: - delete[] gi.progbar.pstr; - break; - default:; // do nothing - break; + case SPHERE: + delete[] gi.sphere.xstr; + delete[] gi.sphere.ystr; + delete[] gi.sphere.zstr; + delete[] gi.sphere.dstr; + break; + case CYLINDER: + delete[] gi.cylinder.x1str; + delete[] gi.cylinder.y1str; + delete[] gi.cylinder.z1str; + delete[] gi.cylinder.x2str; + delete[] gi.cylinder.y2str; + delete[] gi.cylinder.z2str; + delete[] gi.cylinder.dstr; + break; + case ARROW: + delete[] gi.arrow.x1str; + delete[] gi.arrow.y1str; + delete[] gi.arrow.z1str; + delete[] gi.arrow.x2str; + delete[] gi.arrow.y2str; + delete[] gi.arrow.z2str; + delete[] gi.arrow.dstr; + break; + case PROGBAR: + delete[] gi.progbar.pstr; + break; + default:; // do nothing + break; } } @@ -541,18 +541,18 @@ void FixGraphics::init() imgparms[n][6] = gi.progbar.pos[Z]; imgparms[n][7] = 1.1 * gi.progbar.diameter; switch (gi.progbar.dim) { - case X: - imgparms[n][1] = lo + delta * i - 0.05 * delta; - imgparms[n][4] = lo + delta * i + 0.05 * delta; - break; - case Y: - imgparms[n][2] = lo + delta * i - 0.05 * delta; - imgparms[n][5] = lo + delta * i + 0.05 * delta; - break; - case Z: - imgparms[n][3] = lo + delta * i - 0.05 * delta; - imgparms[n][6] = lo + delta * i + 0.05 * delta; - break; + case X: + imgparms[n][1] = lo + delta * i - 0.05 * delta; + imgparms[n][4] = lo + delta * i + 0.05 * delta; + break; + case Y: + imgparms[n][2] = lo + delta * i - 0.05 * delta; + imgparms[n][5] = lo + delta * i + 0.05 * delta; + break; + case Z: + imgparms[n][3] = lo + delta * i - 0.05 * delta; + imgparms[n][6] = lo + delta * i + 0.05 * delta; + break; } ++n; } From 9c193624978bb7f8bcab8df62090929c5f9f5021 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 17:51:57 -0500 Subject: [PATCH 42/54] add some more docs to fix graphics --- doc/src/fix_graphics.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index 2a3628b2e5f..d077c1e7f52 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -77,6 +77,21 @@ for *all* objects of this fix instance, which can be changed using a :doc:`dump modify fcolor ` command. For the *progbar* object *two* atom type values must be specified. +The *x*\, *y*\, and *z* parameters correspond to the position of the +center of the object (*sphere* and *progbar*). *x1*\, *y1*\, and *z1* as +well as *x2*\, *y2*\, and *z2* are instead representing the top and +bottom position of a graphics object (*cylinder* and *arrow*). The *R* +parameter determines the radius. + +The *progbar* object has four additional parameters: *dim* sets the +direction of the progress bar, "x", "y", or "z"; *length* sets the +length of the entire object; *ratio* sets the ratio of progress and +is expected to be between 0.0 and 1.0 (larger or smaller values will +be reset to 1.0 or 0.0, respectively); and *tics* determines the number +of tics shown on the progress bar, this must be a number between 0 and 20. +Unlike for the other graphics objects, all settings except for *ratio* +are fixed and cannot be a variable reference. + Available graphics objects are (see above for exact command line syntax): - *sphere* - a sphere defined by its center location and its radius From 6561ebbf6ad48ff01d3c921c89b243bcd873b207 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 21:03:46 -0500 Subject: [PATCH 43/54] add support for screen-door transparency to Image class --- src/image.cpp | 135 +++++++++++++++++++++++++++++++++----------------- src/image.h | 14 +++--- 2 files changed, 98 insertions(+), 51 deletions(-) diff --git a/src/image.cpp b/src/image.cpp index 7fe44b856b0..85e87791211 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -44,20 +44,70 @@ using MathConst::DEG2RAD; using MathConst::MY_PI; using MathConst::MY_PI4; -static constexpr int NCOLORS = 140; -static constexpr int NELEMENTS = 109; -static constexpr double EPSILON = 1.0e-6; - -enum{NUMERIC,MINVALUE,MAXVALUE}; -enum{CONTINUOUS,DISCRETE,SEQUENTIAL}; -enum{ABSOLUTE,FRACTIONAL}; -enum{NO,YES}; +// clang-format on +namespace { +constexpr int NCOLORS = 140; +constexpr int NELEMENTS = 109; +constexpr double EPSILON = 1.0e-6; + +enum { NUMERIC, MINVALUE, MAXVALUE }; +enum { CONTINUOUS, DISCRETE, SEQUENTIAL }; +enum { ABSOLUTE, FRACTIONAL }; +enum { NO, YES }; + +constexpr double transthresh[16][16] = { + 192.0 / 256.0, 11.0 / 256.0, 183.0 / 256.0, 125.0 / 256.0, 26.0 / 256.0, 145.0 / 256.0, + 44.0 / 256.0, 244.0 / 256.0, 8.0 / 256.0, 168.0 / 256.0, 139.0 / 256.0, 38.0 / 256.0, + 174.0 / 256.0, 27.0 / 256.0, 141.0 / 256.0, 43.0 / 256.0, 115.0 / 256.0, 211.0 / 256.0, + 150.0 / 256.0, 68.0 / 256.0, 194.0 / 256.0, 88.0 / 256.0, 177.0 / 256.0, 131.0 / 256.0, + 61.0 / 256.0, 222.0 / 256.0, 87.0 / 256.0, 238.0 / 256.0, 74.0 / 256.0, 224.0 / 256.0, + 100.0 / 256.0, 235.0 / 256.0, 59.0 / 256.0, 33.0 / 256.0, 96.0 / 256.0, 239.0 / 256.0, + 51.0 / 256.0, 232.0 / 256.0, 16.0 / 256.0, 210.0 / 256.0, 117.0 / 256.0, 32.0 / 256.0, + 187.0 / 256.0, 1.0 / 256.0, 157.0 / 256.0, 121.0 / 256.0, 14.0 / 256.0, 165.0 / 256.0, + 248.0 / 256.0, 128.0 / 256.0, 217.0 / 256.0, 2.0 / 256.0, 163.0 / 256.0, 105.0 / 256.0, + 154.0 / 256.0, 81.0 / 256.0, 247.0 / 256.0, 149.0 / 256.0, 97.0 / 256.0, 205.0 / 256.0, + 52.0 / 256.0, 182.0 / 256.0, 209.0 / 256.0, 84.0 / 256.0, 20.0 / 256.0, 172.0 / 256.0, + 80.0 / 256.0, 140.0 / 256.0, 202.0 / 256.0, 41.0 / 256.0, 185.0 / 256.0, 55.0 / 256.0, + 24.0 / 256.0, 197.0 / 256.0, 65.0 / 256.0, 129.0 / 256.0, 252.0 / 256.0, 35.0 / 256.0, + 70.0 / 256.0, 147.0 / 256.0, 201.0 / 256.0, 63.0 / 256.0, 189.0 / 256.0, 28.0 / 256.0, + 90.0 / 256.0, 254.0 / 256.0, 116.0 / 256.0, 219.0 / 256.0, 137.0 / 256.0, 107.0 / 256.0, + 231.0 / 256.0, 17.0 / 256.0, 144.0 / 256.0, 119.0 / 256.0, 228.0 / 256.0, 109.0 / 256.0, + 46.0 / 256.0, 245.0 / 256.0, 103.0 / 256.0, 229.0 / 256.0, 134.0 / 256.0, 13.0 / 256.0, + 67.0 / 256.0, 162.0 / 256.0, 6.0 / 256.0, 170.0 / 256.0, 47.0 / 256.0, 178.0 / 256.0, + 76.0 / 256.0, 193.0 / 256.0, 4.0 / 256.0, 167.0 / 256.0, 133.0 / 256.0, 9.0 / 256.0, + 159.0 / 256.0, 54.0 / 256.0, 175.0 / 256.0, 124.0 / 256.0, 225.0 / 256.0, 93.0 / 256.0, + 242.0 / 256.0, 79.0 / 256.0, 214.0 / 256.0, 99.0 / 256.0, 241.0 / 256.0, 56.0 / 256.0, + 221.0 / 256.0, 92.0 / 256.0, 186.0 / 256.0, 218.0 / 256.0, 78.0 / 256.0, 208.0 / 256.0, + 37.0 / 256.0, 196.0 / 256.0, 25.0 / 256.0, 188.0 / 256.0, 42.0 / 256.0, 142.0 / 256.0, + 29.0 / 256.0, 158.0 / 256.0, 21.0 / 256.0, 130.0 / 256.0, 156.0 / 256.0, 40.0 / 256.0, + 102.0 / 256.0, 31.0 / 256.0, 148.0 / 256.0, 111.0 / 256.0, 234.0 / 256.0, 85.0 / 256.0, + 151.0 / 256.0, 120.0 / 256.0, 207.0 / 256.0, 113.0 / 256.0, 255.0 / 256.0, 86.0 / 256.0, + 184.0 / 256.0, 212.0 / 256.0, 69.0 / 256.0, 236.0 / 256.0, 176.0 / 256.0, 73.0 / 256.0, + 253.0 / 256.0, 0.0 / 256.0, 138.0 / 256.0, 58.0 / 256.0, 249.0 / 256.0, 71.0 / 256.0, + 10.0 / 256.0, 173.0 / 256.0, 62.0 / 256.0, 200.0 / 256.0, 50.0 / 256.0, 114.0 / 256.0, + 12.0 / 256.0, 123.0 / 256.0, 23.0 / 256.0, 204.0 / 256.0, 118.0 / 256.0, 191.0 / 256.0, + 91.0 / 256.0, 181.0 / 256.0, 19.0 / 256.0, 164.0 / 256.0, 216.0 / 256.0, 101.0 / 256.0, + 233.0 / 256.0, 3.0 / 256.0, 135.0 / 256.0, 169.0 / 256.0, 246.0 / 256.0, 152.0 / 256.0, + 223.0 / 256.0, 60.0 / 256.0, 143.0 / 256.0, 48.0 / 256.0, 240.0 / 256.0, 34.0 / 256.0, + 220.0 / 256.0, 82.0 / 256.0, 132.0 / 256.0, 36.0 / 256.0, 146.0 / 256.0, 106.0 / 256.0, + 227.0 / 256.0, 30.0 / 256.0, 95.0 / 256.0, 49.0 / 256.0, 83.0 / 256.0, 166.0 / 256.0, + 18.0 / 256.0, 199.0 / 256.0, 98.0 / 256.0, 155.0 / 256.0, 122.0 / 256.0, 53.0 / 256.0, + 237.0 / 256.0, 179.0 / 256.0, 57.0 / 256.0, 190.0 / 256.0, 77.0 / 256.0, 195.0 / 256.0, + 127.0 / 256.0, 180.0 / 256.0, 230.0 / 256.0, 108.0 / 256.0, 215.0 / 256.0, 64.0 / 256.0, + 171.0 / 256.0, 5.0 / 256.0, 206.0 / 256.0, 161.0 / 256.0, 22.0 / 256.0, 94.0 / 256.0, + 251.0 / 256.0, 15.0 / 256.0, 153.0 / 256.0, 45.0 / 256.0, 243.0 / 256.0, 7.0 / 256.0, + 72.0 / 256.0, 136.0 / 256.0, 39.0 / 256.0, 250.0 / 256.0, 104.0 / 256.0, 226.0 / 256.0, + 75.0 / 256.0, 112.0 / 256.0, 198.0 / 256.0, 126.0 / 256.0, 66.0 / 256.0, 213.0 / 256.0, + 110.0 / 256.0, 203.0 / 256.0, 89.0 / 256.0, 160.0 / 256.0}; +} // namespace +// clang-format off /* ---------------------------------------------------------------------- */ Image::Image(LAMMPS *lmp, int nmap_caller) : - Pointers(lmp), depthBuffer(nullptr), surfaceBuffer(nullptr), depthcopy(nullptr), - surfacecopy(nullptr), imageBuffer(nullptr), rgbcopy(nullptr), writeBuffer(nullptr) + Pointers(lmp), maps(nullptr), depthBuffer(nullptr), surfaceBuffer(nullptr), depthcopy(nullptr), + surfacecopy(nullptr), imageBuffer(nullptr), rgbcopy(nullptr), writeBuffer(nullptr), + recvcounts(nullptr), displs(nullptr), username(nullptr), userrgb(nullptr), random(nullptr) { MPI_Comm_rank(world,&me); MPI_Comm_size(world,&nprocs); @@ -80,9 +130,6 @@ Image::Image(LAMMPS *lmp, int nmap_caller) : // colors ncolors = 0; - username = nullptr; - userrgb = nullptr; - boxcolor = color2rgb("yellow"); background[0] = background[1] = background[2] = 0; @@ -117,13 +164,6 @@ Image::Image(LAMMPS *lmp, int nmap_caller) : backLightColor[0] = 0.9; backLightColor[1] = 0.9; backLightColor[2] = 0.9; - - random = nullptr; - - // MPI_Gatherv vectors - - recvcounts = nullptr; - displs = nullptr; } /* ---------------------------------------------------------------------- */ @@ -131,7 +171,7 @@ Image::Image(LAMMPS *lmp, int nmap_caller) : Image::~Image() { for (int i = 0; i < nmap; i++) delete maps[i]; - delete [] maps; + delete[] maps; for (int i = 0; i < ncolors; i++) delete [] username[i]; memory->sfree(username); @@ -144,7 +184,7 @@ Image::~Image() memory->destroy(surfacecopy); memory->destroy(rgbcopy); - if (random) delete random; + delete random; memory->destroy(recvcounts); memory->destroy(displs); @@ -416,20 +456,20 @@ void Image::merge() draw simulation bounding box as 12 cylinders ------------------------------------------------------------------------- */ -void Image::draw_box(double (*corners)[3], double diameter) +void Image::draw_box(double (*corners)[3], double diameter, double opacity) { - draw_cylinder(corners[0],corners[1],boxcolor,diameter,3); - draw_cylinder(corners[2],corners[3],boxcolor,diameter,3); - draw_cylinder(corners[0],corners[2],boxcolor,diameter,3); - draw_cylinder(corners[1],corners[3],boxcolor,diameter,3); - draw_cylinder(corners[0],corners[4],boxcolor,diameter,3); - draw_cylinder(corners[1],corners[5],boxcolor,diameter,3); - draw_cylinder(corners[2],corners[6],boxcolor,diameter,3); - draw_cylinder(corners[3],corners[7],boxcolor,diameter,3); - draw_cylinder(corners[4],corners[5],boxcolor,diameter,3); - draw_cylinder(corners[6],corners[7],boxcolor,diameter,3); - draw_cylinder(corners[4],corners[6],boxcolor,diameter,3); - draw_cylinder(corners[5],corners[7],boxcolor,diameter,3); + draw_cylinder(corners[0],corners[1],boxcolor,diameter,3,opacity); + draw_cylinder(corners[2],corners[3],boxcolor,diameter,3,opacity); + draw_cylinder(corners[0],corners[2],boxcolor,diameter,3,opacity); + draw_cylinder(corners[1],corners[3],boxcolor,diameter,3,opacity); + draw_cylinder(corners[0],corners[4],boxcolor,diameter,3,opacity); + draw_cylinder(corners[1],corners[5],boxcolor,diameter,3,opacity); + draw_cylinder(corners[2],corners[6],boxcolor,diameter,3,opacity); + draw_cylinder(corners[3],corners[7],boxcolor,diameter,3,opacity); + draw_cylinder(corners[4],corners[5],boxcolor,diameter,3,opacity); + draw_cylinder(corners[6],corners[7],boxcolor,diameter,3,opacity); + draw_cylinder(corners[4],corners[6],boxcolor,diameter,3,opacity); + draw_cylinder(corners[5],corners[7],boxcolor,diameter,3,opacity); } /* ---------------------------------------------------------------------- @@ -437,11 +477,11 @@ void Image::draw_box(double (*corners)[3], double diameter) axes = 4 end points ------------------------------------------------------------------------- */ -void Image::draw_axes(double (*axes)[3], double diameter) +void Image::draw_axes(double (*axes)[3], double diameter, double opacity) { - draw_cylinder(axes[0],axes[1],color2rgb("red"),diameter,3); - draw_cylinder(axes[0],axes[2],color2rgb("green"),diameter,3); - draw_cylinder(axes[0],axes[3],color2rgb("blue"),diameter,3); + draw_cylinder(axes[0],axes[1],color2rgb("red"),diameter,3,opacity); + draw_cylinder(axes[0],axes[2],color2rgb("green"),diameter,3,opacity); + draw_cylinder(axes[0],axes[3],color2rgb("blue"),diameter,3,opacity); } /* ---------------------------------------------------------------------- @@ -449,7 +489,7 @@ void Image::draw_axes(double (*axes)[3], double diameter) render pixel by pixel onto image plane with depth buffering ------------------------------------------------------------------------- */ -void Image::draw_sphere(const double *x, const double *surfaceColor, double diameter) +void Image::draw_sphere(const double *x, const double *surfaceColor, double diameter, double opacity) { double xlocal[3]; @@ -483,8 +523,9 @@ void Image::draw_sphere(const double *x, const double *surfaceColor, double diam for (int iy = yc - pixelRadius; iy <= yc + pixelRadius; iy++) { for (int ix = xc - pixelRadius; ix <= xc + pixelRadius; ix++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; - double surface[3]; + if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; + double surface[3]; surface[1] = ((iy - yc) - height_error) * pixelWidth; surface[0] = ((ix - xc) - width_error) * pixelWidth; double projRad = surface[0]*surface[0] + surface[1]*surface[1]; @@ -509,7 +550,7 @@ void Image::draw_sphere(const double *x, const double *surfaceColor, double diam render pixel by pixel onto image plane with depth buffering ------------------------------------------------------------------------- */ -void Image::draw_cube(const double *x, const double *surfaceColor, double diameter) +void Image::draw_cube(const double *x, const double *surfaceColor, double diameter, double opacity) { double xlocal[3],surface[3]; double normal[3] = {0.0, 0.0, 1.0}; @@ -548,6 +589,7 @@ void Image::draw_cube(const double *x, const double *surfaceColor, double diamet for (int iy = yc - pixelHalfWidth; iy <= yc + pixelHalfWidth; iy ++) { for (int ix = xc - pixelHalfWidth; ix <= xc + pixelHalfWidth; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; + if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; double sy = ((iy - yc) - height_error) * pixelWidth; double sx = ((ix - xc) - width_error) * pixelWidth; @@ -619,7 +661,7 @@ void Image::draw_cube(const double *x, const double *surfaceColor, double diamet ------------------------------------------------------------------------- */ void Image::draw_cylinder(const double *x, const double *y, - const double *surfaceColor, double diameter, int sflag) + const double *surfaceColor, double diameter, int sflag, double opacity) { double mid[3],xaxis[3],yaxis[3],zaxis[3]; double camLDir[3], camLRight[3], camLUp[3]; @@ -700,6 +742,7 @@ void Image::draw_cylinder(const double *x, const double *y, for (int iy = yc - pixelHalfHeight; iy <= yc + pixelHalfHeight; iy ++) { for (int ix = xc - pixelHalfWidth; ix <= xc + pixelHalfWidth; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; + if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; double surface[3], normal[3]; double sy = ((iy - yc) - height_error) * pixelWidth; @@ -744,10 +787,11 @@ void Image::draw_cylinder(const double *x, const double *y, } /* ---------------------------------------------------------------------- - draw triangle with 3 corner points x,y,z and surfaceColor + draw triangle with 3 corner points x,y,z, surfaceColor ------------------------------------------------------------------------- */ -void Image::draw_triangle(const double *x, const double *y, const double *z, const double *surfaceColor) +void Image::draw_triangle(const double *x, const double *y, const double *z, const double *surfaceColor, + const double opacity) { double d1[3], d1len, d2[3], d2len, normal[3], invndotd; double xlocal[3], ylocal[3], zlocal[3]; @@ -825,6 +869,7 @@ void Image::draw_triangle(const double *x, const double *y, const double *z, con for (int iy = yc - pixelDown; iy <= yc + pixelUp; iy ++) { for (int ix = xc - pixelLeft; ix <= xc + pixelRight; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; + if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; double sy = ((iy - yc) - height_error) * pixelWidth; double sx = ((ix - xc) - width_error) * pixelWidth; diff --git a/src/image.h b/src/image.h index 71d671c0414..eecfe29564b 100644 --- a/src/image.h +++ b/src/image.h @@ -50,12 +50,14 @@ class Image : protected Pointers { void write_PPM(FILE *); void view_params(double, double, double, double, double, double); - void draw_sphere(const double *, const double *, double); - void draw_cube(const double *, const double *, double); - void draw_cylinder(const double *, const double *, const double *, double, int); - void draw_triangle(const double *, const double *, const double *, const double *); - void draw_box(double (*)[3], double); - void draw_axes(double (*)[3], double); + void draw_sphere(const double *, const double *, double, double opacity = 1.0); + void draw_cube(const double *, const double *, double, double opacity = 1.0); + void draw_cylinder(const double *, const double *, const double *, double, int, + double opacity = 1.0); + void draw_triangle(const double *, const double *, const double *, const double *, + double opacity = 1.0); + void draw_box(double (*)[3], double, double opacity = 1.0); + void draw_axes(double (*)[3], double, double opacity = 1.0); int map_dynamic(int); int map_reset(int, int, char **); From 5ac14d13bec7cb9bb78b02bd0026b5c7ac859a67 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 21:05:50 -0500 Subject: [PATCH 44/54] add support for opacity to regions and graphics from fixes using triangulated surfaces --- src/dump_image.cpp | 176 +++++++++++++++++++++++---------------------- src/dump_image.h | 12 ++-- 2 files changed, 99 insertions(+), 89 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 75cced78d2a..1f83c413735 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -207,7 +207,7 @@ void ellipsoid2wireframe(LAMMPS_NS::Image *img, int level, const double *color, } void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, const double *center, - const double *radius, LAMMPS_NS::Region *reg) + const double *radius, LAMMPS_NS::Region *reg, double opacity) { vec3 offset = {center[0], center[1], center[2]}; @@ -230,7 +230,7 @@ void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, con reg->forward_transform(tri[0][0], tri[0][1], tri[0][2]); reg->forward_transform(tri[1][0], tri[1][1], tri[1][2]); reg->forward_transform(tri[2][0], tri[2][1], tri[2][2]); - img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color); + img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color, opacity); } } @@ -244,7 +244,7 @@ void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, con reg->forward_transform(tri[0][0], tri[0][1], tri[0][2]); reg->forward_transform(tri[1][0], tri[1][1], tri[1][2]); reg->forward_transform(tri[2][0], tri[2][1], tri[2][2]); - img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color); + img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color, opacity); } } } @@ -258,7 +258,7 @@ void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, con reg->forward_transform(tri[0][0], tri[0][1], tri[0][2]); reg->forward_transform(tri[1][0], tri[1][1], tri[1][2]); reg->forward_transform(tri[2][0], tri[2][1], tri[2][2]); - img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color); + img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color, opacity); } } } @@ -271,7 +271,7 @@ void ellipsoid2filled(LAMMPS_NS::Image *img, int level, const double *color, con reg->forward_transform(tri[0][0], tri[0][1], tri[0][2]); reg->forward_transform(tri[1][0], tri[1][1], tri[1][2]); reg->forward_transform(tri[2][0], tri[2][1], tri[2][2]); - img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color); + img->draw_triangle(tri[0].data(), tri[1].data(), tri[2].data(), color, opacity); } } } @@ -285,7 +285,7 @@ static constexpr double BIG = 1.0e20; enum { NUMERIC, ATOM, TYPE, ELEMENT, ATTRIBUTE, CONSTANT }; enum { STATIC, DYNAMIC }; enum { NO = 0, YES = 1, AUTO = 2 }; -enum { FILLED, FRAME, POINTS }; +enum { FILLED, FRAME, POINTS, TRANSPARENT }; /* ---------------------------------------------------------------------- */ @@ -504,17 +504,19 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : if (strcmp(arg[iarg+3],"filled") == 0) drawstyle = FILLED; else if (strcmp(arg[iarg+3],"frame") == 0) drawstyle = FRAME; else if (strcmp(arg[iarg+3],"points") == 0) drawstyle = POINTS; + else if (strcmp(arg[iarg+3],"transparent") == 0) drawstyle = TRANSPARENT; else error->all(FLERR, iarg+3, "Unknown region draw style {}", arg[iarg+3]); double framediam = 0.5; + double opacity = 1.0; int npoints = 0; if (drawstyle == FRAME) { - if (iarg+5 > narg) utils::missing_cmd_args(FLERR,"dump image region", error); + if (iarg+5 > narg) utils::missing_cmd_args(FLERR,"dump image region frame", error); framediam = utils::numeric(FLERR, arg[iarg+4], false, lmp); if (framediam <= 0.0) error->all(FLERR, iarg+4, "Dump image region frame diameter must be > 0.0"); ++iarg; } else if (drawstyle == POINTS) { - if (iarg+6 > narg) utils::missing_cmd_args(FLERR,"dump image region", error); + if (iarg+6 > narg) utils::missing_cmd_args(FLERR,"dump image region points", error); npoints = utils::inumeric(FLERR, arg[iarg+4], false, lmp); if (npoints < 1) error->all(FLERR, iarg+4, "Dump image region number of points must be > 0"); @@ -522,9 +524,15 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : if (framediam <= 0.0) error->all(FLERR, iarg+5, "Dump image region point diameter must be > 0.0"); iarg += 2; + } else if (drawstyle == TRANSPARENT) { + if (iarg+5 > narg) utils::missing_cmd_args(FLERR,"dump image region transparent", error); + opacity = utils::numeric(FLERR, arg[iarg+5], false, lmp); + if ((opacity < 0.0) || (opacity > 1.0)) + error->all(FLERR, iarg+5, "Dump image region opacity must be in the range 0.0 to 1.0"); + iarg += 2; } iarg += 4; - regions.emplace_back(regptr->id, regptr, regcolor, drawstyle, framediam, npoints); + regions.emplace_back(regptr->id, regptr, regcolor, drawstyle, framediam, opacity, npoints); } else if (strcmp(arg[iarg],"size") == 0) { if (iarg+3 > narg) utils::missing_cmd_args(FLERR,"dump image size", error); @@ -1697,15 +1705,6 @@ void DumpImage::create_image() // render objects provided by fixes for (const auto &ifix : fixes) { - int tridraw = 0; - int edgedraw = 0; - if (domain->dimension == 3) { - tridraw = 1; - edgedraw = 1; - if ((int) ifix.flag1 == 2) tridraw = 0; - if ((int) ifix.flag1 == 1) edgedraw = 0; - } - int *fixvec = nullptr; double **fixarray = nullptr; const int ntypes = atom->ntypes; @@ -1732,21 +1731,24 @@ void DumpImage::create_image() // @sjplimp for consistency this should be: // image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,ifix.flag2,ifix.flag1); image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color,ifix.flag1,3); - } else if (fixvec[i] == TRI) { - p1 = &fixarray[i][1]; - p2 = &fixarray[i][4]; - p3 = &fixarray[i][7]; - if (tridraw) image->draw_triangle(p1,p2,p3,color); - if (edgedraw) { - image->draw_cylinder(p1,p2,color,ifix.flag2,3); - image->draw_cylinder(p2,p3,color,ifix.flag2,3); - image->draw_cylinder(p3,p1,color,ifix.flag2,3); + } else if (fixvec[i] == TRI) { // don't render surface meshes in 2d + if (domain->dimension == 3) { + p1 = &fixarray[i][1]; + p2 = &fixarray[i][4]; + p3 = &fixarray[i][7]; + if (static_cast(ifix.flag1) % 2) { + image->draw_triangle(p1,p2,p3,color,ifix.flag2); + } else { + image->draw_cylinder(p1,p2,color,ifix.flag2,3); + image->draw_cylinder(p2,p3,color,ifix.flag2,3); + image->draw_cylinder(p3,p1,color,ifix.flag2,3); + } } } else if (fixvec[i] == CYLINDER) { image->draw_cylinder(&fixarray[i][1],&fixarray[i][4],color, fixarray[i][7]+ifix.flag2,(int)ifix.flag1); } else if (fixvec[i] == TRIANGLE) { - image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color); + image->draw_triangle(&fixarray[i][1],&fixarray[i][4],&fixarray[i][7],color,ifix.flag2); } else if (fixvec[i] == BOND) { int type1 = static_cast(fixarray[i][0] - 1.0) % ntypes + 1; int type2 = static_cast(fixarray[i][1] - 1.0) % ntypes + 1; @@ -1882,30 +1884,31 @@ void DumpImage::create_image() image->draw_cylinder(block[5],block[6],reg.color,reg.diameter,3); image->draw_cylinder(block[4],block[7],reg.color,reg.diameter,3); image->draw_cylinder(block[6],block[7],reg.color,reg.diameter,3); - } else if (reg.style == FILLED) { + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; if (!myreg->open_faces[0]) { - image->draw_triangle(block[0], block[1], block[2], reg.color); - image->draw_triangle(block[2], block[3], block[0], reg.color); + image->draw_triangle(block[0], block[1], block[2], reg.color, opacity); + image->draw_triangle(block[2], block[3], block[0], reg.color, opacity); } if (!myreg->open_faces[1]) { - image->draw_triangle(block[4], block[5], block[6], reg.color); - image->draw_triangle(block[6], block[7], block[4], reg.color); + image->draw_triangle(block[4], block[5], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[7], block[4], reg.color, opacity); } if (!myreg->open_faces[2]) { - image->draw_triangle(block[0], block[4], block[7], reg.color); - image->draw_triangle(block[7], block[3], block[0], reg.color); + image->draw_triangle(block[0], block[4], block[7], reg.color, opacity); + image->draw_triangle(block[7], block[3], block[0], reg.color, opacity); } if (!myreg->open_faces[3]) { - image->draw_triangle(block[1], block[2], block[6], reg.color); - image->draw_triangle(block[6], block[5], block[1], reg.color); + image->draw_triangle(block[1], block[2], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[5], block[1], reg.color, opacity); } if (!myreg->open_faces[4]) { - image->draw_triangle(block[0], block[1], block[5], reg.color); - image->draw_triangle(block[5], block[4], block[0], reg.color); + image->draw_triangle(block[0], block[1], block[5], reg.color, opacity); + image->draw_triangle(block[5], block[4], block[0], reg.color, opacity); } if (!myreg->open_faces[5]) { - image->draw_triangle(block[3], block[2], block[6], reg.color); - image->draw_triangle(block[6], block[7], block[3], reg.color); + image->draw_triangle(block[3], block[2], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[7], block[3], reg.color, opacity); } } @@ -2004,7 +2007,8 @@ void DumpImage::create_image() image->draw_cylinder(p2, p4, reg.color, reg.diameter, 3); } } - } else if (reg.style == FILLED) { + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; for (int i = 0; i < RESOLUTION; ++i) { if (myreg->axis == 'x') { p1[0] = p2[0] = myreg->lo; @@ -2021,11 +2025,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p2, p4, p3, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p2, p4, p3, reg.color, opacity); } } else if (myreg->axis == 'y') { @@ -2043,11 +2047,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p2, p4, p3, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p2, p4, p3, reg.color, opacity); } } else { // if (myreg->axis == 'z') p1[2] = p2[2] = myreg->lo; @@ -2064,11 +2068,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p2, p4, p3, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p2, p4, p3, reg.color, opacity); } } } @@ -2157,7 +2161,8 @@ void DumpImage::create_image() image->draw_cylinder(p2, p4, reg.color, reg.diameter, 3); } } - } else if (reg.style == FILLED) { + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; for (int i = 0; i < RESOLUTION; ++i) { if (myreg->axis == 'x') { p1[0] = p2[0] = myreg->lo; @@ -2170,11 +2175,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p3, p4, p2, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p3, p4, p2, reg.color, opacity); } } else if (myreg->axis == 'y') { p1[1] = p2[1] = myreg->lo; @@ -2187,11 +2192,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p3, p4, p2, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p3, p4, p2, reg.color, opacity); } } else { // if (myreg->axis == 'z') p1[2] = p2[2] = myreg->lo; @@ -2204,11 +2209,11 @@ void DumpImage::create_image() myreg->forward_transform(p2[0], p2[1], p2[2]); myreg->forward_transform(p3[0], p3[1], p3[2]); myreg->forward_transform(p4[0], p4[1], p4[2]); - if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color); - if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color); + if (!myreg->open_faces[0]) image->draw_triangle(p1, p2, lo, reg.color, opacity); + if (!myreg->open_faces[1]) image->draw_triangle(p3, p4, hi, reg.color, opacity); if (!myreg->open_faces[2]) { - image->draw_triangle(p1, p2, p3, reg.color); - image->draw_triangle(p2, p4, p3, reg.color); + image->draw_triangle(p1, p2, p3, reg.color, opacity); + image->draw_triangle(p2, p4, p3, reg.color, opacity); } } } @@ -2226,8 +2231,9 @@ void DumpImage::create_image() double radius[3] = {myreg->a, myreg->b, myreg->c}; if (reg.style == FRAME) { ellipsoid2wireframe(image, 4, reg.color, reg.diameter, center, radius, reg.ptr); - } else if (reg.style == FILLED) { - ellipsoid2filled(image, 4, reg.color, center, radius, reg.ptr); + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; + ellipsoid2filled(image, 4, reg.color, center, radius, reg.ptr, opacity); } } else if (regstyle == "prism") { @@ -2260,30 +2266,31 @@ void DumpImage::create_image() image->draw_cylinder(block[5],block[6],reg.color,reg.diameter,3); image->draw_cylinder(block[4],block[7],reg.color,reg.diameter,3); image->draw_cylinder(block[6],block[7],reg.color,reg.diameter,3); - } else if (reg.style == FILLED) { + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; if (!myreg->open_faces[0]) { - image->draw_triangle(block[0], block[1], block[2], reg.color); - image->draw_triangle(block[2], block[3], block[0], reg.color); + image->draw_triangle(block[0], block[1], block[2], reg.color, opacity); + image->draw_triangle(block[2], block[3], block[0], reg.color, opacity); } if (!myreg->open_faces[1]) { - image->draw_triangle(block[4], block[5], block[6], reg.color); - image->draw_triangle(block[6], block[7], block[4], reg.color); + image->draw_triangle(block[4], block[5], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[7], block[4], reg.color, opacity); } if (!myreg->open_faces[2]) { - image->draw_triangle(block[0], block[4], block[7], reg.color); - image->draw_triangle(block[7], block[3], block[0], reg.color); + image->draw_triangle(block[0], block[4], block[7], reg.color, opacity); + image->draw_triangle(block[7], block[3], block[0], reg.color, opacity); } if (!myreg->open_faces[3]) { - image->draw_triangle(block[1], block[2], block[6], reg.color); - image->draw_triangle(block[6], block[5], block[1], reg.color); + image->draw_triangle(block[1], block[2], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[5], block[1], reg.color, opacity); } if (!myreg->open_faces[4]) { - image->draw_triangle(block[0], block[1], block[5], reg.color); - image->draw_triangle(block[5], block[4], block[0], reg.color); + image->draw_triangle(block[0], block[1], block[5], reg.color, opacity); + image->draw_triangle(block[5], block[4], block[0], reg.color, opacity); } if (!myreg->open_faces[5]) { - image->draw_triangle(block[3], block[2], block[6], reg.color); - image->draw_triangle(block[6], block[7], block[3], reg.color); + image->draw_triangle(block[3], block[2], block[6], reg.color, opacity); + image->draw_triangle(block[6], block[7], block[3], reg.color, opacity); } } } else if (regstyle == "sphere") { @@ -2298,9 +2305,10 @@ void DumpImage::create_image() if (reg.style == FRAME) { double radius[3] = {myreg->radius,myreg->radius,myreg->radius}; ellipsoid2wireframe(image, 4, reg.color, reg.diameter, center, radius, reg.ptr); - } else if (reg.style == FILLED) { + } else if ((reg.style == FILLED) || (reg.style == TRANSPARENT)) { + double opacity = (reg.style == TRANSPARENT) ? reg.opacity : 1.0; myreg->forward_transform(center[0], center[1], center[2]); - image->draw_sphere(center, reg.color, 2.0 * myreg->radius); + image->draw_sphere(center, reg.color, 2.0 * myreg->radius, opacity); } } else { if (comm->me == 0) diff --git a/src/dump_image.h b/src/dump_image.h index c2e6a65abc2..9cc9c9975c8 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -43,9 +43,9 @@ class DumpImage : public DumpCustom { SPHERE, // a single sphere with radius provided LINE, // a cylinder with diameter given through fflag2 TRI, // a surface mesh as triangles or cylinder mesh based on fflag1, fflag2 sets diameter - CYLINDER, // a cylinder with diameter given by fix, fflag1 choose caps, fflag2 adjusts diameter - TRIANGLE, // a regular triangle, no settings apply - BOND // two connected cylinders with bond diameter, colored by atom types, fflag1 sets cap + CYLINDER, // a cylinder with diameter given by fix, fflag1 choose caps, fflag2 adjusts diameter + TRIANGLE, // a regular triangle, no settings apply + BOND // two connected cylinders with bond diameter, colored by atom types, fflag1 sets cap }; // used by some Body and Fix child classes DumpImage(LAMMPS *, int, char **); @@ -137,8 +137,9 @@ class DumpImage : public DumpCustom { struct RegionInfo { RegionInfo() = delete; RegionInfo(const std::string &_id, Region *_ptr, double *_color, int _style, - double _diameter = 0.5, int _npoints = 0) : - ptr(_ptr), id(_id), style(_style), color(_color), diameter(_diameter), npoints(_npoints) + double _diameter = 0.5, double _opacity = 1.0, int _npoints = 0) : + ptr(_ptr), id(_id), style(_style), color(_color), diameter(_diameter), opacity(_opacity), + npoints(_npoints) { } @@ -147,6 +148,7 @@ class DumpImage : public DumpCustom { int style; double *color; double diameter; + double opacity; int npoints; }; From 0151d295b77c39617567de378ffb41ffa4a9c388 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 17 Dec 2025 23:37:57 -0500 Subject: [PATCH 45/54] update docs for transparency support of regions and fixes --- doc/src/dump_image.rst | 46 +++++++++++++++++++++----------- doc/src/fix_indent.rst | 5 +++- doc/src/fix_smd_wall_surface.rst | 8 +++--- doc/src/fix_wall.rst | 9 ++++--- doc/src/fix_wall_gran.rst | 9 ++++--- doc/src/fix_wall_reflect.rst | 9 ++++--- 6 files changed, 58 insertions(+), 28 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 39fa297c97d..2c43ccf7d15 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -81,12 +81,15 @@ Syntax axes = *yes* or *no* = do or do not draw xyz axes lines next to simulation box length = length of axes lines as fraction of respective box lengths diam = diameter of axes lines as fraction of shortest box length - *region* values = region-ID color drawstyle [npoints (optional) diameter (optional)] + *region* values = region-ID color drawstyle [opacity (optional) npoints (optional) diameter (optional)] region-ID = ID of the region to render color = color name for region graphics - drawstyle = *filled* or *frame* or *points* + drawstyle = *filled* or *transparent* or *frame* or *points* *filled* = render region as a filled object, with optional open faces + *transparent* = same as *filled* but has selectable opacity *frame* = render region as a wireframe (like box or subbox) + *points* = fill region with spheres at random locations + opacity = level of opacity (from 0.0 to 1.0, only for drawstyle *transparent*) npoints = number of attempted points (only for drawstyle *points*) diameter = diameter of wireframe or points (only for drawstyles *frame* and *points*) *subbox* values = lines diam = draw outline of processor subdomains @@ -379,6 +382,9 @@ the mass of both atoms of a pair within the bond cutoff is lower than 3 atomic mass units, a bond is **not** drawn; this prohibits displaying unwanted hydrogen-hydrogen bonds for alkyl or alcohol groups or for water with typical cutoffs suitable for displaying covalent bonds. +For ReaxFF it is also possible to visualize bonds as they are computed +through using :doc:`fix reaxff/bonds ` with the +*fix* keyword (see below). ---------- @@ -514,6 +520,7 @@ change this via the dump_modify command. The *fix* keyword can be used with a :doc:`fix ` that produces objects to be drawn. Below is a list of supported fixes: +* :doc:`fix graphics ` * :doc:`fix indent ` * :doc:`fix smd/wall_surface ` * :doc:`fix wall/lj93 ` @@ -543,28 +550,37 @@ fcolor* command (see below). By default the constant color will be "red" (same as the default color for atom type 1). The *fflag1* and *fflag2* settings are numerical values which are used -by *dump image* to adjust how the drawing of the objects communicated -by the fix is done. See the documentation of the individual fixes for -a description of what these parameters mean. +by *dump image* to adjust how the drawing of the objects communicated by +the fix is done. See the documentation of the individual fixes for a +description of what these parameters mean for the graphics objects +provided by those fixes. ---------- .. versionadded:: 10Sep2025 +.. versionchanged:: TBD + + style *transparency* was added + The *region* keyword can be used to create a graphical representation of a :doc:`region `. This can be helpful in debugging the location and extent of regions, especially when those have parameters controlled by variables. Three styles of representing a region are available: -*filled*, *frame*, and *points*. With style *filled* the surface of the -region is drawn. For region styles that support open faces, surfaces -are not drawn for such open faces. Draw style *frame* represents the -region with a mesh of "wires". The diameter of these "wires" can be -set. Unlike with the *filled* style, you can see what is *inside* the -region with this draw style. The third draw style *points* generates a -random point cloud inside the simulation box and draws only those points -that are within the region. Draw styles *filled* and *frame* support -only "primitive" region style (no unions or intersections), but the -*points* draw style supports all region styles. +*filled*\, *transparency*\, *frame*\, and *points*. With style *filled* +the surface of the region is triangulated and drawn. For region styles +that support open faces, surfaces for such open faces are skipped. The +style *transparent* is like *filled* but takes an additional parameter +in the range of 0.0 to 1.0 that defines the opacity and thus allows to +see what is inside the region. Draw style *frame* represents the region +with a mesh of "wires". The diameter of these "wires" can be set. +Unlike with the *filled* style and similar to the *transparent* style, +you can see what is *inside* the region with this draw style. The third +draw style, *points*\, generates a random point cloud inside the +simulation box and draws only those points that are within the region. +Draw styles *filled*\, *transparent*\, and *frame* support only +"primitive" region styles (no unions or intersections), but the *points* +draw style supports *all* region styles. ---------- diff --git a/doc/src/fix_indent.rst b/doc/src/fix_indent.rst index 3fcaddd3b06..1efdc1fb55e 100644 --- a/doc/src/fix_indent.rst +++ b/doc/src/fix_indent.rst @@ -228,7 +228,10 @@ reduce the radius of the rendered object so that it does not obscure atoms close to it. For a planar indenter in 2d systems, it should be set to a value > 0 or the indenter will not be visible since the diameter is set internally to zero in that case due to lack of a -suitable heuristic for deriving a meaningful diameter. +suitable heuristic for deriving a meaningful diameter. For a planar +indenter in a 3d system, the *fflag2* value sets the transparency of the +plane. It should be set to a value between 0.0 (invisible) and 1.0 +(fully opaque). Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/src/fix_smd_wall_surface.rst b/doc/src/fix_smd_wall_surface.rst index a615514988a..3407fb1be15 100644 --- a/doc/src/fix_smd_wall_surface.rst +++ b/doc/src/fix_smd_wall_surface.rst @@ -61,10 +61,12 @@ particles to *dump image* so that they be included in the rendered image. The *fflag1* setting of *dump image fix* determine whether the wall will -be rendered as triangles (1) or as a mesh of cylinders (2) or both (3). +be rendered as a set of connected triangles (1) or as a mesh of cylinders (2). -The *fflag2* setting determines the diameter of the cylinders for an -*fflag1* setting of either 2 or 3. +If using triangles, the *fflag2* setting determines the transparency of +the triangles and must use a value between 0.0 (invisible) and 1.0 +(fully opaque). If using a mesh of cylinders, the *fflag2* setting +determines the diameter of the cylinders. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/src/fix_wall.rst b/doc/src/fix_wall.rst index 1d23112cf11..ce7702c20f6 100644 --- a/doc/src/fix_wall.rst +++ b/doc/src/fix_wall.rst @@ -511,9 +511,8 @@ image. Please note, that for :doc:`2d systems `, a wall rendered as a plane would be invisible and it is thus rendered as a cylinder. -The *fflag1* setting and the *fflag2* setting of *dump image fix* are -only relevant for 2d systems. The *fflag1* setting determines whether -the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +For 2d systems, the *fflag1* setting determines whether the cylinder +representing the wall is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. The *fflag2* setting allows to adjust the radius of the rendered cylinder. It should be set to a value > 0 or the @@ -521,6 +520,10 @@ cylinder will not be visible since the diameter is set internally to zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. +For 3d systems, the *fflag1* setting is ignored, but the *fflag2* +setting determines the transparency of the wall. It must bet set to a +value between 0.0 (invisible) and 1.0 (fully opaque). + ----------------- Restart, fix_modify, output, run start/stop, minimize info diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index 99ee1e83400..be8caf1f3e5 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -275,9 +275,8 @@ in the rendered image. Please note, that for :doc:`2d systems `, a wall rendered as a plane would be invisible and it is thus rendered as a cylinder. -The *fflag1* setting and the *fflag2* setting of *dump image fix* are -only relevant for 2d systems. The *fflag1* setting determines whether -the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +For 2d systems, the *fflag1* setting determines whether the cylinder +representing the wall is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. The *fflag2* setting allows to adjust the radius of the rendered cylinder. It should be set to a value > 0 or the @@ -285,6 +284,10 @@ cylinder will not be visible since the diameter is set internally to zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. +For 3d systems, the *fflag1* setting is ignored, but the *fflag2* +setting determines the transparency of the wall. It must bet set to a +value between 0.0 (invisible) and 1.0 (fully opaque). + ----------------- Restrictions diff --git a/doc/src/fix_wall_reflect.rst b/doc/src/fix_wall_reflect.rst index cfd53cec9a1..c8ae3b3a7ff 100644 --- a/doc/src/fix_wall_reflect.rst +++ b/doc/src/fix_wall_reflect.rst @@ -151,9 +151,8 @@ image. Please note, that for :doc:`2d systems `, a wall rendered as a plane would be invisible and it is thus rendered as a cylinder. -The *fflag1* setting and the *fflag2* setting of *dump image fix* are -only relevant for 2d systems. The *fflag1* setting determines whether -the cylinder is capped with a sphere at the ends: 0 means no caps, 1 +For 2d systems, the *fflag1* setting determines whether the cylinder +representing the wall is capped with a sphere at the ends: 0 means no caps, 1 means the lower end is capped, 2 means the upper end is capped, and 3 means both ends are capped. The *fflag2* setting allows to adjust the radius of the rendered cylinder. It should be set to a value > 0 or the @@ -161,6 +160,10 @@ cylinder will not be visible since the diameter is set internally to zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. +For 3d systems, the *fflag1* setting is ignored, but the *fflag2* +setting determines the transparency of the wall. It must bet set to a +value between 0.0 (invisible) and 1.0 (fully opaque). + ---------- .. include:: accel_styles.rst From 69ac9198faf034fe2674956ed74d4f8de74ca057 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 00:36:05 -0500 Subject: [PATCH 46/54] fix typos --- doc/src/fix_wall.rst | 2 +- doc/src/fix_wall_gran.rst | 2 +- doc/src/fix_wall_reflect.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/src/fix_wall.rst b/doc/src/fix_wall.rst index ce7702c20f6..1944f8e5565 100644 --- a/doc/src/fix_wall.rst +++ b/doc/src/fix_wall.rst @@ -521,7 +521,7 @@ zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. For 3d systems, the *fflag1* setting is ignored, but the *fflag2* -setting determines the transparency of the wall. It must bet set to a +setting determines the transparency of the wall. It must be set to a value between 0.0 (invisible) and 1.0 (fully opaque). ----------------- diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index be8caf1f3e5..8ff665784eb 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -285,7 +285,7 @@ zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. For 3d systems, the *fflag1* setting is ignored, but the *fflag2* -setting determines the transparency of the wall. It must bet set to a +setting determines the transparency of the wall. It must be set to a value between 0.0 (invisible) and 1.0 (fully opaque). ----------------- diff --git a/doc/src/fix_wall_reflect.rst b/doc/src/fix_wall_reflect.rst index c8ae3b3a7ff..01492701772 100644 --- a/doc/src/fix_wall_reflect.rst +++ b/doc/src/fix_wall_reflect.rst @@ -161,7 +161,7 @@ zero due to lack of a suitable heuristic for deriving a meaningful diameter for all types of walls and unit settings. For 3d systems, the *fflag1* setting is ignored, but the *fflag2* -setting determines the transparency of the wall. It must bet set to a +setting determines the transparency of the wall. It must be set to a value between 0.0 (invisible) and 1.0 (fully opaque). ---------- From 4518d7f80ed18aedee77d255e6c7fd3c6482f5f8 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 00:36:23 -0500 Subject: [PATCH 47/54] add forgotten opacity argument --- src/image.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/image.cpp b/src/image.cpp index 85e87791211..9cd5535fc5f 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -667,8 +667,8 @@ void Image::draw_cylinder(const double *x, const double *y, double camLDir[3], camLRight[3], camLUp[3]; double zmin, zmax; - if (sflag % 2) draw_sphere(x,surfaceColor,diameter); - if (sflag / 2) draw_sphere(y,surfaceColor,diameter); + if (sflag % 2) draw_sphere(x,surfaceColor,diameter,opacity); + if (sflag / 2) draw_sphere(y,surfaceColor,diameter,opacity); double radius = 0.5*diameter; double radsq = radius*radius; From 6898322bb47db424f1e281e1b87b15a7f2718f6f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 00:37:14 -0500 Subject: [PATCH 48/54] remove redundant code --- src/dump_image.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 1f83c413735..106815a8205 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -742,9 +742,6 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : else if (i % 6 == 5) bcolortype[i] = image->color2rgb("aqua"); else if (i % 6 == 0) bcolortype[i] = image->color2rgb("cyan"); } - } else { - bdiamtype = nullptr; - bcolortype = nullptr; } // viewflag = DYNAMIC if any view parameter is dynamic From f3137c521d76ca6c213388cef9134af7265c1c71 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 00:38:06 -0500 Subject: [PATCH 49/54] add support for transparency to atoms, bonds, box, subbox, and axes --- src/dump_image.cpp | 104 ++++++++++++++++++++++++++++++++++----------- src/dump_image.h | 2 + 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/dump_image.cpp b/src/dump_image.cpp index 106815a8205..86130ca87a7 100644 --- a/src/dump_image.cpp +++ b/src/dump_image.cpp @@ -293,10 +293,10 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : DumpCustom(lmp, narg, arg), thetastr(nullptr), phistr(nullptr), cxstr(nullptr), cystr(nullptr), czstr(nullptr), upxstr(nullptr), upystr(nullptr), upzstr(nullptr), zoomstr(nullptr), diamtype(nullptr), diamelement(nullptr), bdiamtype(nullptr), colortype(nullptr), - colorelement(nullptr), bcolortype(nullptr), grid2d(nullptr), grid3d(nullptr), - id_grid_compute(nullptr), id_grid_fix(nullptr), grid_compute(nullptr), grid_fix(nullptr), - gbuf(nullptr), avec_line(nullptr), avec_tri(nullptr), avec_body(nullptr), image(nullptr), - chooseghost(nullptr), bufcopy(nullptr) + colorelement(nullptr), bcolortype(nullptr), aopacity(nullptr), bopacity(nullptr), + grid2d(nullptr), grid3d(nullptr), id_grid_compute(nullptr), id_grid_fix(nullptr), + grid_compute(nullptr), grid_fix(nullptr), gbuf(nullptr), avec_line(nullptr), avec_tri(nullptr), + avec_body(nullptr), image(nullptr), chooseghost(nullptr), bufcopy(nullptr) { if (binary || multiproc) error->all(FLERR, 4, "Invalid dump image filename {}", filename); @@ -375,6 +375,9 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : boxdiam = 0.02; axesflag = NO; subboxflag = NO; + boxopacity = 1.0; + axesopacity = 1.0; + subboxopacity = 1.0; // parse optional args @@ -719,9 +722,11 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : diamelement = new double[ntypes+1]; colortype = new double*[ntypes+1]; colorelement = new double*[ntypes+1]; + aopacity = new double[ntypes+1]; for (int i = 1; i <= ntypes; i++) { diamtype[i] = 1.0; + aopacity[i] = 1.0; if (i % 6 == 1) colortype[i] = image->color2rgb("red"); else if (i % 6 == 2) colortype[i] = image->color2rgb("green"); else if (i % 6 == 3) colortype[i] = image->color2rgb("blue"); @@ -733,8 +738,10 @@ DumpImage::DumpImage(LAMMPS *lmp, int narg, char **arg) : if (bondflag == YES) { bdiamtype = new double[atom->nbondtypes+1]; bcolortype = new double*[atom->nbondtypes+1]; + bopacity = new double[atom->nbondtypes+1]; for (int i = 1; i <= atom->nbondtypes; i++) { bdiamtype[i] = 0.5; + bopacity[i] = 1.0; if (i % 6 == 1) bcolortype[i] = image->color2rgb("red"); else if (i % 6 == 2) bcolortype[i] = image->color2rgb("green"); else if (i % 6 == 3) bcolortype[i] = image->color2rgb("blue"); @@ -771,8 +778,10 @@ DumpImage::~DumpImage() delete[] diamelement; delete[] colortype; delete[] colorelement; + delete[] aopacity; delete[] bdiamtype; delete[] bcolortype; + delete[] bopacity; memory->destroy(chooseghost); memory->destroy(bufcopy); memory->destroy(gbuf); @@ -1221,7 +1230,7 @@ void DumpImage::create_image() { int i,j,k,m,n,itype,atom1,atom2,imol,iatom,btype,ibonus,drawflag; tagint tagprev; - double diameter,delx,dely,delz; + double diameter,delx,dely,delz,opacity; int *bodyvec; double **bodyarray; double *color,*color1,*color2; @@ -1272,7 +1281,7 @@ void DumpImage::create_image() if (bodyflag && body[j] >= 0) drawflag = 0; } - if (drawflag) image->draw_sphere(x[j],color,diameter); + if (drawflag) image->draw_sphere(x[j],color,diameter,aopacity[atom->type[j]]); m += size_one; } @@ -1368,7 +1377,7 @@ void DumpImage::create_image() pt2[1] = x[j][1] - dy; pt2[2] = 0.0; - image->draw_cylinder(pt1,pt2,color,ldiamvalue,3); + image->draw_cylinder(pt1,pt2,color,ldiamvalue,3,aopacity[atom->type[j]]); } } @@ -1392,6 +1401,7 @@ void DumpImage::create_image() if (tcolor == TYPE) { color = colortype[type[j]]; } + double opacity = aopacity[atom->type[j]]; MathExtra::quat_to_mat(avec_tri->bonus[tri[i]].quat,mat); MathExtra::matvec(mat,avec_tri->bonus[tri[i]].c1,pt1); @@ -1401,11 +1411,11 @@ void DumpImage::create_image() MathExtra::add3(pt2,x[i],pt2); MathExtra::add3(pt3,x[i],pt3); - if (tridraw) image->draw_triangle(pt1,pt2,pt3,color); + if (tridraw) image->draw_triangle(pt1,pt2,pt3,color,opacity); if (edgedraw) { - image->draw_cylinder(pt1,pt2,color,tdiamvalue,3); - image->draw_cylinder(pt2,pt3,color,tdiamvalue,3); - image->draw_cylinder(pt3,pt1,color,tdiamvalue,3); + image->draw_cylinder(pt1,pt2,color,tdiamvalue,3,opacity); + image->draw_cylinder(pt2,pt3,color,tdiamvalue,3,opacity); + image->draw_cylinder(pt3,pt1,color,tdiamvalue,3,opacity); } } } @@ -1425,15 +1435,15 @@ void DumpImage::create_image() itype = static_cast(buf[m]); color = colortype[itype]; } + double opacity = aopacity[atom->type[j]]; ibonus = body[j]; n = bptr->image(ibonus,bodyflag1,bodyflag2,bodyvec,bodyarray); for (k = 0; k < n; k++) { if (bodyvec[k] == SPHERE) - image->draw_sphere(bodyarray[k],color,bodyarray[k][3]); + image->draw_sphere(bodyarray[k],color,bodyarray[k][3],opacity); else if (bodyvec[k] == LINE) - image->draw_cylinder(&bodyarray[k][0],&bodyarray[k][3], - color,bodyarray[k][6],3); + image->draw_cylinder(&bodyarray[k][0],&bodyarray[k][3],color,bodyarray[k][6],3,opacity); } m += size_one; @@ -1565,16 +1575,16 @@ void DumpImage::create_image() xmid[1] = x[atom1][1] + 0.5*dely; xmid[2] = x[atom1][2] + 0.5*delz; if (bcolor == ATOM) - image->draw_cylinder(x[atom1],xmid,color1,diameter,3); - else image->draw_cylinder(x[atom1],xmid,color,diameter,3); + image->draw_cylinder(x[atom1],xmid,color1,diameter,3,aopacity[type[atom1]]); + else image->draw_cylinder(x[atom1],xmid,color,diameter,3,bopacity[btype]); xmid[0] = x[atom2][0] - 0.5*delx; xmid[1] = x[atom2][1] - 0.5*dely; xmid[2] = x[atom2][2] - 0.5*delz; if (bcolor == ATOM) - image->draw_cylinder(xmid,x[atom2],color2,diameter,3); - else image->draw_cylinder(xmid,x[atom2],color,diameter,3); + image->draw_cylinder(xmid,x[atom2],color2,diameter,3,aopacity[type[atom1]]); + else image->draw_cylinder(xmid,x[atom2],color,diameter,3,bopacity[btype]); - } else image->draw_cylinder(x[atom1],x[atom2],color,diameter,3); + } else image->draw_cylinder(x[atom1],x[atom2],color,diameter,3,bopacity[btype]); } } } @@ -1687,12 +1697,12 @@ void DumpImage::create_image() xmid[0] = x[atom1][0] + 0.5*dx; xmid[1] = x[atom1][1] + 0.5*dy; xmid[2] = x[atom1][2] + 0.5*dz; - image->draw_cylinder(x[atom1],xmid,color1,diameter,3); + image->draw_cylinder(x[atom1],xmid,color1,diameter,3,aopacity[type[atom1]]); xmid[0] = x[atom2][0] - 0.5*dx; xmid[1] = x[atom2][1] - 0.5*dy; xmid[2] = x[atom2][2] - 0.5*dz; - image->draw_cylinder(xmid,x[atom2],color2,diameter,3); + image->draw_cylinder(xmid,x[atom2],color2,diameter,3,aopacity[type[atom2]]); } } } @@ -2341,7 +2351,7 @@ void DumpImage::create_image() boxcorners = domain->corners; } - image->draw_box(boxcorners,diameter); + image->draw_box(boxcorners,diameter,subboxopacity); } // render outline of simulation box, orthogonal or triclinic @@ -2368,7 +2378,7 @@ void DumpImage::create_image() boxcorners = domain->corners; } - image->draw_box(boxcorners,diameter); + image->draw_box(boxcorners,diameter,boxopacity); } // render XYZ axes in red/green/blue @@ -2421,7 +2431,7 @@ void DumpImage::create_image() axes[3][1] = axes[0][1] + axeslen*(axes[3][1]-axes[0][1]); axes[3][2] = axes[0][2] + axeslen*(axes[3][2]-axes[0][2]); - image->draw_axes(axes,diameter); + image->draw_axes(axes,diameter,axesopacity); } } @@ -2589,6 +2599,16 @@ int DumpImage::modify_param(int narg, char **arg) return 3; } + if (strcmp(arg[0],"atrans") == 0) { + if (narg < 3) error->all(FLERR,"Illegal dump_modify command"); + int nlo,nhi; + utils::bounds_typelabel(FLERR,arg[1],1,atom->ntypes,nlo,nhi,lmp,Atom::ATOM); + double opacity = utils::numeric(FLERR,arg[2],false,lmp); + if ((opacity < 0.0) || (opacity > 1.0)) error->all(FLERR,"Illegal dump_modify command"); + for (int i = nlo; i <= nhi; i++) aopacity[i] = opacity; + return 3; + } + if ((strcmp(arg[0],"amap") == 0) || (strcmp(arg[0],"gmap") == 0)) { if (narg < 6) error->all(FLERR,"Illegal dump_modify command"); if (strlen(arg[3]) != 2) error->all(FLERR,"Illegal dump_modify command"); @@ -2643,6 +2663,18 @@ int DumpImage::modify_param(int narg, char **arg) return 3; } + if (strcmp(arg[0],"btrans") == 0) { + if (narg < 3) error->all(FLERR,"Illegal dump_modify command"); + if (atom->nbondtypes == 0) + error->all(FLERR,"Dump modify btrans not allowed with no bond types"); + int nlo,nhi; + utils::bounds_typelabel(FLERR,arg[1],1,atom->nbondtypes,nlo,nhi,lmp,Atom::BOND); + double opacity = utils::numeric(FLERR,arg[2],false,lmp); + if ((opacity < 0.0) || (opacity > 1.0)) error->all(FLERR,"Illegal dump_modify command"); + for (int i = nlo; i <= nhi; i++) bopacity[i] = opacity; + return 3; + } + if (strcmp(arg[0],"backcolor") == 0) { if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); double *color = image->color2rgb(arg[1]); @@ -2661,6 +2693,30 @@ int DumpImage::modify_param(int narg, char **arg) return 2; } + if (strcmp(arg[0],"boxtrans") == 0) { + if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); + boxopacity = utils::numeric(FLERR,arg[1],false,lmp); + if ((boxopacity < 0.0) || (boxopacity > 1.0)) + error->all(FLERR,"Invalid boxtrans in dump_modify command"); + return 2; + } + + if (strcmp(arg[0],"subboxtrans") == 0) { + if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); + subboxopacity = utils::numeric(FLERR,arg[1],false,lmp); + if ((subboxopacity < 0.0) || (subboxopacity > 1.0)) + error->all(FLERR,"Invalid subboxtrans in dump_modify command"); + return 2; + } + + if (strcmp(arg[0],"axestrans") == 0) { + if (narg < 2) error->all(FLERR,"Illegal dump_modify command"); + axesopacity = utils::numeric(FLERR,arg[1],false,lmp); + if ((axesopacity < 0.0) || (axesopacity > 1.0)) + error->all(FLERR,"Invalid axestrans in dump_modify command"); + return 2; + } + if (strcmp(arg[0],"color") == 0) { if (narg < 5) error->all(FLERR,"Illegal dump_modify command"); int flag = image->addcolor(arg[1],utils::numeric(FLERR,arg[2],false,lmp), diff --git a/src/dump_image.h b/src/dump_image.h index 9cc9c9975c8..80efc057543 100644 --- a/src/dump_image.h +++ b/src/dump_image.h @@ -89,6 +89,7 @@ class DumpImage : public DumpCustom { int zoomvar; // index to zoom variable int boxflag, axesflag; // 0/1 for draw box and axes double boxdiam, axeslen, axesdiam; // params for drawing box and axes + double boxopacity, axesopacity, subboxopacity; // opacity for box, subbox, axes int subboxflag; double subboxdiam; @@ -96,6 +97,7 @@ class DumpImage : public DumpCustom { double *diamtype, *diamelement, *bdiamtype; // per-type diameters double **colortype, **colorelement, **bcolortype; // per-type colors + double *aopacity, *bopacity; // per-type opacity int gridflag; // 0/1 for draw grid cells Grid2d *grid2d; From 6b1d4d3c73042682f7d68e7c8ecf35b920beaaf6 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 01:19:00 -0500 Subject: [PATCH 50/54] document transparency settings --- doc/src/dump_image.rst | 43 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 2c43ccf7d15..61b3966ac97 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -117,7 +117,7 @@ Syntax dump_modify dump-ID keyword values ... * these keywords apply only to the *image* and *movie* styles and are documented on this page -* keyword = *acolor* or *adiam* or *amap* or *gmap* or *backcolor* or *bcolor* or *bdiam* or *bitrate* or *boxcolor* or *color* or *framerate* or *gmap* +* keyword = *acolor* or *adiam* or *amap* or *gmap* or *atrans* or *backcolor* or *bcolor* or *bdiam* or *btrans* or *bitrate* or *boxcolor* or *color* or *framerate* or *axestrans* or *boxtrans* or *subboxtrans* * see the :doc:`dump modify ` doc page for more general keywords .. parsed-literal:: @@ -148,6 +148,9 @@ Syntax color = name of color used for that subset of values entry = color (for sequential style) color = name of color used for a bin of values + *atrans* args = type transparency + type = atom type (numeric or type label) or range of numeric types (see below) + transparency = transparency of atoms of that type (value between 0 (invisible) and 1 (fully opaque)) *backcolor* arg = color color = name of color for background *bcolor* args = type color @@ -156,16 +159,25 @@ Syntax *bdiam* args = type diam type = bond type (numeric or type label) or range of numeric types (see below) diam = diameter of bonds of that type (distance units) - *bitrate* arg = rate - rate = target bitrate for movie in kbps + *btrans* args = type transparency + type = bond type (numeric or type label) or range of numeric types (see below) + transparency = transparency of bonds of that type (value between 0 (invisible) and 1 (fully opaque)) + *axestrans* arg = transparency + transparency = transparency for axes lines (value between 0 (invisible) and 1 (fully opaque)) + *boxtrans* arg = transparency + transparency = transparency for simulation box lines (value between 0 (invisible) and 1 (fully opaque)) *boxcolor* arg = color color = name of color for simulation box lines and processor subdomain lines + *subboxtrans* arg = transparency + transparency = transparency for simulation subbox lines (value between 0 (invisible) and 1 (fully opaque)) *color* args = name R G B name = name of color R,G,B = red/green/blue numeric values from 0.0 to 1.0 *fcolor* args = fix-ID color fix-ID = ID of the fix color = name of color for image objects provided by this fix + *bitrate* arg = rate + rate = target bitrate for movie in kbps *framerate* arg = fps fps = frames per second for movie *gmap* args = identical to *amap* args @@ -1066,6 +1078,22 @@ pre-defined color names with new RBG values. ---------- +**Transparency settings** + +.. versionadded:: TBD + +Various graphical objects in *dump image* output can be rendered in a +transparent fashion using the so-called screen-door transparency method. +This means that only a subset of pixels for a graphical object are +written to the image. This can be controlled with various +*dump\_modify* settings: *atrans* for atoms, *btrans* for bonds, +*axestrans* for axes lines, *boxtrans* for the simulation box, and +*subboxtrans* for the subdomain box lines. The transparency value +must be between 0.0 (invisible) and 1.0 (fully opaque). The default +setting for all is 1.0. + +---------- + .. versionadded:: TBD The *fcolor* keyword sets the color of any image objects created by a @@ -1169,6 +1197,7 @@ The defaults for the dump image and dump movie keywords are as follows: * subbox no 0.0 * shiny = 1.0 * ssao = no +* fsaa = no ---------- @@ -1177,14 +1206,18 @@ The defaults for the dump_modify keywords specific to dump image and dump movie * acolor = \* red/green/blue/yellow/aqua/cyan * adiam = \* 1.0 * amap = min max cf 0.0 2 min blue max red +* atrans = 1.0 * backcolor = black * bcolor = \* red/green/blue/yellow/aqua/cyan * bdiam = \* 0.5 -* bitrate = 2000 +* btrans = 1.0 * boxcolor = yellow +* axestrans = 1.0 +* boxtrans = 1.0 +* subboxtrans = 1.0 * color = 140 color names are pre-defined as listed below +* bitrate = 2000 * framerate = 24 -* fsaa = no * gmap = min max cf 0.0 2 min blue max red ---------- From bd0effb0ad7c2ed27a51686e34b1cd5150f52122 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 03:01:33 -0500 Subject: [PATCH 51/54] add alternate threshold matrix and use symbolic constants instead of magic numbers --- src/image.cpp | 110 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/image.cpp b/src/image.cpp index 9cd5535fc5f..575017904aa 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -55,7 +55,106 @@ enum { CONTINUOUS, DISCRETE, SEQUENTIAL }; enum { ABSOLUTE, FRACTIONAL }; enum { NO, YES }; -constexpr double transthresh[16][16] = { +// #define USE_BAYER_MATRIX +#if defined(USE_BAYER_MATRIX) +//////////////////////////////////////////////////////////////////////// +// the following regular Bayer threshold matrix can be created for any +// power of 2 ranks with the following python code. +// See https://en.wikipedia.org/wiki/Ordered_dithering +// +// import numpy as np +// +// def bayer_matrix(n: int) -> np.ndarray: +// """Generate an n x n Bayer (ordered dither) matrix for n a power of 2.""" +// if n == 1: +// return np.array([[0]], dtype=int) +// +// m = n // 2 +// B = bayer_matrix(m) +// +// # Standard Bayer recursion +// return np.block([ +// [4 * B + 0, 4 * B + 2], +// [4 * B + 3, 4 * B + 1] +// ]) +// +// # set rank +// n=16 +// +// # normalized matrix from recursion +// matrix = (bayer_matrix(n) + 0.5) / (n * n) +// +// print("constexpr int TRANK = %d;" % n) +// print("constexpr double transthresh[TRANK][TRANK] = {") +// nx, ny = matrix.shape +// for iy in range(0,ny): +// print("{") +// for ix in range(0,nx): +// if ix < nx -1: +// print(f"{matrix[ix][iy]:12.9f},") +// else: +// print(f"{matrix[ix][iy]:12.9f}") +// if iy < ny - 1: +// print("},") +// else: +// print("}") +// +// print("};") +//////////////////////////////////////////////////////////////////////// +constexpr int TRANK = 16; +constexpr double transthresh[TRANK][TRANK] = { + {0.001953125, 0.751953125, 0.189453125, 0.939453125, 0.048828125, 0.798828125, 0.236328125, + 0.986328125, 0.013671875, 0.763671875, 0.201171875, 0.951171875, 0.060546875, 0.810546875, + 0.248046875, 0.998046875}, + {0.501953125, 0.251953125, 0.689453125, 0.439453125, 0.548828125, 0.298828125, 0.736328125, + 0.486328125, 0.513671875, 0.263671875, 0.701171875, 0.451171875, 0.560546875, 0.310546875, + 0.748046875, 0.498046875}, + {0.126953125, 0.876953125, 0.064453125, 0.814453125, 0.173828125, 0.923828125, 0.111328125, + 0.861328125, 0.138671875, 0.888671875, 0.076171875, 0.826171875, 0.185546875, 0.935546875, + 0.123046875, 0.873046875}, + {0.626953125, 0.376953125, 0.564453125, 0.314453125, 0.673828125, 0.423828125, 0.611328125, + 0.361328125, 0.638671875, 0.388671875, 0.576171875, 0.326171875, 0.685546875, 0.435546875, + 0.623046875, 0.373046875}, + {0.033203125, 0.783203125, 0.220703125, 0.970703125, 0.017578125, 0.767578125, 0.205078125, + 0.955078125, 0.044921875, 0.794921875, 0.232421875, 0.982421875, 0.029296875, 0.779296875, + 0.216796875, 0.966796875}, + {0.533203125, 0.283203125, 0.720703125, 0.470703125, 0.517578125, 0.267578125, 0.705078125, + 0.455078125, 0.544921875, 0.294921875, 0.732421875, 0.482421875, 0.529296875, 0.279296875, + 0.716796875, 0.466796875}, + {0.158203125, 0.908203125, 0.095703125, 0.845703125, 0.142578125, 0.892578125, 0.080078125, + 0.830078125, 0.169921875, 0.919921875, 0.107421875, 0.857421875, 0.154296875, 0.904296875, + 0.091796875, 0.841796875}, + {0.658203125, 0.408203125, 0.595703125, 0.345703125, 0.642578125, 0.392578125, 0.580078125, + 0.330078125, 0.669921875, 0.419921875, 0.607421875, 0.357421875, 0.654296875, 0.404296875, + 0.591796875, 0.341796875}, + {0.009765625, 0.759765625, 0.197265625, 0.947265625, 0.056640625, 0.806640625, 0.244140625, + 0.994140625, 0.005859375, 0.755859375, 0.193359375, 0.943359375, 0.052734375, 0.802734375, + 0.240234375, 0.990234375}, + {0.509765625, 0.259765625, 0.697265625, 0.447265625, 0.556640625, 0.306640625, 0.744140625, + 0.494140625, 0.505859375, 0.255859375, 0.693359375, 0.443359375, 0.552734375, 0.302734375, + 0.740234375, 0.490234375}, + {0.134765625, 0.884765625, 0.072265625, 0.822265625, 0.181640625, 0.931640625, 0.119140625, + 0.869140625, 0.130859375, 0.880859375, 0.068359375, 0.818359375, 0.177734375, 0.927734375, + 0.115234375, 0.865234375}, + {0.634765625, 0.384765625, 0.572265625, 0.322265625, 0.681640625, 0.431640625, 0.619140625, + 0.369140625, 0.630859375, 0.380859375, 0.568359375, 0.318359375, 0.677734375, 0.427734375, + 0.615234375, 0.365234375}, + {0.041015625, 0.791015625, 0.228515625, 0.978515625, 0.025390625, 0.775390625, 0.212890625, + 0.962890625, 0.037109375, 0.787109375, 0.224609375, 0.974609375, 0.021484375, 0.771484375, + 0.208984375, 0.958984375}, + {0.541015625, 0.291015625, 0.728515625, 0.478515625, 0.525390625, 0.275390625, 0.712890625, + 0.462890625, 0.537109375, 0.287109375, 0.724609375, 0.474609375, 0.521484375, 0.271484375, + 0.708984375, 0.458984375}, + {0.166015625, 0.916015625, 0.103515625, 0.853515625, 0.150390625, 0.900390625, 0.087890625, + 0.837890625, 0.162109375, 0.912109375, 0.099609375, 0.849609375, 0.146484375, 0.896484375, + 0.083984375, 0.833984375}, + {0.666015625, 0.416015625, 0.603515625, 0.353515625, 0.650390625, 0.400390625, 0.587890625, + 0.337890625, 0.662109375, 0.412109375, 0.599609375, 0.349609375, 0.646484375, 0.396484375, + 0.583984375, 0.333984375}}; +#else +// alternate threshold matrix with a more Floyd-Steinberg like pattern +constexpr int TRANK = 16; +constexpr double transthresh[TRANK][TRANK] = { 192.0 / 256.0, 11.0 / 256.0, 183.0 / 256.0, 125.0 / 256.0, 26.0 / 256.0, 145.0 / 256.0, 44.0 / 256.0, 244.0 / 256.0, 8.0 / 256.0, 168.0 / 256.0, 139.0 / 256.0, 38.0 / 256.0, 174.0 / 256.0, 27.0 / 256.0, 141.0 / 256.0, 43.0 / 256.0, 115.0 / 256.0, 211.0 / 256.0, @@ -99,6 +198,7 @@ constexpr double transthresh[16][16] = { 72.0 / 256.0, 136.0 / 256.0, 39.0 / 256.0, 250.0 / 256.0, 104.0 / 256.0, 226.0 / 256.0, 75.0 / 256.0, 112.0 / 256.0, 198.0 / 256.0, 126.0 / 256.0, 66.0 / 256.0, 213.0 / 256.0, 110.0 / 256.0, 203.0 / 256.0, 89.0 / 256.0, 160.0 / 256.0}; +#endif } // namespace // clang-format off @@ -523,7 +623,7 @@ void Image::draw_sphere(const double *x, const double *surfaceColor, double diam for (int iy = yc - pixelRadius; iy <= yc + pixelRadius; iy++) { for (int ix = xc - pixelRadius; ix <= xc + pixelRadius; ix++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; - if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; + if (((opacity < 1.0) && (transthresh[ix % TRANK][iy % TRANK] > opacity)) || (opacity <= 0.0)) continue; double surface[3]; surface[1] = ((iy - yc) - height_error) * pixelWidth; @@ -589,7 +689,7 @@ void Image::draw_cube(const double *x, const double *surfaceColor, double diamet for (int iy = yc - pixelHalfWidth; iy <= yc + pixelHalfWidth; iy ++) { for (int ix = xc - pixelHalfWidth; ix <= xc + pixelHalfWidth; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; - if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; + if (((opacity < 1.0) && (transthresh[ix % TRANK][iy % TRANK] > opacity)) || (opacity <= 0.0)) continue; double sy = ((iy - yc) - height_error) * pixelWidth; double sx = ((ix - xc) - width_error) * pixelWidth; @@ -742,7 +842,7 @@ void Image::draw_cylinder(const double *x, const double *y, for (int iy = yc - pixelHalfHeight; iy <= yc + pixelHalfHeight; iy ++) { for (int ix = xc - pixelHalfWidth; ix <= xc + pixelHalfWidth; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; - if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; + if (((opacity < 1.0) && (transthresh[ix % TRANK][iy % TRANK] > opacity)) || (opacity <= 0.0)) continue; double surface[3], normal[3]; double sy = ((iy - yc) - height_error) * pixelWidth; @@ -869,7 +969,7 @@ void Image::draw_triangle(const double *x, const double *y, const double *z, con for (int iy = yc - pixelDown; iy <= yc + pixelUp; iy ++) { for (int ix = xc - pixelLeft; ix <= xc + pixelRight; ix ++) { if (iy < 0 || iy >= height || ix < 0 || ix >= width) continue; - if (((opacity < 1.0) && (transthresh[ix % 16][iy % 16] > opacity)) || (opacity <= 0.0)) continue; + if (((opacity < 1.0) && (transthresh[ix % TRANK][iy % TRANK] > opacity)) || (opacity <= 0.0)) continue; double sy = ((iy - yc) - height_error) * pixelWidth; double sx = ((ix - xc) - width_error) * pixelWidth; From 9d36d6436e077035c5254d80b013c967d2f46e22 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 16:47:20 -0500 Subject: [PATCH 52/54] remove zcylinder option from fix wall/gran --- doc/src/fix_wall_gran.rst | 47 +++++++++++++++++----------------- src/GRANULAR/fix_wall_gran.cpp | 41 +++-------------------------- src/GRANULAR/fix_wall_gran.h | 2 +- 3 files changed, 28 insertions(+), 62 deletions(-) diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index 8ff665784eb..cf3ccd97766 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -38,15 +38,13 @@ Syntax For *granular*, *fstyle_params* are set using the same syntax as for the *pair_coeff* command of :doc:`pair_style granular ` -* wallstyle = *xplane* or *yplane* or *zplane* or *zcylinder* +* wallstyle = *xplane* or *yplane* or *zplane* * args = list of arguments for a particular style .. parsed-literal:: *xplane* or *yplane* or *zplane* args = lo hi lo,hi = position of lower and upper plane (distance units), either can be NULL) - *zcylinder* args = radius - radius = cylinder radius (distance units) * zero or more keyword/value pairs may be appended to args * keyword = *wiggle* or *shear* or *contacts* or *temperature* @@ -73,7 +71,6 @@ Examples fix 1 all wall/gran hooke 200000.0 NULL 50.0 NULL 0.5 0 xplane -10.0 10.0 fix 1 all wall/gran hooke/history 200000.0 NULL 50.0 NULL 0.5 0 zplane 0.0 NULL - fix 2 all wall/gran hooke 100000.0 20000.0 50.0 30.0 0.5 1 zcylinder 15.0 wiggle z 3.0 2.0 fix 3 all wall/gran granular hooke 1000.0 50.0 tangential linear_nohistory 1.0 0.4 damping velocity region myBox fix 4 all wall/gran granular jkr 1e5 1500.0 0.3 10.0 tangential mindlin NULL 1.0 0.5 rolling sds 500.0 200.0 0.5 twisting marshall region myCone fix 5 all wall/gran granular dmt 1e5 0.2 0.3 10.0 tangential mindlin NULL 1.0 0.5 rolling sds 500.0 200.0 0.5 twisting marshall damping tsuji heat 10 region myCone temperature 1.0 @@ -147,29 +144,34 @@ material. system with particles of diameter 1, Kn, Kt, gamma_n, and gamma_s should be set sqrt(2.0) larger than they were previously. -The effective mass *m_eff* in the formulas listed on the :doc:`pair_style granular ` page is the mass of the particle for -particle/wall interactions (mass of wall is infinite). If the +The effective mass *m_eff* in the formulas listed on the +:doc:`pair_style granular ` page is the mass of the particle +for particle/wall interactions (mass of wall is infinite). If the particle is part of a rigid body, its mass is replaced by the mass of -the rigid body in those formulas. This is determined by searching for -a :doc:`fix rigid ` command (or its variants). +the rigid body in those formulas. This is determined by searching for a +:doc:`fix rigid ` command (or its variants). -The *wallstyle* can be planar or cylindrical. The 3 planar options -specify a pair of walls in a dimension. Wall positions are given by -*lo* and *hi*\ . Either of the values can be specified as NULL if a -single wall is desired. For a *zcylinder* wallstyle, the cylinder's -axis is at x = y = 0.0, and the radius of the cylinder is specified. +The *wallstyle* must one of the three planar options. They specify a +pair of walls in a dimension. Wall positions are given by *lo* and +*hi*\ . Either of the values can be specified as NULL if a single wall +is desired. + +.. deprecated:: TBD + +The *zcylinder* wallstyle has been removed. Pleas use :doc:`fix +wall/gran/region ` instead. Optionally, the wall can be moving, if the *wiggle* or *shear* keywords are appended. Both keywords cannot be used together. For the *wiggle* keyword, the wall oscillates sinusoidally, similar to -the oscillations of particles which can be specified by the :doc:`fix move ` command. This is useful in packing simulations of +the oscillations of particles which can be specified by the :doc:`fix +move ` command. This is useful in packing simulations of granular particles. The arguments to the *wiggle* keyword specify a dimension for the motion, as well as it's *amplitude* and *period*\ . Note that if the dimension is in the plane of the wall, this is -effectively a shearing motion. If the dimension is perpendicular to -the wall, it is more of a shaking motion. A *zcylinder* wall can only -be wiggled in the z dimension. +effectively a shearing motion. If the dimension is perpendicular to the +wall, it is more of a shaking motion. Each timestep, the position of a wiggled wall in the appropriate *dim* is set according to this equation: @@ -186,12 +188,11 @@ to the derivative of this expression. For the *shear* keyword, the wall moves continuously in the specified dimension with velocity *vshear*\ . The dimension must be tangential to walls with a planar *wallstyle*, e.g. in the *y* or *z* directions for -an *xplane* wall. For *zcylinder* walls, a dimension of *z* means the -cylinder is moving in the z-direction along it's axis. A dimension of -*x* or *y* means the cylinder is spinning around the z-axis, either in -the clockwise direction for *vshear* > 0 or counter-clockwise for -*vshear* < 0. In this case, *vshear* is the tangential velocity of -the wall at whatever *radius* has been defined. +an *xplane* wall. A dimension of *x* or *y* means the cylinder is +spinning around the z-axis, either in the clockwise direction for +*vshear* > 0 or counter-clockwise for *vshear* < 0. In this case, +*vshear* is the tangential velocity of the wall at whatever *radius* has +been defined. The *temperature* keyword is used to assign a temperature to the wall. The following value can either be a numeric value or an equal-style diff --git a/src/GRANULAR/fix_wall_gran.cpp b/src/GRANULAR/fix_wall_gran.cpp index d4c99d95482..5dd8d6a9fc1 100644 --- a/src/GRANULAR/fix_wall_gran.cpp +++ b/src/GRANULAR/fix_wall_gran.cpp @@ -49,7 +49,7 @@ static constexpr double BIG = 1.0e20; // XYZ PLANE need to be 0,1,2 -enum {NOSTYLE=-1,XPLANE=0,YPLANE=1,ZPLANE=2,ZCYLINDER,REGION}; +enum {NOSTYLE=-1,XPLANE=0,YPLANE=1,ZPLANE=2,REGION}; enum {NONE,CONSTANT,EQUAL}; /* ---------------------------------------------------------------------- */ @@ -185,12 +185,9 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : } iarg += 3; } else if (strcmp(arg[iarg],"zcylinder") == 0) { - if (narg < iarg+2) error->all(FLERR,"Illegal fix wall/gran command"); - wallstyle = ZCYLINDER; - numwalls = 1; - lo = hi = 0.0; - cylradius = utils::numeric(FLERR,arg[iarg+1],false,lmp); iarg += 2; + error->all(FLERR, iarg, "The zcylinder keyword has been removed. " + "Please use fix wall/gran/region instead."); } else if (strcmp(arg[iarg],"region") == 0) { if (narg < iarg+2) error->all(FLERR,"Illegal fix wall/gran command"); wallstyle = REGION; @@ -259,13 +256,9 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : error->all(FLERR,"Cannot use wall in periodic dimension"); if (wallstyle == ZPLANE && domain->zperiodic) error->all(FLERR,"Cannot use wall in periodic dimension"); - if (wallstyle == ZCYLINDER && (domain->xperiodic || domain->yperiodic)) - error->all(FLERR,"Cannot use wall in periodic dimension"); if (wiggle && wshear) error->all(FLERR,"Cannot wiggle and shear fix wall/gran"); - if (wiggle && wallstyle == ZCYLINDER && axis != 2) - error->all(FLERR,"Invalid wiggle direction for fix wall/gran"); if (wshear && wallstyle == XPLANE && axis == 0) error->all(FLERR,"Invalid shear direction for fix wall/gran"); if (wshear && wallstyle == YPLANE && axis == 1) @@ -325,14 +318,6 @@ FixWallGran::FixWallGran(LAMMPS *lmp, int narg, char **arg) : imgparms[2 * m + 1][0] = 1; // use color of first atom type by default } } - } else if (wallstyle == ZCYLINDER) { - // one cylinder object per wall to draw - memory->create(imgobjs, numwalls, "fix_wall:imgobjs"); - memory->create(imgparms, numwalls, 8, "fix_wall:imgparms"); - for (int m = 0; m < numwalls; ++m) { - imgobjs[m] = DumpImage::CYLINDER; - imgparms[m][0] = 1; // use color of first atom type by default - } } } @@ -541,23 +526,6 @@ void FixWallGran::post_force(int /*vflag*/) del2 = whi - x[i][2]; if (del1 < del2) dz = del1; else dz = -del2; - } else if (wallstyle == ZCYLINDER) { - delxy = sqrt(x[i][0] * x[i][0] + x[i][1] * x[i][1]); - delr = cylradius - delxy; - if (delr > radius[i]) { - dz = cylradius; - rwall = 0.0; - } else { - dx = -delr / delxy * x[i][0]; - dy = -delr / delxy * x[i][1]; - // rwall = -2r_c if inside cylinder, 2r_c outside - rwall = (delxy < cylradius) ? -2 * cylradius : 2 * cylradius; - if (wshear && axis != 2) { - vwall[0] += vshear * x[i][1] / delxy; - vwall[1] += -vshear * x[i][0] / delxy; - vwall[2] = 0.0; - } - } } // Reset model and copy initial geometric data @@ -848,9 +816,6 @@ int FixWallGran::image(int *&objs, double **&parms) return 2 * numwalls; } break; - case ZCYLINDER: - return 0; - break; case REGION: // can visualize region directly return 0; break; diff --git a/src/GRANULAR/fix_wall_gran.h b/src/GRANULAR/fix_wall_gran.h index 413878a588a..abb769ee5bb 100644 --- a/src/GRANULAR/fix_wall_gran.h +++ b/src/GRANULAR/fix_wall_gran.h @@ -60,7 +60,7 @@ class FixWallGran : public Fix { int nlevels_respa; bigint time_origin; - double lo, hi, cylradius; + double lo, hi; double amplitude, period, omega, vshear; double dt; double Twall; From 59d391eee7e8097eb39c9d31a631f616d0f615f4 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 18 Dec 2025 19:31:44 -0500 Subject: [PATCH 53/54] stick to Bayer matrix only and recommend 0.25, 0.5, or 0.75 with FSAA --- doc/src/dump_image.rst | 8 ++++++- src/image.cpp | 50 ------------------------------------------ 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/doc/src/dump_image.rst b/doc/src/dump_image.rst index 61b3966ac97..ed77daef314 100644 --- a/doc/src/dump_image.rst +++ b/doc/src/dump_image.rst @@ -594,6 +594,9 @@ Draw styles *filled*\, *transparent*\, and *frame* support only "primitive" region styles (no unions or intersections), but the *points* draw style supports *all* region styles. +Recommended transparency settings are the values of 0.25, 0.5, or 0.75 +when used in combination with *fsaa on*. + ---------- The *size* keyword sets the width and height of the created images, @@ -1078,7 +1081,7 @@ pre-defined color names with new RBG values. ---------- -**Transparency settings** +**Transparency settings for atoms bonds and standard visualization objects** .. versionadded:: TBD @@ -1092,6 +1095,9 @@ written to the image. This can be controlled with various must be between 0.0 (invisible) and 1.0 (fully opaque). The default setting for all is 1.0. +Recommended transparency settings are the values of 0.25, 0.5, or 0.75 +when used in combination with *fsaa on*. + ---------- .. versionadded:: TBD diff --git a/src/image.cpp b/src/image.cpp index 575017904aa..c8ad2d351fb 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -55,8 +55,6 @@ enum { CONTINUOUS, DISCRETE, SEQUENTIAL }; enum { ABSOLUTE, FRACTIONAL }; enum { NO, YES }; -// #define USE_BAYER_MATRIX -#if defined(USE_BAYER_MATRIX) //////////////////////////////////////////////////////////////////////// // the following regular Bayer threshold matrix can be created for any // power of 2 ranks with the following python code. @@ -151,54 +149,6 @@ constexpr double transthresh[TRANK][TRANK] = { {0.666015625, 0.416015625, 0.603515625, 0.353515625, 0.650390625, 0.400390625, 0.587890625, 0.337890625, 0.662109375, 0.412109375, 0.599609375, 0.349609375, 0.646484375, 0.396484375, 0.583984375, 0.333984375}}; -#else -// alternate threshold matrix with a more Floyd-Steinberg like pattern -constexpr int TRANK = 16; -constexpr double transthresh[TRANK][TRANK] = { - 192.0 / 256.0, 11.0 / 256.0, 183.0 / 256.0, 125.0 / 256.0, 26.0 / 256.0, 145.0 / 256.0, - 44.0 / 256.0, 244.0 / 256.0, 8.0 / 256.0, 168.0 / 256.0, 139.0 / 256.0, 38.0 / 256.0, - 174.0 / 256.0, 27.0 / 256.0, 141.0 / 256.0, 43.0 / 256.0, 115.0 / 256.0, 211.0 / 256.0, - 150.0 / 256.0, 68.0 / 256.0, 194.0 / 256.0, 88.0 / 256.0, 177.0 / 256.0, 131.0 / 256.0, - 61.0 / 256.0, 222.0 / 256.0, 87.0 / 256.0, 238.0 / 256.0, 74.0 / 256.0, 224.0 / 256.0, - 100.0 / 256.0, 235.0 / 256.0, 59.0 / 256.0, 33.0 / 256.0, 96.0 / 256.0, 239.0 / 256.0, - 51.0 / 256.0, 232.0 / 256.0, 16.0 / 256.0, 210.0 / 256.0, 117.0 / 256.0, 32.0 / 256.0, - 187.0 / 256.0, 1.0 / 256.0, 157.0 / 256.0, 121.0 / 256.0, 14.0 / 256.0, 165.0 / 256.0, - 248.0 / 256.0, 128.0 / 256.0, 217.0 / 256.0, 2.0 / 256.0, 163.0 / 256.0, 105.0 / 256.0, - 154.0 / 256.0, 81.0 / 256.0, 247.0 / 256.0, 149.0 / 256.0, 97.0 / 256.0, 205.0 / 256.0, - 52.0 / 256.0, 182.0 / 256.0, 209.0 / 256.0, 84.0 / 256.0, 20.0 / 256.0, 172.0 / 256.0, - 80.0 / 256.0, 140.0 / 256.0, 202.0 / 256.0, 41.0 / 256.0, 185.0 / 256.0, 55.0 / 256.0, - 24.0 / 256.0, 197.0 / 256.0, 65.0 / 256.0, 129.0 / 256.0, 252.0 / 256.0, 35.0 / 256.0, - 70.0 / 256.0, 147.0 / 256.0, 201.0 / 256.0, 63.0 / 256.0, 189.0 / 256.0, 28.0 / 256.0, - 90.0 / 256.0, 254.0 / 256.0, 116.0 / 256.0, 219.0 / 256.0, 137.0 / 256.0, 107.0 / 256.0, - 231.0 / 256.0, 17.0 / 256.0, 144.0 / 256.0, 119.0 / 256.0, 228.0 / 256.0, 109.0 / 256.0, - 46.0 / 256.0, 245.0 / 256.0, 103.0 / 256.0, 229.0 / 256.0, 134.0 / 256.0, 13.0 / 256.0, - 67.0 / 256.0, 162.0 / 256.0, 6.0 / 256.0, 170.0 / 256.0, 47.0 / 256.0, 178.0 / 256.0, - 76.0 / 256.0, 193.0 / 256.0, 4.0 / 256.0, 167.0 / 256.0, 133.0 / 256.0, 9.0 / 256.0, - 159.0 / 256.0, 54.0 / 256.0, 175.0 / 256.0, 124.0 / 256.0, 225.0 / 256.0, 93.0 / 256.0, - 242.0 / 256.0, 79.0 / 256.0, 214.0 / 256.0, 99.0 / 256.0, 241.0 / 256.0, 56.0 / 256.0, - 221.0 / 256.0, 92.0 / 256.0, 186.0 / 256.0, 218.0 / 256.0, 78.0 / 256.0, 208.0 / 256.0, - 37.0 / 256.0, 196.0 / 256.0, 25.0 / 256.0, 188.0 / 256.0, 42.0 / 256.0, 142.0 / 256.0, - 29.0 / 256.0, 158.0 / 256.0, 21.0 / 256.0, 130.0 / 256.0, 156.0 / 256.0, 40.0 / 256.0, - 102.0 / 256.0, 31.0 / 256.0, 148.0 / 256.0, 111.0 / 256.0, 234.0 / 256.0, 85.0 / 256.0, - 151.0 / 256.0, 120.0 / 256.0, 207.0 / 256.0, 113.0 / 256.0, 255.0 / 256.0, 86.0 / 256.0, - 184.0 / 256.0, 212.0 / 256.0, 69.0 / 256.0, 236.0 / 256.0, 176.0 / 256.0, 73.0 / 256.0, - 253.0 / 256.0, 0.0 / 256.0, 138.0 / 256.0, 58.0 / 256.0, 249.0 / 256.0, 71.0 / 256.0, - 10.0 / 256.0, 173.0 / 256.0, 62.0 / 256.0, 200.0 / 256.0, 50.0 / 256.0, 114.0 / 256.0, - 12.0 / 256.0, 123.0 / 256.0, 23.0 / 256.0, 204.0 / 256.0, 118.0 / 256.0, 191.0 / 256.0, - 91.0 / 256.0, 181.0 / 256.0, 19.0 / 256.0, 164.0 / 256.0, 216.0 / 256.0, 101.0 / 256.0, - 233.0 / 256.0, 3.0 / 256.0, 135.0 / 256.0, 169.0 / 256.0, 246.0 / 256.0, 152.0 / 256.0, - 223.0 / 256.0, 60.0 / 256.0, 143.0 / 256.0, 48.0 / 256.0, 240.0 / 256.0, 34.0 / 256.0, - 220.0 / 256.0, 82.0 / 256.0, 132.0 / 256.0, 36.0 / 256.0, 146.0 / 256.0, 106.0 / 256.0, - 227.0 / 256.0, 30.0 / 256.0, 95.0 / 256.0, 49.0 / 256.0, 83.0 / 256.0, 166.0 / 256.0, - 18.0 / 256.0, 199.0 / 256.0, 98.0 / 256.0, 155.0 / 256.0, 122.0 / 256.0, 53.0 / 256.0, - 237.0 / 256.0, 179.0 / 256.0, 57.0 / 256.0, 190.0 / 256.0, 77.0 / 256.0, 195.0 / 256.0, - 127.0 / 256.0, 180.0 / 256.0, 230.0 / 256.0, 108.0 / 256.0, 215.0 / 256.0, 64.0 / 256.0, - 171.0 / 256.0, 5.0 / 256.0, 206.0 / 256.0, 161.0 / 256.0, 22.0 / 256.0, 94.0 / 256.0, - 251.0 / 256.0, 15.0 / 256.0, 153.0 / 256.0, 45.0 / 256.0, 243.0 / 256.0, 7.0 / 256.0, - 72.0 / 256.0, 136.0 / 256.0, 39.0 / 256.0, 250.0 / 256.0, 104.0 / 256.0, 226.0 / 256.0, - 75.0 / 256.0, 112.0 / 256.0, 198.0 / 256.0, 126.0 / 256.0, 66.0 / 256.0, 213.0 / 256.0, - 110.0 / 256.0, 203.0 / 256.0, 89.0 / 256.0, 160.0 / 256.0}; -#endif } // namespace // clang-format off From 44954f74c1a36890336fa31dc8d276204c6ae0ea Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 19 Dec 2025 04:27:38 -0500 Subject: [PATCH 54/54] small corrections and updates --- doc/src/fix_graphics.rst | 14 +++++++------- doc/src/fix_smd_wall_surface.rst | 15 ++++++++------- doc/utils/sphinx-config/false_positives.txt | 6 ++++++ src/fix_graphics.cpp | 3 ++- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/doc/src/fix_graphics.rst b/doc/src/fix_graphics.rst index d077c1e7f52..b3247e2f976 100644 --- a/doc/src/fix_graphics.rst +++ b/doc/src/fix_graphics.rst @@ -68,6 +68,13 @@ parameter of the :doc:`dump ` image command. LAMMPS will stop with an error message if the settings for this fix and the dump command are not compatible. +Available graphics objects are (see above for exact command line syntax): + +- *sphere* - a sphere defined by its center location and its radius +- *cylinder* - a cylinder defined by its two center endpoints and its radius +- *arrow* - a cylinder with a cone at one side (see note below) +- *progbar* - progress bar a long a selected axis and with optional tick marks + The *type* quantity determines the color of the object. Its represents an *atom* type and the object will be colored the same as the corresponding atom type when the *type* coloring scheme is used in the @@ -92,13 +99,6 @@ of tics shown on the progress bar, this must be a number between 0 and 20. Unlike for the other graphics objects, all settings except for *ratio* are fixed and cannot be a variable reference. -Available graphics objects are (see above for exact command line syntax): - -- *sphere* - a sphere defined by its center location and its radius -- *cylinder* - a cylinder defined by its two center endpoints and its radius -- *arrow* - a cylinder with a cone at one side (see note below) -- *progbar* - progress bar a long a selected axis and with optional tick marks - .. admonition:: Work in progress notice :class: note diff --git a/doc/src/fix_smd_wall_surface.rst b/doc/src/fix_smd_wall_surface.rst index 3407fb1be15..53556b940bb 100644 --- a/doc/src/fix_smd_wall_surface.rst +++ b/doc/src/fix_smd_wall_surface.rst @@ -60,13 +60,13 @@ Fix *smd/wall\_surface* supports the *fix* keyword of :doc:`dump image particles to *dump image* so that they be included in the rendered image. -The *fflag1* setting of *dump image fix* determine whether the wall will +The *fflag1* setting of *dump image fix* determines whether the wall will be rendered as a set of connected triangles (1) or as a mesh of cylinders (2). -If using triangles, the *fflag2* setting determines the transparency of -the triangles and must use a value between 0.0 (invisible) and 1.0 -(fully opaque). If using a mesh of cylinders, the *fflag2* setting -determines the diameter of the cylinders. +In case of using triangles, the *fflag2* setting determines the +transparency of the triangles and must use a value between 0.0 +(invisible) and 1.0 (fully opaque). If using a mesh of cylinders, the +*fflag2* setting determines the diameter of the cylinders. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -77,8 +77,9 @@ minimization. This fix has no outputs. Restrictions """""""""""" -This fix is part of the MACHDYN package. It is only enabled if -LAMMPS was built with that package. See the :doc:`Build package ` page for more info. +This fix is part of the MACHDYN package. It is only enabled if LAMMPS +was built with that package. See the :doc:`Build package +` page for more info. The molecule ID given to the particles created by this fix have to be equal to or larger than 65535. diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index d49c8210b7e..f2cc6c1aed2 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -191,6 +191,7 @@ AtomicPairStyle atomID atomistic atomtypes +atrans attogram attograms attrac @@ -217,6 +218,7 @@ awpmd AWPMD ax Axel +axestrans Axilrod Ay ay @@ -384,6 +386,7 @@ Bourne boxcolor boxhi boxlo +boxtrans boxxhi boxxlo boxyhi @@ -415,6 +418,7 @@ Bruskin Brusselle Bryantsev Btarget +btrans btype buckPlusAttr buf @@ -1736,6 +1740,7 @@ isokinetic isomorphism isothermal isotropically +isovalue isovolume Isralewitz iter @@ -3758,6 +3763,7 @@ strstr Stukowski Su subbox +subboxtrans Subclassed subcutoff subcycle diff --git a/src/fix_graphics.cpp b/src/fix_graphics.cpp index b54f5d0a1fc..a58abae62f8 100644 --- a/src/fix_graphics.cpp +++ b/src/fix_graphics.cpp @@ -46,6 +46,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : nevery = utils::inumeric(FLERR, arg[3], false, lmp); if (nevery <= 0) error->all(FLERR, 3, "Illegal fix graphics nevery value {}", nevery); global_freq = nevery; + dynamic_group_allow = 1; numobjs = 0; varflag = 0; @@ -129,7 +130,7 @@ FixGraphics::FixGraphics(LAMMPS *lmp, int narg, char **arg) : ++numobjs; iarg += 9; } else if (strcmp(arg[iarg], "arrow") == 0) { - if (iarg + 9 > narg) utils::missing_cmd_args(FLERR, "fix graphics arrow", error); + if (iarg + 10 > narg) utils::missing_cmd_args(FLERR, "fix graphics arrow", error); // clang-format off ArrowItem arrow{ARROW, 1, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, 0.0, 0.1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,