Polyscope

repository·master·Indexed 24 days ago

https://github.com/nmwsharp/polyscope

A lightweight C++/Python viewer and user interface for 3D data such as meshes and point clouds. It provides an interactive 3D GUI to visualize geometric structures and associated scalar or vector quantities with minimal code.

Tokens
12.8K
Snippets
25
Records
43
Agent score
81%

What's inside polyscope

  1. Core design principles of JSON for Modern C++

    master

    The library is designed around three primary goals:

    1. Intuitive syntax: Uses C++ operator overloading to make JSON feel like a first-class data type, similar to how it feels in languages like Python.
    2. Trivial integration: Consists of a single header file (json.hpp) with no dependencies or complex build requirements.
    3. Serious testing: Heavily unit-tested with 100% coverage and verified for memory leaks using Valgrind.

    Note on Performance and Memory:

    • Memory: Each JSON object has an overhead of one pointer and one enumeration element (1 byte). Default types used are std::string for strings, int64_t/uint64_t/double for numbers, std::map for objects, std::vector for arrays, and bool for Booleans. You can customize this by templating the basic_json class.
    • Speed: While there are faster libraries available, this library prioritizes development speed and ease of integration via its single-header design.
  2. How Polyscope structures and quantities work

    master

    Polyscope operates on a paradigm of structures and quantities:

    1. Structures: Geometric objects in the 3D scene, such as a surface mesh or a point cloud.
    2. Quantities: Data associated with a structure, such as a scalar function (e.g., temperature, curvature) or a vector field (e.g., normals, velocity).

    When you register a structure and its quantities, Polyscope automatically handles the visualization boilerplate, including toggling visibility, color-mapping data, adjusting color maps, and enabling 'picking' to query numerical values by clicking in the 3D scene.

  3. Handle unchecked access and assertions in JSON for Modern C++

    master

    The library uses debug assertions to catch errors. You can disable these by defining the NDEBUG preprocessor macro.

    Warning on operator[]: For const objects, operator[] implements unchecked access. If the requested key does not exist, the behavior is undefined (similar to dereferencing a null pointer) and will trigger an assertion failure if assertions are enabled.

    Best Practice: If you are unsure whether a key exists in a JSON object, use the checked access method at() instead of operator[] to avoid undefined behavior.

  4. Use argument groups for validation

    master

    Arguments can be organized into args::Group objects to enforce validation logic. Groups can be nested to create complex validation hierarchies.

    Supported validators for args::Group include:

    • args::Group::Validators::Xor: Exactly one of the flags in the group must be present.
    • args::Group::Validators::AllOrNone: Either all flags in the group must be present, or none at all.
    • args::Group::Validators::AtLeastOne: At least one flag in the group must be present.

    When a ValidationError occurs, you can use parser.Help(std::cerr) to print a formatted help menu that clearly shows the group nesting and validation requirements.

  5. Convert third-party types using adl_serializer

    master

    If you cannot modify the namespace of a type (e.g., boost::optional or std::filesystem::path), you can specialize nlohmann::adl_serializer within the nlohmann namespace.

    namespace nlohmann {
        template <typename T>
        struct adl_serializer<boost::optional<T>> {
            static void to_json(json& j, const boost::optional<T>& opt) {
                if (opt == boost::none) {
                    j = nullptr;
                } else {
                  j = *opt;
                }
            }
    
            static void from_json(const json& j, boost::optional<T>& opt) {
                if (j.is_null()) {
                    opt = boost::none;
                } else {
                    opt = j.get<T>();
                }
            }
        };
    }
  6. Use argument groups and validation

    master

    You can group arguments together and apply validation logic (e.g., Xor, And, Or). If a group's validation fails, the parser throws an args::ValidationError.

    Because group validation can involve complex logic (like (A && B) || (C && (D XOR E))), the library cannot automatically tell you which specific part of the logic failed. To provide helpful error messages to users, you must catch args::ValidationError and manually check your group conditions to print custom messages.

    #include <iostream>
    #include <args.hxx>
    
    int main(int argc, char **argv)
    {
        args::ArgumentParser parser("This is a test program.", "This goes after the options.");
        // Create an exclusive group (XOR)
        args::Group group(parser, "This group is all exclusive:", args::Group::Validators::Xor);
        
        args::Flag foo(group, "foo", "The foo flag", {'f', "foo"});
        args::Flag bar(group, "bar", "The bar flag", {'b'});
        args::Flag baz(group, "baz", "The baz flag", {"baz"});
    
        try
        {
            parser.ParseCLI(argc, argv);
        }
        catch (args::Help)
        {
            std::cout << parser;
            return 0;
        }
        catch (args::ParseError e)
        {
            std::cerr << e.what() << std::endl;
            std::cerr << parser;
            return 1;
        }
        catch (args::ValidationError e)
        {
            // Custom error handling for failed group validation
            std::cerr << e.what() << std::endl;
            std::cerr << parser;
            return 1;
        }
    
        if (foo) { std::cout << "foo" << std::endl; }
        if (bar) { std::cout << "bar" << std::endl; }
        if (baz) { std::cout << "baz" << std::endl; }
        return 0;
    }
  7. Preserve insertion order of object elements

    master
    By default, the library does not preserve the insertion order of object elements, as per the JSON standard. If your application requires preserving the order in which elements were added, you must specialize the object type using an ordered container such as tsl::ordered_map or nlohmann::fifo_map.
  8. Configure Unicode and String handling

    master

    The library follows RFC 7159 for Unicode support:

    • Supported Encoding: Only UTF-8 encoded input is supported. Other encodings (Latin-1, UTF-16, UTF-32) will result in parse errors.
    • String Storage: Strings are stored as UTF-8.
    • String Length: When using the default std::string type, functions like .size() or .length() return the number of bytes, not the number of characters or glyphs.
  9. How IconFontCppHeaders language files work

    master

    Each generated language file for a specific font contains:

    • Icon Definitions: Each icon code point is defined using an ICON_* naming convention.
    • Font Loading Metadata:
      • min: The minimum code point (excluding ASCII characters).
      • max: The maximum code point.
      • max 16 bit: The maximum 16-bit code point, specifically useful for libraries that only support 16-bit code points, such as Dear ImGui.
  10. Generate Font Awesome Pro language files

    master

    Font Awesome Pro is a commercial product. To generate language files for version 5 (or similar processes for 6+), follow these steps:

    1. Download the Font Awesome Pro 5 Web package from fontawesome.com.
    2. Place the icons.yml file (found in the package's metadata/ directory) in the same directory as the GenerateIconFontCppHeaders.py script.
    3. Run the GenerateIconFontCppHeaders.py script.
    4. Use the generated files with the corresponding .ttf files from the package (e.g., fa-brands-400.ttf, fa-light-300.ttf, fa-regular-400.ttf, fa-solid-900.ttf).