Index: src/libged/lc.c
===================================================================
--- src/libged/lc.c	(revision 0)
+++ src/libged/lc.c	(working copy)
@@ -0,0 +1,403 @@
+/*                         L C . C
+ * BRL-CAD
+ *
+ * Copyright (c) 2014-2014 United States Government as represented by
+ * the U.S. Army Research Laboratory.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; see the file named COPYING for more
+ * information.
+ */
+/** @file libged/lc.c
+ *
+ * The lc command.
+ *
+ */
+
+#include "common.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "ged.h"
+#include "bu/str.h"
+#include "bu/vls.h"
+#include "bu/file.h"
+#include "bu/getopt.h"
+#include "bu/sort.h"
+#include "bu/ptbl.h"
+
+
+struct region_record
+{
+    int ignore;
+    const char *region_id;
+    const char *material_id;
+    const char *los;
+    const char *obj_name;
+    const char *obj_parent;
+};
+
+/**
+ * Sorts the region lists such that identical entries are next to each
+ * other; used to remove duplicates.
+ */
+static int
+cmp_regions(const void *a, const void *b, void *UNUSED(arg))
+{
+    struct region_record *r1 = (struct region_record *)a;
+    struct region_record *r2 = (struct region_record *)b;
+    int cmp;
+
+    cmp = bu_strcmp(r1->region_id, r2->region_id);
+    if (cmp) { return cmp; }
+    cmp = bu_strcmp(r1->material_id, r2->material_id);
+    if (cmp) { return cmp; }
+    cmp = bu_strcmp(r1->los, r2->los);
+    return cmp;
+}
+
+/**
+ * Sorts the region list according to the sort parameter (1 - 5) passed in.
+ */
+static int
+sort_regions(const void *a, const void *b, void *arg)
+{
+    struct region_record *r1 = (struct region_record *)a;
+    struct region_record *r2 = (struct region_record *)b;
+    int *sort_type = (int *)arg;
+
+    switch (*sort_type) {
+	case 1:
+	    return bu_strcmp(r1->region_id, r2->region_id);
+	case 2:
+	    return bu_strcmp(r1->material_id, r2->material_id);
+	case 3:
+	    return bu_strcmp(r1->los, r2->los);
+	case 4:
+	    return bu_strcmp(r1->obj_name, r2->obj_name);
+	case 5:
+	    return bu_strcmp(r1->obj_parent, r2->obj_parent);
+    }
+    /* This should never be executed */
+    return 0;
+}
+
+static void
+print_cmd_args(struct bu_vls *output, int argc, const char *argv[])
+{
+    int i;
+    bu_vls_printf(output, "Command args: '");
+    for (i = 0; i < argc; i++) {
+	bu_vls_printf(output, "%s", argv[i]);
+	if (i < argc - 1) {
+	    bu_vls_printf(output, " ");
+	} else {
+	    bu_vls_printf(output, "'\n");
+	}
+    }
+}
+
+/**
+ * Get an attribute from a bu_avs, like bu_avs_get, but return "--" if
+ * the item is missing, instead of NULL
+ */
+static const char *
+get_attr(const struct bu_attribute_value_set *avp, const char *attribute)
+{
+    const char *value = bu_avs_get(avp, attribute);
+    return value ? value : "--";
+}
+
+
+int
+ged_lc(struct ged *gedp, int argc, const char *argv[])
+{
+    char *file_name = NULL;
+    int file_name_flag_cnt = 0;
+    int sort_column = 1;
+    int sort_column_flag_cnt = 0;
+    int find_duplicates_flag = 0;
+    int skip_special_duplicates_flag = 0;
+    int skip_subtracted_regions_flag = 0;
+    int descending_sort_flag = 0;
+    int unrecognized_flag_cnt = 0;
+
+    int orig_argc;
+    const char **orig_argv;
+
+    static const char *usage = "[-d|-s|-r] [-z] [-0|-1|-2|-3|-4|-5] [-f {FileName}] {GroupName}";
+
+    int c;
+    int error_cnt = 0;
+
+    FILE *outfile = NULL;
+    struct bu_vls *output;
+
+    const char *group_name;
+
+    size_t i = 0;
+    struct bu_ptbl results1 = BU_PTBL_INIT_ZERO;
+    struct bu_ptbl results2 = BU_PTBL_INIT_ZERO;
+    char *path;
+    char *plan;
+    struct db_full_path root;
+    int matches;
+    struct region_record *regions;
+    size_t ignored_cnt = 0;
+
+    /* The defaults are the minimum widths to accommodate column headers */
+    size_t region_id_len_max = 2;
+    size_t material_id_len_max = 3;
+    size_t los_len_max = 3;
+    size_t obj_len_max = 6;
+
+    /* For the output at the end */
+    size_t start, end, incr;
+
+    /* initialize result */
+    bu_vls_trunc(gedp->ged_result_str, 0);
+
+    if (argc == 1) {
+	bu_vls_printf(gedp->ged_result_str, "Usage: %s\n", usage);
+	return GED_HELP;
+    }
+
+    GED_CHECK_DATABASE_OPEN(gedp, GED_ERROR);
+    GED_CHECK_ARGC_GT_0(gedp, argc, GED_ERROR);
+
+    bu_optind = 1; /* re-init bu_getopt() */
+    while ((c = bu_getopt(argc, (char * const *)argv, "dsrz012345f:")) != -1) {
+	switch (c) {
+	    case '0':
+	    case '1':
+	    case '2':
+	    case '3':
+	    case '4':
+	    case '5':
+		sort_column_flag_cnt++;
+		sort_column = c - '0';
+		break;
+	    case 'f':
+		file_name_flag_cnt++;
+		file_name = bu_optarg;
+		break;
+	    case 'd':
+		find_duplicates_flag = 1;
+		break;
+	    case 's':
+		find_duplicates_flag = 1;
+		skip_special_duplicates_flag = 1;
+		break;
+	    case 'r':
+		skip_subtracted_regions_flag = 1;
+		break;
+	    case 'z':
+		descending_sort_flag = 1;
+	    default:
+		unrecognized_flag_cnt++;
+	}
+    }
+    orig_argc = argc;
+    orig_argv = argv;
+    argc -= (bu_optind - 1);
+    argv += (bu_optind - 1);
+
+    /* Attempt to recreate the exact error messages from the original lc.tcl */
+
+    if (file_name_flag_cnt > 1) {
+	bu_vls_printf(gedp->ged_result_str, "Error: '-f' used more than once.\n");
+	error_cnt++;
+    }
+
+    if (sort_column_flag_cnt > 1) {
+	bu_vls_printf(gedp->ged_result_str, "Error: Sort column defined more than once.\n");
+	error_cnt++;
+    }
+
+    if (file_name_flag_cnt + argc + unrecognized_flag_cnt > 3) {
+	bu_vls_printf(gedp->ged_result_str, "Error: More than one group name and/or file name was specified.\n");
+	error_cnt++;
+    } else if (argc < 2) {
+	if (file_name_flag_cnt && !file_name) {
+	    bu_vls_printf(gedp->ged_result_str, "Error: Group name and file name not specified\n");
+	    error_cnt++;
+	} else {
+	    bu_vls_printf(gedp->ged_result_str, "Error: Group name not specified.\n");
+	    error_cnt++;
+	}
+    } else if (argc + unrecognized_flag_cnt > 2) {
+	bu_vls_printf(gedp->ged_result_str, "Error: More than one group name was specified.\n");
+	error_cnt++;
+    } else if (file_name_flag_cnt && !file_name) {
+	bu_vls_printf(gedp->ged_result_str, "Error: File name not specified.\n");
+	error_cnt++;
+    }
+
+    if (file_name) {
+	char *norm_name;
+	if (file_name[0] == '-') {
+	    bu_vls_printf(gedp->ged_result_str, "Error: File name can not start with '-'.\n");
+	    error_cnt++;
+	} else if (bu_file_exists(file_name, NULL)) {
+	    bu_vls_printf(gedp->ged_result_str, "Error: File '$norm_name' already exists.\n");
+	    error_cnt++;
+	} else {
+	    outfile = fopen(file_name, "w");
+	    if (!outfile) {
+		bu_vls_printf(gedp->ged_result_str, "Error: %d\n", errno);
+		error_cnt++;
+	    }
+	}
+	norm_name = bu_realpath(file_name, NULL);
+	bu_vls_printf(gedp->ged_result_str, "Output filename: %s\n", norm_name);
+	bu_free(norm_name, "ged_lc");
+	output = bu_vls_vlsinit();
+    } else {
+	output = gedp->ged_result_str;
+    }
+
+    if (error_cnt > 0) { return GED_ERROR; }
+
+    group_name = argv[1];
+
+    /* The 7 is for the "-name" and '\0' */
+    plan = (char *) bu_malloc(sizeof(char) * (strlen(group_name) + 7), "ged_lc");
+    sprintf(plan, "-name %s", group_name);
+    matches = db_search(&results1, DB_SEARCH_TREE, plan, 0, NULL, gedp->ged_wdbp->dbip);
+    if (matches < 1) {
+	bu_vls_printf(gedp->ged_result_str, "Error: Group '%s' does not exist.\n", group_name);
+	return GED_ERROR;
+    }
+    bu_free(plan, "ged_lc");
+    db_search_free(&results1);
+
+    if (skip_subtracted_regions_flag) {
+	plan = "-type region ! -bool -";
+    } else {
+	plan = "-type region";
+    }
+    path = (char *) bu_malloc(sizeof(char) * (strlen(group_name) + 2), "ged_lc");
+    sprintf(path, "/%s", group_name);
+    db_string_to_path(&root, gedp->ged_wdbp->dbip, path);
+    matches = db_search(&results2, DB_SEARCH_TREE, plan, root.fp_len, root.fp_names, gedp->ged_wdbp->dbip);
+    bu_free(path, "ged_lc");
+    if (matches < 1) { return GED_ERROR; }
+    regions = (struct region_record *) bu_malloc(sizeof(struct region_record) * BU_PTBL_LEN(&results2), "ged_lc");
+    for (i = 0; i < BU_PTBL_LEN(&results2); i++) {
+	struct db_full_path *entry = (struct db_full_path *)BU_PTBL_GET(&results2, i);
+	struct directory *dp_curr_dir = DB_FULL_PATH_CUR_DIR(entry);
+	struct bu_attribute_value_set avs;
+
+	regions[i].ignore = 0;
+
+	bu_avs_init_empty(&avs);
+	db5_get_attributes(gedp->ged_wdbp->dbip, &avs, dp_curr_dir);
+
+	regions[i].region_id = get_attr(&avs, "region_id");
+	V_MAX(region_id_len_max, strlen(regions[i].region_id));
+	regions[i].material_id = get_attr(&avs, "material_id");
+	V_MAX(material_id_len_max, strlen(regions[i].material_id));
+	regions[i].los = get_attr(&avs, "los");
+	V_MAX(los_len_max, strlen(regions[i].los));
+	regions[i].obj_name = dp_curr_dir->d_namep;
+	V_MAX(obj_len_max, strlen(regions[i].obj_name));
+
+	if (entry->fp_len > 1) {
+	    struct directory *dp_parent = DB_FULL_PATH_GET(entry, entry->fp_len - 2);
+	    regions[i].obj_parent = dp_parent->d_namep;
+	} else {
+	    regions[i].obj_parent = "--";
+	}
+    }
+
+    if (find_duplicates_flag) {
+
+	bu_sort((void *) regions, BU_PTBL_LEN(&results2), sizeof(struct region_record), cmp_regions, NULL);
+
+	for (i = 1;  i < BU_PTBL_LEN(&results2); i++) {
+	    int same;
+	    if (skip_special_duplicates_flag) {
+		same = !cmp_regions((void *)&(regions[i - 1]), (void *)&(regions[i]), NULL);
+	    } else {
+		same = !bu_strcmp(regions[i - 1].region_id, regions[i].region_id);
+	    }
+	    if (same) {
+		regions[i].ignore = 1;
+		ignored_cnt++;
+	    }
+	}
+
+	if (ignored_cnt == BU_PTBL_LEN(&results2)) {
+	    if (file_name) {
+		print_cmd_args(output, orig_argc, orig_argv);
+		bu_vls_printf(output, "No duplicate region_id\n");
+		bu_vls_fwrite(outfile, output);
+		fclose(outfile);
+	    }
+	    bu_vls_printf(gedp->ged_result_str, "No duplicate region_id\n");
+	    bu_vls_printf(gedp->ged_result_str, "Done.\n");
+	    bu_free(regions, "ged_lc");
+	    return GED_ERROR;
+	}
+    }
+
+    if (sort_column > 0) {
+	bu_sort((void *) regions, BU_PTBL_LEN(&results2), sizeof(struct region_record), sort_regions, (void *)&sort_column);
+    }
+
+    if (file_name) {
+	print_cmd_args(output, orig_argc, orig_argv);
+    }
+
+    bu_vls_printf(output, "List length: %d\n", BU_PTBL_LEN(&results2) - ignored_cnt);
+    bu_vls_printf(output, "%-*s %-*s %-*s %-*s %s\n",
+		  region_id_len_max + 1, "ID",
+		  material_id_len_max + 1, "MAT",
+		  los_len_max, "LOS",
+		  obj_len_max,  "REGION",
+		  "PARENT");
+    start = 0; end = BU_PTBL_LEN(&results2); incr = 1;
+    if (descending_sort_flag) {
+	start = end - 1; end = -1; incr = -1;
+    }
+    for (i = start; i != end; i += incr) {
+	if (regions[i].ignore) { continue; }
+	bu_vls_printf(output, "%-*s %-*s %-*s %-*s %s\n",
+		      region_id_len_max + 1, regions[i].region_id,
+		      material_id_len_max + 1, regions[i].material_id,
+		      los_len_max, regions[i].los,
+		      obj_len_max, regions[i].obj_name,
+		      regions[i].obj_parent);
+    }
+    bu_vls_printf(gedp->ged_result_str, "Done.\n");
+
+    if (file_name) {
+	bu_vls_fwrite(outfile, output);
+	fclose(outfile);
+    }
+
+    bu_free(regions, "ged_lc");
+
+    return GED_OK;
+}
+
+/*
+ * Local Variables:
+ * tab-width: 8
+ * mode: C
+ * indent-tabs-mode: t
+ * c-file-style: "stroustrup"
+ * End:
+ * ex: shiftwidth=4 tabstop=8
+ */
Index: src/libged/CMakeLists.txt
===================================================================
--- src/libged/CMakeLists.txt	(revision 63610)
+++ src/libged/CMakeLists.txt	(working copy)
@@ -138,6 +138,7 @@
   killrefs.c
   killtree.c
   label.c
+  lc.c
   list.c
   loadview.c
   lod.c
Index: src/libtclcad/tclcad_obj.c
===================================================================
--- src/libtclcad/tclcad_obj.c	(revision 63610)
+++ src/libtclcad/tclcad_obj.c	(working copy)
@@ -1131,6 +1131,7 @@
     {"killrefs",	(char *)0, TO_UNLIMITED, to_pass_through_and_refresh_func, ged_killrefs},
     {"killtree",	(char *)0, TO_UNLIMITED, to_pass_through_and_refresh_func, ged_killtree},
     {"l",	(char *)0, TO_UNLIMITED, to_pass_through_func, ged_list},
+    {"lc",      "[-d|-s|-r] [-z] [-0|-1|-2|-3|-4|-5] [-f {FileName}] {GroupName}", TO_UNLIMITED, to_pass_through_func, ged_lc},
     {"light",	"[0|1]", TO_UNLIMITED, to_light, GED_FUNC_PTR_NULL},
     {"list_views",	(char *)0, TO_UNLIMITED, to_list_views, GED_FUNC_PTR_NULL},
     {"listen",	"[port]", TO_UNLIMITED, to_listen, GED_FUNC_PTR_NULL},
Index: src/mged/setup.c
===================================================================
--- src/mged/setup.c	(revision 63610)
+++ src/mged/setup.c	(working copy)
@@ -196,6 +196,7 @@
     {"l", cmd_ged_info_wrapper, ged_list},
     {"l_muves", f_l_muves, GED_FUNC_PTR_NULL},
     {"labelvert", f_labelvert, GED_FUNC_PTR_NULL},
+    {"lc", cmd_ged_plain_wrapper, ged_lc},
     {"left", f_bv_left, GED_FUNC_PTR_NULL},
     {"listeval", cmd_ged_plain_wrapper, ged_pathsum},
     {"lm", cmd_lm, GED_FUNC_PTR_NULL},
Index: src/tclscripts/archer/ArcherCore.tcl
===================================================================
--- src/tclscripts/archer/ArcherCore.tcl	(revision 63610)
+++ src/tclscripts/archer/ArcherCore.tcl	(working copy)
@@ -248,6 +248,7 @@
 	method killtree            {args}
 	method l                   {args}
 	method ls                  {args}
+	method lc                  {args}
 	method make		   {args}
 	method make_name           {args}
 	method make_pnts           {args}
@@ -578,7 +579,7 @@
 	    delete draw e E edarb edcodes edcolor edcomb edit edmater d erase ev exists \
 	    exit facetize fracture freezeGUI g get graph group hide human i igraph \
 	    importFg4Section in inside item kill killall killrefs \
-	    killtree l ls make make_name make_pnts man mater mirror move \
+	    killtree l lc ls make make_name make_pnts man mater mirror move \
 	    move_arb_edge move_arb_face mv mvall nmg_collapse \
 	    nmg_simplify ocenter opendb orotate oscale otranslate p q \
 	    quit packTree prefix protate pscale ptranslate pull push put \
@@ -6563,6 +6564,9 @@
 ::itcl::body ArcherCore::l {args} {
     eval gedWrapper l 1 0 0 0 $args
 }
+::itcl::body ArcherCore::lc {args} {
+    eval gedWrapper lc 1 0 0 0 $args
+}
 ::itcl::body ArcherCore::ls {args} {
     eval gedWrapper ls 1 0 0 0 $args
 }
Index: src/tclscripts/lib/Ged.tcl
===================================================================
--- src/tclscripts/lib/Ged.tcl	(revision 63610)
+++ src/tclscripts/lib/Ged.tcl	(working copy)
@@ -283,6 +283,7 @@
 	method killtree {args}
 	method l {args}
 	method lastMouseRayPos {}
+	method lc {args}
 	method light {args}
 	method light_all {args}
 	method list_views {args}
@@ -2016,6 +2017,10 @@
     return $mLastMouseRayPos
 }
 
+::itcl::body cadwidgets::Ged::lc {args} {
+    eval $mGed lc $args
+}
+
 ::itcl::body cadwidgets::Ged::light {args} {
     eval $mGed light $itk_component($itk_option(-pane)) $args
 }
Index: include/ged.h
===================================================================
--- include/ged.h	(revision 63610)
+++ include/ged.h	(working copy)
@@ -1170,6 +1170,11 @@
 GED_EXPORT extern int ged_killtree(struct ged *gedp, int argc, const char *argv[]);
 
 /**
+ * List attributes of regions within a group/combination.
+ */
+GED_EXPORT extern int ged_lc(struct ged *gedp, int argc, const char *argv[]);
+
+/**
  * List object information, verbose.
  */
 GED_EXPORT extern int ged_list(struct ged *gedp, int argc, const char *argv[]);
