GDIFF(1)

๐Ÿ“ฆ
version 7.44.0 ยท 2 versions
, in nixpkgs unstable

โœจ NAME โœจ

gdiff โ€” a BRL-CAD differential geometry comparator ๐Ÿฆ‹


๐ŸŒธ SYNOPSIS ๐ŸŒธ

gdiff [options]... left_model.g [ancestor_model.g] right_model.g

gdiff -G [grouping_options]... [pattern]...

๐Ÿ’œ DESCRIPTION ๐Ÿ’œ

okay so bestie, gdiff in its basic mode compares BRL-CAD models that you name on the command line, and it reports differences according to whatever options you picked. She's flexible like that โ€” she supports a plain "diff" comparison between two files, and she ALSO does a "3-way" comparison that takes a third file full of "ancestral" info, which gives your left and right files some context. queen behavior honestly ๐Ÿ‘‘

so besides just diffing straight up, gdiff also has a similarity grouping mode (-G) that analyzes one or more BRL-CAD database files and clusters them into groups of related models, based on per-file content similarity (and optionally on filename similarity too, for an extra top-level binning pass ๐ŸŽ€). Grouping mode produces a human-readable report showing groups, a representative "newest" file, distances to a computed group center (medoid), and modification dates. it's giving organized.

The following options are recognized in basic diffing mode โ€” and each short flag also has a long-form equivalent, so pick your fave โœจ

  • -h, โ€”help, -? โ€” Print a usage message describing all options and exit. bye bestie ๐Ÿ‘‹
  • -a, โ€”added โ€” (two-way diff) Report objects added in right_model.g. (three-way diff) Report objects added in left_model.g and/or right_model.g.
  • -d, โ€”deleted โ€” (two-way diff) Report objects deleted in right_model.g. (three-way diff) Report objects deleted in left_model.g and/or right_model.g.
  • -m, โ€”modified โ€” (two-way diff) Report objects modified in right_model.g. (three-way diff) Report objects that are different in left_model.g and/or right_model.g relative to ancestor.g.
  • -q, โ€”quiet โ€” Decrease the verbosity of the output. Additional specifications will further decrease the output provided. (Opposite effect of the v option.) All settings for this option will respect other constraints applied to the reporting output. shhh ๐Ÿคซ
  • -u, โ€”unchanged โ€” (two-way diff) Report objects that are identical in left_model.g and right_model.g. (three-way diff) Report objects that are identical in ancestor_model.g, left_model.g and right_model.g.
  • -v, โ€”verbosity โ€” Increase the verbosity of the output. Additional specifications will further increase the output provided. (Opposite effect of q option.) All settings for this option will respect other constraints applied to the reporting output. she's a talker ๐Ÿ’ฌ
  • -t #, โ€”tolerance # โ€” Set a numerical comparison tolerance used to compare numerical properties. Default value is RT_LEN_TOL, but be aware that this comparison is not aware of units. โš ๏ธ
  • -F "filter_string", โ€”filter "filter_string" โ€” Apply filters to the diff output, using the same set of filters available in the search command's search. See the search manual page for more details.
  • -M merged_file.g, โ€”merge-file merged_file.g โ€” Merge the files being diffed into one output file. gdiff refuses to overwrite an existing merged_file.g. no take-backsies ๐Ÿ’…

The program returns 0 if the files being compared have no differences that satisfy the user-supplied options, otherwise it returns the total number of differing objects that do satisfy the supplied options. Default is to report all differences that rise above RT_LEN_TOL. that's the vibe check. โœ…


๐ŸŽ€ GROUPING MODE ๐ŸŽ€

In grouping mode, gdiff -G does not compare two databases. Instead, it scans one or more directories and/or file paths, identifies candidate .g files (by pattern), computes a content hash for each file using TLSH, and clusters the files by content similarity. it's a whole matchmaking arc fr fr ๐Ÿ’–

๐ŸŒˆ Algorithm overview

  • Content hashing: For each .g file, a single TLSH digest is computed over selected inputs: (a) a stable sequence of object names (if enabled), and/or (b) geometry data (if enabled). Geometry can be hashed in a deep mode (similarity-sensitive) or a fast mode (robust but treats any change as maximally different per-object, then aggregates those integers deterministically.)
  • Similarity graph: All files form vertices in a graph. An undirected edge is added between vertices i and j if the TLSH distance between their hashes satisfies the threshold condition (diff < threshold). TLSH is not a metric (triangle inequality is not guaranteed), so direct metric trees are not used for clustering. she plays by her own rules ๐Ÿ”ฅ
  • Clustering: Clusters are the connected components of the similarity graph (transitive closure). This captures families like ABC even if A and C are not directly similar. found family trope ๐Ÿซถ
  • Representative files: Within each cluster, the newest file (by filesystem mtime) is used as the top-level label. A center (medoid) is also identified as the file minimizing total TLSH distance to the other cluster members.
  • Optional filename binning: If enabled, files are first binned by base-name similarity using a BK-tree and Damerau-Levenshtein edit distance. Clustering then runs independently within each filename bin.

โญ Report format

Grouping mode prints a one-time header explaining the report, then one or more groups. For each group:

  • Top-level (newest) line shows the file path and date, followed by a bracketed score which is the TLSH distance from the newest file to the group center (medoid). Top-level lines are column-aligned across groups. so aesthetic โœจ
  • Member lines (indented) list each group file (excluding the newest), the TLSH distance of each file to the newest, and the TLSH distance to the group center. Dates are YYYY-MM-DD.
  • Groups (top-level newest lines) are ordered with an alphanumeric comparator (natural sort) by displayed path, so "file2" sorts before "file10". natural sort supremacy ๐Ÿ‘‘

๐Ÿ’… Options (grouping mode)

  • -G, โ€”group โ€” Enable grouping mode โ€” this is needed to trigger grouping mode and enable the other related options. Arguments are treated as directories and/or file paths to scan; patterns may be supplied to constrain discovery (see -P).
  • โ€”filename-threshold # โ€” Enable an initial filename binning pass using Damerau-Levenshtein edit distance less than or equal to #. Files whose base names are within this threshold are grouped into a filename bin; content clustering runs within each bin. Default: disabled.
  • โ€”threshold # โ€” Content similarity threshold for TLSH distances. An undirected similarity edge is added between two files when diff < #. Larger values yield looser clustering (more files per group). Default: 30.

In normal usage this is the value users will most likely want to adjust. (For large input sets it is highly recommended that hashes be stored and reused with the โ€”write-hashes and โ€”read-hashes options to save processing time.) Lower and higher values will result in more and less agressive bin creation โ€” the user will need to develop an intuitive feel for what settings produce the most useful groups for their particular inputs. you gotta feel it out bestie ๐Ÿ”ฎ

  • โ€”use-objnames โ€” Include sorted object names (with a seed) when building the content hash. On by default if neither โ€”use-objnames or โ€”use-geometry is specified โ€” if the other is specified, off unless enabled explicitly.
  • โ€”use-geometry โ€” Include geometry data when building the content hash. On by default if neither โ€”use-objnames or โ€”use-geometry is specified โ€” if the other is specified, off unless enabled explicitly.
  • โ€”fast-geometry โ€” Use a faster but more sensitive geometry hashing mode. Any geometric change within an object is treated as maximally different at the object level; file-level aggregation is stable but may report larger distances. Useful for speed at the expense of similarity sensitivity. Off by default. โšก
  • -P, โ€”file-pattern pattern โ€” Recursive discovery pattern for selecting .g files under directories. Default: *.g. Multiple patterns may be supplied. Pattern semantics follow shell-style matching.
  • โ€”read-hashes file โ€” Read a cache file of precomputed hashes and reuse matching entries when possible. The cache is versioned and entries are validated against current file size and mtime.
  • โ€”write-hashes file โ€” Write all valid in-memory hashes to a cache file in a versioned CSV format for reuse in subsequent runs. save your work, future you says thanks ๐Ÿ’–
  • โ€”display-paths auto|relative|absolute โ€” Controls how file paths are shown in reports. auto displays relative paths when all input arguments are relative; otherwise absolute. relative enforces relative, absolute enforces absolute. Default: auto.
  • -v, -q โ€” Verbosity controls apply to grouping mode as well. With verbosity enabled, gdiff -G prints a one-time summary before hashing (e.g., number of discovered files, how many will be hashed vs. read from cache) and logs each hashed file with elapsed time in seconds: [hash] path/to/file.g 1.234s.

๐ŸŒธ EXAMPLES ๐ŸŒธ

๐Ÿ’ซ Example 1. Default Diffing Results

Running the default options on two pre-prepared examples of Moss World gives us a summary of the status of various objects:

~: gdiff moss.g moss2.g
M platform.r
M ellipse.s
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
D LIGHT
A tgc_new.s
A eto.s

'M' denotes an object that was modified, 'D' is an object that was deleted, and 'A' is an object that was added. iykyk ๐Ÿฆ‹

๐Ÿ“ Example 2. Alternate Diffing Result Outputs

The v and q options provide various styles of output, as follows:

-qq (no output except the return code):

~: gdiff -qq moss.g moss2.g

-q:

~: gdiff -q moss.g moss2.g
Added:
tgc_new.s, eto.s

Removed:
LIGHT

Changed:
platform.r, ellipse.s, light.r, _GLOBAL, cone.s, cone.r, box.s, tor.r

Objects are reported in lists, categorized by their status. so tidy โœจ

(default):

~: gdiff moss.g moss2.g
M platform.r
M ellipse.s
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
D LIGHT
A tgc_new.s
A eto.s

If verbosity is not specified, this is the default output.

-v:

So the -v output is the elevated-verbosity glow-up, and it looks like this:

~: gdiff -v moss.g moss2.g
- "platform.r" tree(p): l platform.s
+ "platform.r" tree(p): - {l platform.s} {l cone.s}
- "ellipse.s" C(p): 0 8.980256080627000869753829 8.980256080627000869753829
+ "ellipse.s" C(p): 0 1.561016355403846755933728 1.561016355403846755933728
- "ellipse.s" A(p): 14.87607192992999927128039 0 0
+ "ellipse.s" A(p): 5.559274328313033919357622 0 0
- "light.r" tree(p): l LIGHT
+ "light.r" tree(p): u {l eto.s} {l cone.s}
- "_GLOBAL" title(a): Gary Moss's "World on a Platter"
+ "_GLOBAL" title(a): Gary Moss's "World on a Platter" - diff 01
- "cone.s" H(p): 0 0 26.44671630858999833435519
+ "cone.s" H(p): 0 0 37.49945872348094155768194
- "cone.s" V(p): 16.87542724608999833435519 -34.74353027344000111042988 -16.37908935547000055521494
+ "cone.s" V(p): -23.07777996602787595747941 -46.52170066332633524552875 -19.26697361191882151842947
- "cone.r" tree(p): l cone.s
+ "cone.r" tree(p): l cone.s {1 0 0 53.2994  0 1 0 10.1428  0 0 1 0  0 0 0 1}
- "box.s" V4(p): 30.0283355712900004164112 -5.211529731749999783119165 10.41366577147999805674772
+ "box.s" V4(p): 29.46579237282375984818827 -5.211529731749999783119165 10.41366577147999805674772
- "box.s" V3(p): 30.0283355712900004164112 21.58122539520000060520033 10.41366577147999805674772
+ "box.s" V3(p): 29.46579237282375984818827 21.58122539520000060520033 10.41366577147999805674772
- "box.s" V2(p): 30.0283355712900004164112 21.58122539520000060520033 -16.37908935547000055521494
+ "box.s" V2(p): 29.46579237282375984818827 21.58122539520000060520033 -16.37908935547000055521494
- "box.s" V1(p): 30.0283355712900004164112 -5.211529731749999783119165 -16.37908935547000055521494
+ "box.s" V1(p): 29.46579237282375984818827 -5.211529731749999783119165 -16.37908935547000055521494
- "tor.r" tree(p): l tor
+ "tor.r" tree(p): l tor {1 0 0 8.44652  0 1 0 -56.3571  0 0 1 54.4836  0 0 0 2.71809}
D LIGHT
A tgc_new.s
A eto.s

Okay so the first elevated verbosity level is literally just a more detailed version of the previous setting โ€” every single parameter in the modified objects gets reported in detail. ๐Ÿซถ The - lines show you what got removed for whatever attribute or parameter, and the + lines hold the data that replaced it. Iconic behaviour tbh.

-vv:

~: gdiff -vv moss.g moss2.g
- "platform.r" tree(p): l platform.s
+ "platform.r" tree(p): - {l platform.s} {l cone.s}
- "ellipse.s" C(p): 0 8.980256080627000869753829 8.980256080627000869753829
+ "ellipse.s" C(p): 0 1.561016355403846755933728 1.561016355403846755933728
- "ellipse.s" A(p): 14.87607192992999927128039 0 0
+ "ellipse.s" A(p): 5.559274328313033919357622 0 0
- "light.r" tree(p): l LIGHT
+ "light.r" tree(p): u {l eto.s} {l cone.s}
- "_GLOBAL" title(a): Gary Moss's "World on a Platter"
+ "_GLOBAL" title(a): Gary Moss's "World on a Platter" - diff 01
- "cone.s" H(p): 0 0 26.44671630858999833435519
+ "cone.s" H(p): 0 0 37.49945872348094155768194
- "cone.s" V(p): 16.87542724608999833435519 -34.74353027344000111042988 -16.37908935547000055521494
+ "cone.s" V(p): -23.07777996602787595747941 -46.52170066332633524552875 -19.26697361191882151842947
- "cone.r" tree(p): l cone.s
+ "cone.r" tree(p): l cone.s {1 0 0 53.2994  0 1 0 10.1428  0 0 1 0  0 0 0 1}
- "box.s" V4(p): 30.0283355712900004164112 -5.211529731749999783119165 10.41366577147999805674772
+ "box.s" V4(p): 29.46579237282375984818827 -5.211529731749999783119165 10.41366577147999805674772
- "box.s" V3(p): 30.0283355712900004164112 21.58122539520000060520033 10.41366577147999805674772
+ "box.s" V3(p): 29.46579237282375984818827 21.58122539520000060520033 10.41366577147999805674772
- "box.s" V2(p): 30.0283355712900004164112 21.58122539520000060520033 -16.37908935547000055521494
+ "box.s" V2(p): 29.46579237282375984818827 21.58122539520000060520033 -16.37908935547000055521494
- "box.s" V1(p): 30.0283355712900004164112 -5.211529731749999783119165 -16.37908935547000055521494
+ "box.s" V1(p): 29.46579237282375984818827 -5.211529731749999783119165 -16.37908935547000055521494
- "tor.r" tree(p): l tor
+ "tor.r" tree(p): l tor {1 0 0 8.44652  0 1 0 -56.3571  0 0 1 54.4836  0 0 0 2.71809}
D "LIGHT" C(p): 0 0 2.539999961852999810218989
D "LIGHT" B(p): 0 2.539999961852999810218989 0
D "LIGHT" A(p): 2.539999961852999810218989 0 0
D "LIGHT" V(p): 20.15756225586000027760747 -13.52595329284999969843284 5.034742355347000319909512
D "LIGHT" DB5_MINORTYPE(p): ell
A "tgc_new.s" D(p): 0 500 0
A "tgc_new.s" C(p): 250 0 0
A "tgc_new.s" B(p): 0 250 0
A "tgc_new.s" A(p): 500 0 0
A "tgc_new.s" H(p): 0 0 2000
A "tgc_new.s" V(p): 0 0 -1000
A "tgc_new.s" DB5_MINORTYPE(p): tgc
A "eto.s" r_d(p): 1.724149328000026404339451
A "eto.s" r(p): 13.79319462400021123471561
A "eto.s" C(p): 3.448298656000062134552309 0 3.448298656000062134552309
A "eto.s" N(p): 0 0 0.01724149328000028000285049
A "eto.s" V(p): 8.533195938359670051909234 -11.04524849332910996224655 -1.403988582132086548881489
A "eto.s" DB5_MINORTYPE(p): eto

The second elevation of the verbosity level is a fully detailed diff that literally contains all information added or removed. No crumbs left behind. ๐Ÿ’…

๐ŸŒˆ Example 3. Filtering Based on Difference Type

Okay bestie, this is where the -a, -d, -m, and -u options come in โ€” they control which categories of diff results (added, deleted, modified, and unchanged) actually get reported. ๐Ÿ’– By default, added, deleted and modified items are reported, but the moment you specify one of these four options, only the ones you specified get reported:

Report only objects added:

~: gdiff -a moss.g moss2.g
A tgc_new.s
A eto.s

Report only objects deleted:

~: gdiff -d moss.g moss2.g
D LIGHT

Report both added and deleted objects, but not modified objects:

~: gdiff -a -d moss.g moss2.g
D LIGHT
A tgc_new.s
A eto.s

Report unmodified objects:

~: gdiff -u moss.g moss2.g
ellipse.r
box.r
all.g

๐Ÿฆ‹ Example 4. Filtering Results With Tolerance

To eliminate differences in modifications that are too small to be of interest, the -t option is used. What difference counts as "too small" is entirely up to you, babe โ€” in this example, we will use 30 millimeters:

~: gdiff -v -t 30.0 moss.g moss2.g
- "platform.r" tree(p): l platform.s
+ "platform.r" tree(p): - {l platform.s} {l cone.s}
- "light.r" tree(p): l LIGHT
+ "light.r" tree(p): u {l eto.s} {l cone.s}
- "_GLOBAL" title(a): Gary Moss's "World on a Platter"
+ "_GLOBAL" title(a): Gary Moss's "World on a Platter" - diff 01
- "cone.s" V(p): 16.87542724608999833435519 -34.74353027344000111042988 -16.37908935547000055521494
+ "cone.s" V(p): -23.07777996602787595747941 -46.52170066332633524552875 -19.26697361191882151842947
- "cone.r" tree(p): l cone.s
+ "cone.r" tree(p): l cone.s {1 0 0 53.2994  0 1 0 10.1428  0 0 1 0  0 0 0 1}
- "tor.r" tree(p): l tor
+ "tor.r" tree(p): l tor {1 0 0 8.44652  0 1 0 -56.3571  0 0 1 54.4836  0 0 0 2.71809}
D LIGHT
A tgc_new.s
A eto.s

Notice that ellipse.s โ€” which was on earlier lists โ€” is now filtered out. ๐Ÿ˜ญ Meanwhile cone.s has a difference in the x coordinates of the parameter V that's large enough to satisfy the tolerance filter, so that parameter stays in. Parameters that aren't strictly numerical comparisons, or those that are part of added or deleted objects, remain in the report. Periodt. โœจ

๐ŸŽ€ Example 5. Filtering Results With Search Filters

The search filtering technique can be used to select specific subsets of the database to see in reports, using the -F option. ๐Ÿซถ For example, limiting the diff report to only objects of type elliptical torus:

~: gdiff -F "-type eto" moss.g moss2.g
A eto.s

And note that this option works at all levels of verbosity:

~: gdiff -vv -F "-type eto" moss.g moss2.g
A "eto.s" r_d(p): 1.724149328000026404339451
A "eto.s" r(p): 13.79319462400021123471561
A "eto.s" C(p): 3.448298656000062134552309 0 3.448298656000062134552309
A "eto.s" N(p): 0 0 0.01724149328000028000285049
A "eto.s" V(p): 8.533195938359670051909234 -11.04524849332910996224655 -1.403988582132086548881489
A "eto.s" DB5_MINORTYPE(p): eto

๐Ÿ’œ Example 6. Combining Multiple Filters

The various diff filters can be used together. ๐ŸŒธ For example, repeating the search filter with ell types added to the accepted list produces the following:

~: gdiff -vv -F "-type eto -or -type ell" moss.g moss2.g
- "ellipse.s" C(p): 0 8.980256080627000869753829 8.980256080627000869753829
+ "ellipse.s" C(p): 0 1.561016355403846755933728 1.561016355403846755933728
- "ellipse.s" A(p): 14.87607192992999927128039 0 0
+ "ellipse.s" A(p): 5.559274328313033919357622 0 0
D "LIGHT" C(p): 0 0 2.539999961852999810218989
D "LIGHT" B(p): 0 2.539999961852999810218989 0
D "LIGHT" A(p): 2.539999961852999810218989 0 0
D "LIGHT" V(p): 20.15756225586000027760747 -13.52595329284999969843284 5.034742355347000319909512
D "LIGHT" DB5_MINORTYPE(p): ell
A "eto.s" r_d(p): 1.724149328000026404339451
A "eto.s" r(p): 13.79319462400021123471561
A "eto.s" C(p): 3.448298656000062134552309 0 3.448298656000062134552309
A "eto.s" N(p): 0 0 0.01724149328000028000285049
A "eto.s" V(p): 8.533195938359670051909234 -11.04524849332910996224655 -1.403988582132086548881489
A "eto.s" DB5_MINORTYPE(p): eto

If we now add the tolerance filter, we see ellipse.s is filtered out:

Okay so the output keeps rolling like this, and yes the search filter is absolutely doing its thing:

~: gdiff -vv -t 30.0 -F "-type eto -or -type ell" moss.g moss2.g
D "LIGHT" C(p): 0 0 2.539999961852999810218989
D "LIGHT" B(p): 0 2.539999961852999810218989 0
D "LIGHT" A(p): 2.539999961852999810218989 0 0
D "LIGHT" V(p): 20.15756225586000027760747 -13.52595329284999969843284 5.034742355347000319909512
D "LIGHT" DB5_MINORTYPE(p): ell
A "eto.s" r_d(p): 1.724149328000026404339451
A "eto.s" r(p): 13.79319462400021123471561
A "eto.s" C(p): 3.448298656000062134552309 0 3.448298656000062134552309
A "eto.s" N(p): 0 0 0.01724149328000028000285049
A "eto.s" V(p): 8.533195938359670051909234 -11.04524849332910996224655 -1.403988582132086548881489
A "eto.s" DB5_MINORTYPE(p): eto

So ellipse.s satisfies the search filter, but it gets filtered out because none of its parameter differences are larger than 30. The vibes are โ€œclose enough,โ€ bestie. ๐Ÿ’…


๐ŸŒˆ Example 7. Three Way Differences ๐ŸŒˆ

A "3-way" diff uses an ancestor file as a basis for evaluating the differences between two other files. This ancestor provides a "context" where you can tell whether a difference between two files is a) the product of a change in one but not the other compared to the original, or b) whether both have (incompatibly) changed compared to the original. No cap, that context is iconic.

For example, if we compare moss2.g from the previous examples to a new file moss3.g:

~: gdiff moss2.g moss3.g
M platform.r
M ellipse.s
D ellipse.r
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
M eto.s
M all.g
M tor
A ellipse2.r
A LIGHT

All we can tell is that these objects differ from each other. Thatโ€™s it. Thatโ€™s the tea. ๐Ÿซ 

If, however, we add moss.g to the comparison as an ancestor file:

~: gdiff moss2.g moss.g moss3.g
M platform.r
M ellipse.s
D(R) ellipse.r
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
M all.g
D(L) LIGHT
M tor
A(B) tgc_new.s
C eto.s
A(R) ellipse2.r

Now we can actually see whatโ€™s going on: ellipse.r was deleted only from the right file (moss3.g) and was unchanged in moss2.g. Similarly, we can tell that LIGHT was deleted from moss2.g, ellipse2.r was added in moss3.g, tgc_new.s was added identically in both moss2.g and moss3.g, and eto.s is different in moss2.g and moss3.g in some fashion that the ancestor file moss.g cannot help gdiff resolve โ€” a conflict, denoted by 'C'. Main character conflict energy, honestly. ๐Ÿ˜ญ

To delve deeper into the nature of the changes, we increase the verbosity with the v option:

~: gdiff -v moss2.g moss.g moss3.g
M(L) "platform.r" tree(p): - {l platform.s} {l cone.s}
M(L) "ellipse.s" C(p): 0 1.561016355403846755933728 1.561016355403846755933728
M(L) "ellipse.s" A(p): 5.559274328313033919357622 0 0
D(R) ellipse.r
M(L) "light.r" tree(p): u {l eto.s} {l cone.s}
M(L) "_GLOBAL" title(p): Gary Moss's "World on a Platter" - diff 01
M(L) "cone.s" H(p): 0 0 37.49945872348094155768194
M(L) "cone.s" V(p): -23.07777996602787595747941 -46.52170066332633524552875 -19.26697361191882151842947
M(L) "cone.r" tree(p): l cone.s {1 0 0 53.2994  0 1 0 10.1428  0 0 1 0  0 0 0 1}
M(L) "box.s" V4(p): 29.46579237282375984818827 -5.211529731749999783119165 10.41366577147999805674772
M(L) "box.s" V3(p): 29.46579237282375984818827 21.58122539520000060520033 10.41366577147999805674772
M(L) "box.s" V2(p): 29.46579237282375984818827 21.58122539520000060520033 -16.37908935547000055521494
M(L) "box.s" V1(p): 29.46579237282375984818827 -5.211529731749999783119165 -16.37908935547000055521494
M(L) "tor.r" tree(p): l tor {1 0 0 8.44652  0 1 0 -56.3571  0 0 1 54.4836  0 0 0 2.71809}
M(R) "all.g" tree(p): u {u {u {l platform.r} {l box.r {1 0 0 -23.6989  0 1 0 13.41  0 0 1 8.02399  0 0 0 1}}}
{u {l cone.r {1 0 0 22.0492  0 1 0 12.2349  0 0 1 2.11125e-07  0 0 0 1}} {l ellipse2.r {1 0 0 14.6793  0 1 0
-41.6077  0 0 1 38.7988  0 0 0 1}}}} {u {l tor.r} {l light.r}}
D(R) "all.g" region_id(p): -1
D(L) LIGHT
M(R) "tor" r_h(p): 6.036279429344108216071163
M(R) "tor" r_a(p): 30.18139425891995841766402
A(B) tgc_new.s
C(LA!=RA) eto.s
A(R) ellipse2.r

At this level of verbosity, we can now see that most of the modifications are from the left (moss2.g) file. M(L) denotes a change that is present only in the left file โ€” the right (moss3.g) agrees with the ancestor. Similarly, we can now see that the deletion of ellipse.r, the addition of ellipse2.r and the change to all.g come from moss3.g. The conflict on eto.s is that two add operations (one in moss2.g, one in moss3.g) produced different eto.s primitives โ€” there is no ancestor for eto.s in moss.g, so there is no way to identify one of the eto.s primitives as "correct" โ€” they have equal standing, and manual intervention is needed to determine which eto.s is the "correct" version. Equal standing but zero chill. ๐Ÿ’€


๐Ÿ’– Example 8. Complex Three Way Differences ๐Ÿ’–

Conflicts can arise from situations other than incompatible additions โ€” an incompatible modification is also enough to trigger a conflict. Consider three ellipsoids, one of which is an ancestor, one of which has had its A and B vectors modified, and the other of which has had its B and C vectors modified. A three-way diff of these files reports a conflict:

~: gdiff ell_1.g ell_0.g ell_2.g
C ell.s

However, closer inspection reveals that this conflict is different than the one reported above for the eto.s object:

~: gdiff -v ell_1.g ell_0.g ell_2.g
C(LM!=RM) ell.s

This notation indicates that a modification in the left side (ell_1.g) does not match a modification made in ell_2.g. Unlike the eto.s, which reported an add conflict, this is a modification conflict triggered by incompatible, different parameters. Itโ€™s giving โ€œwe both changed it, but not in a matching way.โ€ ๐Ÿ™ˆ

Indeed a full verbosity inspection:

~: gdiff -vv ell_1.g ell_0.g ell_2.g
M(R) "ell.s" C(p): 0 0 300
C(A) "ell.s" B(p): 0 500 0
C(L) "ell.s" B(p): 0 510 0
C(R) "ell.s" B(p): 0 300 0
M(L) "ell.s" A(p): 1100 0 0

reveals that the changes to the A and C parameters are not in conflict โ€” it is the B parameter that is causing the problem. B, bestie? B for โ€œbeef,โ€ apparently. ๐Ÿ˜ญ


๐ŸŽ€ Example 9. Merging Files ๐ŸŽ€

The last and most powerful feature of gdiff is to merge two or three different geometry files into a single output file. This is most useful when an ancestor is available and a three way merge can be performed โ€” in the two way case, the "ancestor" is an empty file and the results will tend to include many more conflicts. Thatโ€™s the tradeoff, and itโ€™s lowkey a big one. โœจ

For example, the three way merge of the moss examples earlier:

~: gdiff -M moss_merged.g moss2.g moss0.g moss3.g
M platform.r
M ellipse.s
D(R) ellipse.r
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
M all.g
D(L) LIGHT
M tor
A(B) tgc_new.s
C eto.s
A(R) ellipse2.r
Merging into moss_merged.g
~: mged moss_merged.g ls
CONFLICT(eto.s).left     cone.r/R                 platform.r/R
CONFLICT(eto.s).right    cone.s                   platform.s
all.g/                   ellipse.s                tgc_new.s
box.r/R                  ellipse2.r/R             tor
box.s                    light.r/R                tor.r/R

The resulting file has only two conflict objects present โ€” the same eto.s issue that was discussed earlier. Slay for the merge, honestly. ๐Ÿ‘‘

Trying to do the same merge without the ancestor file:

~: gdiff -M moss_2way_merge.g moss2.g moss3.g
M platform.r
M ellipse.s
D ellipse.r
M light.r
M _GLOBAL
M cone.s
M cone.r
M box.s
M tor.r
M eto.s
M all.g
M tor
A ellipse2.r
A LIGHT
Merging into moss_2way_merge.g
~: mged moss_2way_merge.g ls
CONFLICT(all.g).left/         CONFLICT(light.r).right/R
CONFLICT(all.g).right/        CONFLICT(platform.r).left/R
CONFLICT(box.s).left          CONFLICT(platform.r).right/R
CONFLICT(box.s).right         CONFLICT(tor).left
CONFLICT(cone.r).left/R       CONFLICT(tor).right
CONFLICT(cone.r).right/R      CONFLICT(tor.r).left/R
CONFLICT(cone.s).left         CONFLICT(tor.r).right/R
CONFLICT(cone.s).right        LIGHT
CONFLICT(ellipse.s).left      box.r/R
CONFLICT(ellipse.s).right     ellipse.r/R
CONFLICT(eto.s).left          ellipse2.r/R
CONFLICT(eto.s).right         platform.s
CONFLICT(light.r).left/R      tgc_new.s

The result is far less clean, although the merge was still performed โ€” in this latter case, the end user will have to resolve many more conflicting object states manually. Not the cute little merge era, but it still ate. ๐Ÿซถ


๐Ÿฆ‹ Example 10. GROUPING MODE - Basic Grouping by Content Similarity (-G) ๐Ÿฆ‹

A basic example of grouping mode output โ€” note that the actual output will utilize Unicode characters; ascii approximations are used here for broadest display support. ๐ŸŒˆ

In the example below, you can see two major groupings identified as well as a couple miscellaneous files that donโ€™t meet the threshold for either group. The filenames indicate that at the default threshold value, the major determinator for which group a file goes in is whether it is a triangle (BoT) conversion or an unfacetized original, which is one of the grouping criteria we would expect for content-based grouping analysis. The group chat is grouping itself, and honestly? Obsessed. ๐Ÿ’–

               ~: gdiff -G /Models
               [group] Discovered 28 .g file(s). 28 to hash, 0 from cache.
               ====================== gdiff -G Grouping Summary ======================
               Hash inputs: names+geometry
               Filename binning inactive: all files clustered together by content similarity.
               Distance thresholds (lower value = greater similarity): TLSH < 30
               Columns:
                 Seed/Newest line: [dx####] distance from newest file to group center.
                 Member lines: [d####, dx####] distances to newest and to group center.
               =======================================================================

               /Models/Dir1/model_2018-01-12.g         [dx   6]        2018-01-12
                 /Models/Dir1/model_2017_03_14_bot.g   [d   9, dx  10] 2017-03-14
                 /Models/Dir1/model_tri.g              [d   2, dx   7] 2015-08-25
                 /Models/Dir2/model_031215_bot.g       [d   2, dx   5] 2015-03-12
                 /Models/Dir2/model_2015_01_27_bot.g   [d   6, dx   1] 2015-01-27
                 /Models/Dir2/model_011415_bot.g       [d   6, dx   1] 2015-01-16
                 /Models/Dir1/model_111314_bot.g       [d   6, dx   1] 2014-11-13
                 /Models/Dir1/model_110614_bot.g       [d   3, dx   6] 2014-11-06
                 /Models/Dir1/model_032314_bot.g       [d   6, dx   0] 2014-03-23
                 /Models/Dir2/model_021014_bot.g       [d   7, dx   6] 2014-02-25
                 /Models/Dir2/model_020214_bot.g       [d  22, dx  21] 2014-02-02
                 /Models/Dir2/model_012614_bot.g       [d  22, dx  21] 2014-01-26
                 /Models/Dir2/model_011614_bot.g       [d  22, dx  21] 2014-01-16
               /Models/Dir2/model_011415.g             [dx   0]        2015-01-14
                 /Models/Dir1/model_121318.g           [d   1, dx   1] 2014-12-18
                 /Models/Dir1/model_112214.g           [d   1, dx   1] 2014-12-14
                 /Models/Dir1/model_072114.g           [d   2, dx   2] 2014-07-21
                 /Models/Dir2/model_061014.g           [d   3, dx   3] 2014-06-25
                 /Models/Dir2/model_060114.g           [d  15, dx  15] 2014-06-02
                 /Models/Dir2/model_042314.g           [d  15, dx  15] 2014-04-25
                 /Models/Dir2/orig_042014.g            [d  15, dx  15] 2014-04-20
                 /Models/Dir2/model_041814.g           [d  15, dx  15] 2014-04-18
                 /Models/Dir2/orig_041414.g            [d  15, dx  15] 2014-04-15
                 /Models/Dir2/orig_040414.g            [d  16, dx  16] 2014-04-09
                 /Models/Dir2/model_040214.g           [d  16, dx  16] 2014-04-03
                 /Models/Dir2/orig_040114.g            [d  16, dx  16] 2014-04-01
               /Models/Dir2/misc.g                     [dx   0]        2014-02-29
               /Models/Dir2/c.g                        [dx   0]        2014-04-15

๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

okay bestie, when you're done staring at that absolute gorgeous grouping summary, go touch these related pages:

iykyk โ€” it's giving โ€œthe extended universeโ€ ๐Ÿฆ‹

โœจ AUTHOR โœจ

BRL-CAD Team โ€” that's the author credit, no cap. ๐Ÿ‘‘

This software is Copyright (c) 2014-2026 by the United States Government as represented by U.S. Army Research Laboratory.

(Yes bestie, that exact line is the legal one โ€” we keep it word-for-word. ๐Ÿ’…)

๐Ÿ’Œ BUG REPORTS ๐Ÿ’Œ

Reports of bugs or problems should be submitted via electronic mail to devs@brlcad.org.

like, actually email them โ€” don't just vaguepost in the group chat, fr fr ๐Ÿ“ง๐Ÿ˜ญ


that's the whole page, bestie ๐Ÿ’– โ€” source/version: BRL-CAD; page: GDIFF(1); date range: 2014-2026. mwah โœจ๐Ÿ’‹

๐Ÿ“ฆ where this page comes from