(3D) Build Specifications

Config files

master.json

Vendor-specific. Specifies how the information will be presented in the layout, including the order of the options and exclusion.

{
    // the name of the brand/vendor associated with the build for which this `master.json`
    // file is going to be used, should be coherent with naming in directories
    "brand" : "swear",

    // the sequence of ISO-15897 standard locale definitions for which proper locale
    // files should exist in this build, may not be overridden on a `spec.json` level
    // order is not irrelevant and the first locales in the list are considered to be
    // more "relevant" than the last ones (for instance for a fallback decision)
    "locales" : ["en_us", "pt_br", "ar"],

    // defines the order in which the original file structure should be presented in the layout
    "order" : {
        // the order in which the models appear in the layout (a model which is not
        // here won't appear in the catalog)
        "models" : ["vyner", "maddox", (...)],

        // the order in which the parts appear in the layout (both the alias and the
        // original names are present)
        "parts" : ["vamp", "front", "quarter", "lower_quarter", "ankle_quarter", "back", (...)],

        // the order in which the materials appear in the layout (again, both the alias and
        // the original names are present)
        "materials" : {
            "vamp" : ["nappa", "suede", "metallic", "hairy_calf", "ostrich", "python", "croc"],
            "front" : ["nappa", "suede", "metallic", "hairy_calf", "ostrich", "python", "croc"],
            (...)
        },

        // the order in which the colors appear in the layout (again, both the alias
        // and the original names are present)
        "colors" : {
            "nappa" : ["white", "black", "navy_blue", "royal_blue", "red", "fuchsia_pink", "chocolate_brown"],
            (...)
        }
    },

    // you can prevent combinations of appearing in the layout by explicitly
    // stating by part, materials per part and colors per material, you can
    // also prevent combinations by creating "rules" with "open" dimensions
    // using the empty string value (eg: `["", "vege_tan_calf", ""]`)
    "blacklist" : {
        "parts" : ["side", "top"],
        "materials" : {
            "side" : ["nappa", "python"],
            "upper" : ["calf"]
        },
        "colors" : {
            "cotton" : ["silver"],
            "metal" : ["pewter", "fuchsia_pink", "navy_blue"]
        },
        "rules" : [
            ["sole", "rubber", "gold"],
            ["sole", "rubber", "copper"],
            ["", "vege_tan_calf", ""],
            ["", "metallic", "pewter"],
            ["", "metallic", "fuchsia"],
            ["", "metallic", "navy"]
        ]
    },

    // defines the part's priorities for drawing, this should
    // ensure proper layering of the model (no hidden parts)
    "z_index" : {
        "shadow" : 1,
        "lining" : 2,
        "back_stay" : 3,
        "front" : 4,
        "side" : 5,
        "metal_toe_cap" : 10000,
        "overlay" : 30000
    },

    // defines visual alias for part, materials or colors, note that a fully
    // qualified name may be used to reduce ambiguity
    "alias" : {
        "vamp" : "vamper",
        "cotton" : "coton",
        "vamp:cotton" : "contone"
    }
}

config.json

Vendor-specific. Used to set the params of the building scripts (all.py).

{
    // vendor's name (myswear, swear, chanel, saint_laurent, ...).
    "vendor" : "myswear",

    // the z-index of each part, used by color.py to determine the correct picking
    // mask (values default to 100), usually shadow has the lowest value (appears
    // below) and the optionals have the highest (appear above), non-optional parts
    // usually do not need to be defined (when they come from 3D they are already
    // mutually exclusive and cropped by z-index)
    "priorities" : {
        "shadow" : 1,
        "fringe" : 10000,
        "metal_toe_cap" : 10000,
        "metal_caps" : 10000,
        "strap_tip" : 10000,
        "strap_tips" : 10000,
        "fringe_eyelets" : 10001,
        "toe_cap" : 20000,
        "overlay" : 30000
    },

    // avoids the color.py step where the masks are created,
    // the default is true
    "colorize" : false,

    // defines the color to be used when creating the masks,
    // the default is `000000`
    "color_colorize" : "ffffff",

    // avoids the `alpha.py` step where the alpha composition of images is applied,
    // the default is true
    "alpha_composite" : false,

    // to be ignored by `color.py`, since they are not parts folders, alternatively
    // this may be used to avoid the creation of masks for the certain parts as
    // they are not visible in the final layout
    "skip_colorize" : ["fringe_shadow", "masks", "overlay"],

    // the parts that are going to be ignored for the colorize operation, this is
    // different from the skip colorize in the sense that the index value is still
    // going to be counted towards the global mask (required to avoid incorrect
    // calculus of part index at runtime) and the picker (single) mask is still
    // going to be generated for the part
    "ignore_colorize" : ["inner_buckle"]

    // defines a specific material to be used for the colorize operation
    // for certain part of a model, in case the material is not defined using
    // this field a "random" one is used instead
    "specific_colorize" : {
        "vyner" : {
            "front" : "nappa",
            "side" : "nappa"
        }
    },

    // y-offset to be applied in a vertical translation
    "repositions" : {
        "regent" : 140,
        "bond" : 164,
        "maltby" : 78,
        "carnaby" : 78
    },

    // to be ignored by reposition.py, usually the top frame and the overlay
    // folder, since those files should already have the proper vertical alignment
    "skip_repositions" : ["overlay", "top"],

    // parts where opacity_shadow.py should apply, these parts are also going to be
    // ignored for the color.py
    "shadows" : ["fringe_shadow", "shadow"],

    // opacity_shadow's parameters (setting both to 1 ignores the operation)
    "opacity_shadow" : {
        "default" : {
            "blur_radius" : 1,
            "alpha_divisor" : 1
        },
        "fringe_shadow" : {
            "blur_radius" : 3.5,
            "alpha_divisor" : 2
        }
    },

    // merges two parts, the first value in the key-value pair appears below
    "blend_shadow" : {
        "maltby" : {
            "fringe_shadow" : "fringe"
        }
    },
    "no_alpha_composite" : ["white"],
    "to_remove" : [".DS_Store", "Thumbs.db", "thumbs.db"],

    // folders to include in the build (by copying)
    "static_dirs" : ["overlay"],

    // if the verification process on the building should be avoided
    // note that the avoiding verification is a dangerous operation
    "skip_verify" : false,

    // avoids a step of verify.py where it checks if the parts/materials/colors
    // present in spec.json exist
    "skip_verify_defaults" : ["masks", "overlay"],

    // creates a "croc_beya_loafer" which is a copy from "beya_loafer"
    // but where parts with "alligator_kirk" only have that material
    "new_models" : {
        "beya_loafer" : {
            "model_name" : "croc_beya_loafer",
            "material_to_extract" : "alligator_kirk"
        }
    }
}

spec.json

Model-specific. Every model has one as listed below, besides also supporting some of the settings in master.json such as order, blacklist, alias and restrictions (if one of these properties is present in both files, they will be merged, and the settings in spec.json takes priority).

{
    // if the current model configuration should be considered enabled by the runtime
    // if set to false the model is ignored (defaults to `true`)
    "enabled" : true,

    // the (tech) name of the brand associated with this model, should be
    // typically consistent with the vendor name for the build (bundle)
    "brand" : "swear",

    // to be displayed in the layout
    "description" : "vyner",

    // to be displayed in the layout
    "title" : "lace up round toe",

    // describes the model's style (eg: high, low top, etc.) should be used
    // carefully and the usage of the `tags` field is now encouraged for a more
    // "detailed" description of the model
    "style" : "low",

    // the default scale to be used in the sizes of the model, can be defined
    // as a string or an object (eg: `{"male" : "it", "female" : "fr"}`) if different
    // scales should be applied per gender, in case this value is not provided
    // explicitly it is inferred (if possible) from the values defined in the `sizes` field
    "scale" : "it",

    // {both, male, female, kids}, affects how the catalog's filter works
    "gender" : "both",

    // to be displayed in the layout
    "observations" : "A statement high-top sneaker detailed with four velcro straps.",

    // {round, pointy}, affects how the catalog's filter works
    "toe" : "round",

    // related to the image resolution, use "very-large" by default (1000x1000)
    "type" : "very-large",

    // the size of the base composition images in pixels, if not provided this
    // value may be inferred from the dimensions `$base` field
    "size" : [1000, 1000],

    // the format of the base files for PRC (eg: "png", "jpeg", "webp", etc) if not
    // provided it may be inferred from the dimensions `$base` field
    "format" : "png",

    // defines preferred pre-computed composition dimensions, additionally
    // other settings can be also defined such as format, color depth and quality
    // the special `$base` item defines the resolution of the base (original) images
    // these values may be used by tools like RIPE Building to pre-generated the
    // assets for non base dimensions according to parameters
    "dimensions" : {
        "$base" : {
            "size" : [1000, 1000],
            "format" : "png"
        },
        "small" : {
            "size" : [500, 500],
            "format" : "png"
        },
        "jpeg" : {
            "size" : [1000, 1000],
            "format" : "jpeg",
            "background" : "9d9d9d",
            "options" : {
                "quality" : 80,
                "optimize" : true
            }
        }
    },

    // keywords that define the behavior or characteristics of the model
    // "no_size" is used when the model has no size to choose
    // "no_initials" is used when the model has no personalization
    // "no_customization" is used when the model has no customization
    // "initials_lowercase" initials are normalized in an lowercase form
    // "initials_uppercase" initials are normalized in an uppercase form
    // "initials_dot" initials are normalized with a dot separator in between letters
    // "initials_mandatory" is used when the personalization is required to advance to the next step
    // "initials_type_full" is used to show the 'full' model's image in the personalization step, when choosing the initials
    // "initials_type_zoom" is used to show a 'zoomed' image of the model (circular form) in the personalization step, when choosing the initials
    // "farfetch_single_step" is used when the full customization is done in a single step on the farfetch plugin
    // "farfetch_no_masks" is used when the model has no masks on the farfetch plugin
    // "farfetch_size_step_no_initials" is used to hide the image with the initials on the size step of the farfetch plugin
    "tags" : ["initials_mandatory"],

    // describes the `factory` that is manufacturing the model
    // and some producing related information such as, if multiple
    // factories exist that are able to produce this model the `factories`
    // key should be used instead with a sequence of factories, note that
    // the `factories` can always be generated if only the `factory` is set
    // "production_time" production time in days
    // "production_cost" production cost in system's default currency
    // "locale" the locale in which the factory operates
    "factory" : {
        "name" : "FLAJ",
        "production_time" : 5,
        "production_cost" : 50,
        "locale" : "it_it_tech"
    },
    "factories" : [{
        "name" : "FLAJ",
        "production_time" : 5,
        "production_cost" : 50,
        "locale" : "it_it_tech"
    }, {
        "name" : "ASIAL",
        "production_time" : 6,
        "production_cost" : 70,
        "locale" : "pt_pt_tech"
    }],

    // describes the size range (in native) for the current model using either
    // a range (see https://docs.python.org/3/library/stdtypes.html#ranges)
    // based object that contains the lower bound (start) and the upper bound
    // (end) not inclusive or an array of explicit values, notice that the key
    // defines both the scale and the gender separated by the `:` character
    "sizes" : {
        "it:male" : {
            "start" : 25,
            "end" : 47,
            "step" : 2
        },
        "it:female" : [
            19,
            21,
            23,
            26,
            29,
            31
        ]
    },

    // legacy field that describes the number of frames images available for the
    // the main/first face, typically the side one (legacy)
    "frames" : 24,

    // possible perspectives of the model, notice that the first face is considered
    // the main one and subject to the defined default number of frames, this also
    // defines the default ordering of the faces (legacy)
    "faces" : ["side", "top"],

    // map version of the faces definition that allows a more flexible and powerful
    // description the faces, if this map does not exist it is going to be constructed
    // from the `faces` and `frames` fields, a face is described by the number of `frames`
    // (mandatory field) and if the face is recommended to be used as a `thumbnail` (optional field)
    "faces_m" : {
        "side" : {
            "frames" : 24,
            "thumbnail" : true
        },
        "top" : {
            "frames" : 1,
            "thumbnail" : false
        }
    },

    // the sequence of faces (and frames) that are going to be used as the meta-information
    // for the generation of (possible) thumbnails in the UI, should be considered just meta
    // information responsible for conditioning the UI and not something else, a typical usage
    // of these values is to merge them against static definition on the runtime UI
    // there's a compressed version of the description that allows someone to just define a
    // string with the face and possibly the frame to define the thumbnail, notice that extra
    // metadata like `overlay` may be added with more information for a certain thumbnail in
    // this case requesting that an overlay should exist for that face and frame,
    // these settings should match the definition of the faces (either `faces` or `faces_m`)
    "thumbnails" : ["face.<frame>", "top", "side.0", "side.6"],
    "thumbnails" : [
        {
            "name" : "top",
            "face" : "top",
            "overlay" : true
        },
        {
            "name" : "side",
            "face" : "side",
            "frame" : 0
        },
        {
            "name" : "back",
            "face" : "side",
            "frame" : 6
        }
    ],

    // parts not to be displayed in the layout, should also be added to defaults (see below) as hidden
    "hidden" : ["shadow"],

    // same as the order definition in master.json (overrides)
    "order" : {},

    // same as the blacklist definition in master.json (overrides)
    "blacklist" : {},

    // same as the alias definition in master.json (overrides)
    "alias" : {},

    // specifies the frame to be used to place the initials, based on
    // the engraving material, default is applied if no material rule
    // is found (fallback)
    "initials_frame" : {
        "default" : "top",
        "metal" : 18
    },

    // list of materials for which the dot character is going to be
    // place in between the two letters for the initials
    "initials_dot_materials" : [
        "embossed"
    ],

    // defines the set of rules (material a then color) that when matched
    // on the pivot part (usually front) will trigger the usage of the patch
    // and using the material defined in the leaf node
    "initials_patch" : {
        "suede" : {
            "white" : {
                "material" : "nappa",
                "color" : "white"
            }
        }
    },

    // the pivot part: the one whose material is used to determine
    // if the initials will be placed on a patch. It normally is the
    // part where the initials are applied to
    "initials_pivot_part" : "front",

    // the part whose material will be used to determine if a patch
    // should be used or not
    "initials_patch_materials" : "front",

    // list of initials materials for which the placing of the initials is
    // going to be done on a patch instead of the "normal" direct printing
    // of the initials on the initials frame pivot part
    "initials_patch_materials" : [
        "metal"
    ],

    // when set to true, the model will use a patch for the initials printing
    // regardless of the pivot part's material, this allows simplification of
    // model's configuration file so that there's no need to define the complete
    // set of materials of the model in the `initials_patch` map, the material
    // and color to be used in the patch is the one defined for the pivot part
    // in case the material color for patch does not exist the patch apply operation
    // is ignored and normal "apply" of initials is used instead
    "always_patch" : true,

    // describes sets of optional parts where mutual exclusion is going to take place, meaning that
    // if one is visible the other ones are going to become invisible
    "exclusions" : {
        "toe_cap_exclusion" : ["toe_cap", "metal_toe_cap"]
    },

    // describes sets of optionals parts where when one is visible, the others in the set also are
    "groups" : {
        "fringe_group" : ["fringe", "fringe_eyelets"]
    },

    // default material and color for every part and the definition of optional,
    // there's also some other information defined in this object that includes
    // (default) `face`, (default) `frame` and `thumbnail`
    // all these data definitions are derived from the `parts` attribute declared above
    "defaults" : {
        // thumbnail is set to `true` meaning that this value is going to
        // "automagically" included in the `thumbnails` list, it also defines
        // frame 2 of the front face as the one that best displays the front part
        "front" : {
            "material" : "nappa",
            "color" : "white",
            "face" : "front",
            "frame" : 2,
            "thumbnail" : true
        },

        // metal toe cap is considered to be optional, meaning that it
        // must be explicitly requested to be part of the final composition
        "metal_toe_cap" : {
            "optional" : true
        },

        // fringe is optional but it defaults to a visible state, meaning
        // that it can be set to invalid but by default it's going to be
        // displayed as part of a configuration
        "fringe" : {
            "optional" : true,
            "material" : "nappa",
            "color" : "white"
        },

        "shadow" : {
            "hidden" : true,
            "material" : "default",
            "color" : "default"
        }
    },

    // unstructured meta-data information, should be divided into different
    // contexts for good information isolation, either by placing them within
    // a context identifier map or by prefixing the keys with the context identifier
    "meta" : {
        "farfetch" : {
            "show_phone" : false
        },
        "ff_bounding_specs" : {
            "000" : {
                "suffix" : "_v1"
            },
            "002" : {
                "width" : 890,
                "suffix" : "_v2"
            },
            "top" : {
                "height" : 1200,
                "rotation" : 270,
                "suffix" : "_v4"
            }
        },
        "retail" : {
            "show_notification" : false
        }
    },

    // describes technical data used during the visual assets production
    // the data contained here should be separated by context and should
    // be interpreted according to such context
    "technical" : {
        "maya" : {
            "displacements" : {
                "parts" : ["front", "fringes", "side"],
                "ao_pass" : {
                    "main" : {
                        "type" : 1,
                        "amount" : 0.03,
                        "shift" : 0.0,
                        "edge_length" : 0.1,
                        "max_subdivs" : 8
                    },
                    "front" : {
                        "amount" : 0.06,
                        "shift" : 0.0,
                        "edge_length" : 0.1,
                        "max_subdivs" : 8
                    }
                },
                "materials" : {
                    "nappa" : {
                        "amount" : 0.03,
                        "shift" : 0.0,
                        "edge_length" : 0.15,
                        "max_subdivs" : 8
                    }
                }
            },
            "matchings" : {
                "front" : {
                    "materials" : {
                        "nappa" : {
                            "colors" : {
                                "black" : ["red", "green"],
                                "white" : ["grey", "blue"]
                            }
                        }
                    }
                }
            },
            "render_layers" : {
                "front" : {
                    "adjustments" : ["vraySettings.dmcMaxSubdivs", "vraySettings.dmcThreshold"],
                    "values" : [32, 0.001]
                },
                "fringe" : {
                    "adjustments" : ["MainDomeShape.diffuseContrib"],
                    "values" : [0.95]
                }
            }
        }
    }
}

initials (spec.json)

{
    // the name of the profile that is going to be applied by default
    // in every initials composition
    "profile" : "main",

    // the name of the frame (may include face) that is going to be used
    // for the display of the "special" initials compose request
    "frame" : "top",

    // the algorithm to be used in the blending operation between the initials
    // image and the target image (eg: `mask_top`, `destination_over`, `source_over`, etc.)
    "algorithm" : "mask_top",

    // name of the font that is going to be used for the "drawing" of the
    // initials, the specific name of the font is constructed by appending
    // the `font-family` with the `font-weight` (eg: DoraBlack-Regular.fnt)
    "font_family" : "DoraBlack",
    "font_weight" : "Regular",

    // the kind of mask that is going to be used upon the rendering of the
    // font to the target initials image (eg: `simple`, `self`)
    "font_mask" : "simple",

    // list of parts that are meant to be excluded from the composition
    // when a valid initials value exists
    "exclusion" : [
        "logo"
    ],

    // list of parts or triplets for parts that are going to be included
    // in case a valid initials value exists, if a single value is provided
    // the material and the color is assumed to be `default`
    "inclusion" : [
        "patch",
        "initials_patch:nappa:green"
    ],

    // the z-index to be used in the composition when "drawing"/"painting" the initials
    // into the final composition value, this is used as part of the dynamic layer strategy
    // used for the display of the initials on the image composition
    "z_index" : 4,

    // the definition of the multiple properties (characteristics) of the initials
    // that can be used at runtime for the construction of the `engraving` value,
    // there are two ways of defining `properties`: the compressed form with strings
    // that defined both the name of the property and optionally the type of it and
    // a the canonical way that defines each property as an object of values that
    // should at least define the name of the property
    // by using these values one may create a valid `engraving` value of `silver.opensans:font`
    // or in alternative a more complete `gold:style.opensans:font:` giving proper
    // context for the interpretation of the `engraving` value
    "properties" : ["name.<type>", "silver.style", "gold.style", "opensans.font"],
    "properties" : [{ name: "silver", type: "style" }],

    // description of the context-aware profiles that can be used to control
    // the initials parameters activation for certain situations, most of the
    // parameters associated with initials can be used under profiles
    "$profiles" : {
        "base" : {
            "frame" : "top",
            "font_family" : "DoraBlack",
            "font_weight" : "Regular"
        }
    },

    // the set of "pseudo-profiles" that are going to target the concrete ones
    // and that allow an extra level of indirection that can be used to better
    // structure the information, wildcard expansion is supported
    "$alias" : {
        "report" : "viewport::large",
        "step::personalization" : ["viewport::large*"]
    }
}

Logic Files

Context (ctx)

The context object contains the context of customization for the current environment and it's considered the primary way of sharing information in/out from the hosted application and the build related logic (eg: logic.js).

{
    "brand" : "swear",
    "model" : "vyner",
    "initials" : {
        "left" : {
            "initials" : "J",
            "engraving" : "metal"
        },
        "right" : {
            "initials" : "M",
            "engraving" : "silver"
        }
    },
    parts: {
        "side" : {
            "material" : "metal",
            "color" : "gold"
        }
    },
    choices: {
        "side" : {
            "available": true
            "materials" : {
                "available": true
                "metal" : {
                    "colors" : {
                        "gold" : {
                            "available": true
                        }
                    }
                }
            }
        }
    }
}

logic.py

config.py

class Config(object):

    def on_install(self, ctx):
        print("Thank you for installing the dummy package")
        print("For more information send an email to growth@platforme.com")

    def on_uninstall(self):
        print("We're sorry to see you leave dummy")

base.py

Vendor-specific. General vendor-wise behavior implementation.

import ripe_compose.logic

class Logic(ripe_compose.logic.Logic):

    def groups(self, ctx):
        return ["main"]

    def minimum_initials(self, group, ctx):
        return 1

    def maximum_initials(self, group, ctx):
        return 3

    def supported_characters(self, group, index, ctx):
        return "abcdefghijklmnopqrstuvwxyz"

    def supported_patterns(self, group, index, ctx):
        return ["[A-C]", "ζ–‡"]

    def spec_to_sku(self, spec):
        body = spec["parts"]["body"]
        top = spec["parts"]["top"]

        product = self.http.get(
            PRODUCT_API,
            params = {
                "model": "fancy"
                "body": "%s.%s" % (body["material"], body["color"])
                "accessory": "%s.%s" % (top["material"], top["color"])
            }
        )
        return product.sku

    def sku_to_spec(self, sku):
        product = self.http.get(
            SKU_API,
            params = {
                "model": "fancy"
                "sku": sku
            }
        )
        return {
            "brand": self.brand,
            "model": self.model,
            "parts": {
                "side": {
                    "material": product["body"].split(".")[0],
                    "color":  product["body"].split(".")[1]
                },
                "top": {
                    "material": product["accessory"].split(".")[0],
                    "color":  product["accessory"].split(".")[1]
                }
            }
        }

    def on_config(self, brand, model, ctx):
        print("Starting config for brand '%s' and model '%s'" % (brand, model))
        ctx["initials"]["main"]["initials"] = "ST"
        return ctx

    def on_part(self, name, value, ctx):
        print(
            "Trying to change part '%s' for material '%s' and color '%s'" %\
            (name, value["material"], value["color"])
        )
        if name == "shadow": value = dict(material = "default", color = "default")
        else: value = dict(material = "leather_dmy", color = "black")
        ctx["parts"][name] = value
        ctx["initials"]["main"]["initials"] = "DM"
        ctx["messages"] = ctx.get("messages", []) + [
            ("config", "Colors forced to black"),
            ("config", "Shadow forced to default")
        ]
        return ctx

    def on_initials(self, group, initials, engraving, ctx):
        print(
            "Trying to change initials to '%s' in '%s' for group '%s'" %\
            (initials, engraving, group)
        )

        blacklist = ["dog", "cat"]
        if initials in blacklist:
            ctx["initials"]["main"]["initials"] = ""
            ctx["messages"] += ctx.get("messages", []) + [
                ("initials", "Blacklisted word detected")
            ]

        return ctx

    def validate_gen(self, ctx):
        for item in ripe_compose.logic.Logic.validate_gen(self, ctx): yield item
        initials = ctx["initials"]["main"]["initials"]
        if initials in blacklist:
            yield "The word '%s' is blacklisted" % initials

model.py

Model-specific. Every model can have its own logic (ie: vyner.py). One can import the base implementation and override the desired behavior.

import base

class Logic(base.Logic):

    def maximum_initials(self, group, ctx):
        return 5

logic.js

{
    /**
     * Returns a boolean that indicates if personalization is allowed
     * for the current context/state.
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Boolean} If personalization is allowed for the current
     * context.
     */
    "allowPersonalization" : function(ctx) {
        return true;
    },

    /**
     * Returns a sequence of names for the groups allowed under the execution
     * of the current context.
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Array} The sequence of allowed groups.
     */
    "groups" : function(ctx) {
        return ["left", "right"];
    },

    /**
     * The minimum number of initials required for the "personalization" step
     * to be considered as valid.
     *
     * @param {Object} group The initials group for which the target number of
     * initials validation is meant to be performed.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Integer} The minimum amount of initials for the provided group.
     */
    "minimumInitials" : function(group, ctx) {
        return 0;
    },

    /**
     * The maxium number of initials required for the "personalization" step
     * to be considered as valid.
     *
     * @param {Object} group The initials group for which the target number of
     * initials validation is meant to be performed.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Integer} The maximum amount of initials for the provided group.
     */
    "maximumInitials" : function(group, ctx) {
        return 2;
    },

    /**
     * Returns the valid options for a given group and index, supports use
     * cases where special (Unicode) characters are only allowed on a
     * specific index, like MMI.
     *
     * @param {String} group The name of the group to obtain the sequence
     * of supported characters.
     * @param {Integer} index The initial index withing the group to obtain
     * the sequence of supported characters.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Array} The sequence of supported characters for the requested group
     * and initials index.
     */
    "supportedCharacters" : function(group, index, ctx) {
        return ["a", "b", " ", "1", "2", "3", "πŸ˜€", "😎"];
    },

    /**
     * Returns the valid options for a given group and index, supports use
     * cases where special (Unicode) characters are only allowed on a
     * specific index, like MMI.
     *
     * The provided regex patterns should comply with the PCRE standard, so
     * that they can be safely "compiled" by common regex engines.
     *
     * @param {String} group The name of the group to obtain the sequence
     * of supported characters.
     * @param {Integer} index The initial index withing the group to obtain
     * the sequence of supported characters.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Array} The sequence of supported regex patterns for the
     * requested group and initials index.
     */
    "supportedPatterns" : function(group, index, ctx) {
        return ["[A-C]", "ζ–‡", "πŸ˜€"];
    },

    /**
     * Calculates the SKU to a determined configuration spec allowing the
     * usage of "computed SKU's".
     *
     * This method may use external sources for the computation of the SKU
     * (eg: using an HTTP client).
     *
     * @param {Object} spec The object containing the configuration spec.
     * @return {String} The calculated SKU for the current context.
     */
    "specToSku" : function(spec) {
        const codes = {
            "materials": {
                "leather": "LTH",
                "suede": "SDE"
            }
            "colors": {
                "black": "001",
                "blue": "002"
            }
        };

        const side = spec.parts["side"];
        const top = spec.parts["top"];

        const sideCode = `${codes.materials[side.material]}${codes.colors[side.color]}`;
        const topCode = `${codes.materials[side.material]}${codes.colors[side.color]}`;

        return `${sideCode}-${topCode}`;
    },

    /**
     * "Resolves" the configuration spec that matches associated to the
     * provided SKU string, using either internal or external "heuristics".
     *
     * @param {String} sku The SKU to be used in the resolution process.
     * @return {Object} The configuration spec "resolved" from the provided SKU.
     */
    "skuToSpec" : function(sku) {
        const codes = {
            "materials": {
                "LTH": "leather",
                "SDE": "suede"
            }
            "colors": {
                "001": "black",
                "002": "blue"
            }
        };

        const parts = sku.split("-");
        const sideMaterial = parts[0].substring(0, 3);
        const sideColor = parts[0].substring(3, 6);
        const topMaterial = parts[1].substring(0, 3);
        const topColor = parts[1].substring(3, 6);

        return {
            parts: {
                side: {
                    material: `${codes.materials[sideMaterial]}`,
                    color: `${codes.colors[sideColor]}`
                },
                top: {
                    material: `${codes.materials[topMaterial]}`,
                    color: `${codes.colors[topColor]}`
                }
            }
        };
    }

    /**
     * Returns the set of engraving options for the provided context, the
     * reference implementation should be used most of the times using the
     * `properties` field provided by the model's spec file.
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Array} The sequence of engraving options as a name and value
     * paradigm for the current context.
     */
    "engravingOptions" : function(ctx) {
        return [{
            name: "font"
            values: ["medium", "Bold"]
        }, {
            name: "color"
            values: ["white", "black"]
        }]
    },

    /**
     * Validates the current context returning if it's valid according
     * to the basic set of rules (eg: restrictions, sync, etc.).
     *
     * This method should be run both on the client and server side to
     * ensure that for example the order complies with a series of rules
     * for the associated customization.
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Boolean} If the customization associated with the provided
     * context is valid according to domain rules.
     */
    "isValid" : function(ctx) {},

    /**
     * Legacy version of the initials retrieval, should be able to return
     * a string with the main initials value for the model.
     *
     * This method should be used with proper care as the `getInitialsExtra`
     * method should be considered the current canonical version of the initials
     * information retrieval.
     *
     * @return {String} A string with the textual representation of the initials
     * for the current context of configuration.
     */
    "getInitials" : function(ctx) {},

    /**
     * Legacy version of the engraving value retrieval.
     *
     * As with `getInitials` proper care should be taken while using this method
     * as the alternative `getInitialsExtra` method should be used for most cases.
     *
     * @return {String} A string with the textual representation of the engraving
     * type for the current context.
     */
    "getEngraving" : function(ctx) {},

    /**
     * Retrieves the complete initials extra structure ready to be sent
     * to the server side (for order creation).
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Object} The normalized initials extra object ready to be sent
     * to the server side.
     */
    "getInitialsExtra" : function(ctx) {
        return {
            main: {
                initials: ctx.initials.main.value.toUpperCase(),
                engraving: "ada"
            }
        }
    },

    /**
     * Allows the change of the current context of execution, this logic method is
     * to be called on a situation where a customization for a model has been started.
     *
     * @param {String} brand The brand to which the customization session is being started.
     * @param {String} model The model to which the customization session is being started.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Object} The new update ctx with the desired changes made to the current ctx.
     */
    "onConfig" : function(brand, model, ctx) {
        console.log(`Starting config for brand '${brand}' and model '${model}'`);
        ctx.initials.main.initials = "ST";
    },

    /**
     * Allows the change of the current context of execution, this logic method is
     * to be called on situation where a customization change was made on a part.
     *
     * @param {String} name The name of the part that is going to be changed.
     * @param {Object} value The value (material and color) of the part to be changed.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Object} The new update ctx with the desired changes made to the current ctx.
     */
    "onPart" : function(name, value, ctx) {
        console.log(`Trying to change part '${name}' for material '${value["material"]}' and color '${value["color"]}'`);
        if (name !== "shadow") {
            ctx.parts[name] = {
                "material": "leather_dmy",
                "color": "black"
            };
            ctx.messages.concat([["config", "Colors forced to black"]]);
        }
    },

    /**
     * Allows the change of the current context of execution, this logic method is
     * to be called on situation where the initials or engraving values were changed.
     *
     * @param {String} group The name of the group that is going to be changed.
     * @param {String} initials The initials value to be changed.
     * @param {String} engraving The engraving value to be changed.
     * @param {Object} ctx The object containing the customization context
     * to be used for the operation execution.
     * @return {Object} The new update ctx with the desired changes made to the current ctx.
     */
    "onInitials" : function(group, initials, engraving, ctx) {
        console.log(`Trying to change initials to '${initials}' in '${engraving}' for group '${group}'`);

        const blacklist = ["dog", "cat"];
        if (blacklist.includes(initials)) {
            ctx.initials.main.initials = "";
            ctx.messages.concat([["initials", "Blacklisted word detected"]]);
        }

        return ctx;
    }

    /**
     * Allows to extend the set of validations done against a context.
     * One should call the base's `validate_gen` and then append the
     * desired validations by "yielding" the invalidation messages.
     *
     * @param {Object} ctx The object containing the customization context
     * to be used for the validation operation.
     */
    "validateGen" : function*(ctx) {
        super.validate_gen(ctx);

        const initials = ctx.initials.main.initials;
        if (blacklist.indexOf(initials) > -1) {
            yield `The word '${initials}' is blacklisted`;
        }
    }
}