示例#1
0
def make_tile(vmf: VMF, p1: Vec, p2: Vec, top_mat, bottom_mat, beam_mat):
    """Generate a 2 or 1 unit thick squarebeams tile.

    """
    prism = vmf.make_prism(p1, p2)
    brush, t, b, n, s, e, w = prism
    t.mat = top_mat
    b.mat = bottom_mat

    n.mat = beam_mat
    s.mat = beam_mat
    e.mat = beam_mat
    w.mat = beam_mat

    thickness = abs(p1.z - p2.z)

    if thickness == 2:
        # The z-axis texture offset needed
        # The texture is 512 high, so wrap around
        # 56 is the offset for the thin-line part of squarebeams
        # Textures are at 0.25 size, so 4 per hammer unit
        z_off = ((max(p1.z, p2.z) * 4) + 56) % 512
    elif thickness == 1:
        # Slightly different offset, so the line is still centered
        z_off = ((max(p1.z, p2.z) * 4) + 54) % 512
    else:
        raise ValueError('Tile has incorrect thickness '
                         '(expected 1 or 2, got {})'.format(thickness))

    n.uaxis = UVAxis(0, 0, 1, offset=z_off)
    n.vaxis = UVAxis(1, 0, 0, offset=0)
    s.uaxis = n.uaxis.copy()
    s.vaxis = n.vaxis.copy()

    e.uaxis = UVAxis(0, 0, 1, offset=z_off)
    e.vaxis = UVAxis(0, 1, 0, offset=0)
    w.uaxis = e.uaxis.copy()
    w.vaxis = e.vaxis.copy()

    # Ensure the squarebeams textures aren't replaced, as well as floor tex
    vbsp.IGNORED_FACES.update(brush.sides)

    return prism
示例#2
0
def make_tile(vmf: VMF, p1: Vec, p2: Vec, top_mat, bottom_mat, beam_mat):
    """Generate a 2 or 1 unit thick squarebeams tile.

    """
    prism = vmf.make_prism(p1, p2)
    prism.top.mat = top_mat
    prism.bottom.mat = bottom_mat

    prism.north.mat = beam_mat
    prism.south.mat = beam_mat
    prism.east.mat = beam_mat
    prism.west.mat = beam_mat

    thickness = abs(p1.z - p2.z)

    if thickness == 2:
        # The z-axis texture offset needed
        # The texture is 512 high, so wrap around
        # 56 is the offset for the thin-line part of squarebeams
        # Textures are at 0.25 size, so 4 per hammer unit
        z_off = ((max(p1.z, p2.z) * 4) + 56) % 512
    elif thickness == 1:
        # Slightly different offset, so the line is still centered
        z_off = ((max(p1.z, p2.z) * 4) + 54) % 512
    else:
        raise ValueError('Tile has incorrect thickness '
                         '(expected 1 or 2, got {})'.format(thickness))

    prism.north.uaxis = UVAxis(0, 0, 1, offset=z_off)
    prism.north.vaxis = UVAxis(1, 0, 0, offset=0)
    prism.south.uaxis = prism.north.uaxis.copy()
    prism.south.vaxis = prism.north.vaxis.copy()

    prism.east.uaxis = UVAxis(0, 0, 1, offset=z_off)
    prism.east.vaxis = UVAxis(0, 1, 0, offset=0)
    prism.west.uaxis = prism.east.uaxis.copy()
    prism.west.vaxis = prism.east.vaxis.copy()

    return prism
示例#3
0
    def parse(cls, ent: Entity):
        """Parse a template from a config entity.

        This should be a 'bee2_template_scaling' entity.
        """
        axes = {}

        for norm, name in (
            ((0, 0, 1), 'up'),
            ((0, 0, -1), 'dn'),
            ((0, 1, 0), 'n'),
            ((0, -1, 0), 's'),
            ((1, 0, 0), 'e'),
            ((-1, 0, 0), 'w'),
        ):
            axes[norm] = (
                ent[name + '_tex'],
                UVAxis.parse(ent[name + '_uaxis']),
                UVAxis.parse(ent[name + '_vaxis']),
                srctools.conv_float(ent[name + '_rotation']),
            )
        return cls(ent['template_id'], axes)
示例#4
0
def retexture_template(
    template_data: ExportedTemplate,
    origin: Vec,
    fixup: srctools.vmf.EntityFixup=None,
    replace_tex: dict= srctools.EmptyMapping,
    force_colour: MAT_TYPES=None,
    force_grid: str=None,
    use_bullseye=False,
    no_clumping=False,
):
    """Retexture a template at the given location.

    - Only textures in the TEMPLATE_RETEXTURE dict will be replaced.
    - Others will be ignored (nodraw, plasticwall, etc)
    - Wall textures pointing up and down will switch to floor/ceiling textures.
    - Textures of the same type, normal and inst origin will randomise to the
      same type.
    - replace_tex is a replacement table. This overrides everything else.
      The values should either be a list (random), or a single value.
    - If force_colour is set, all tile textures will be switched accordingly.
      If set to 'INVERT', white and black textures will be swapped.
    - If force_grid is set, all tile textures will be that size:
      ('wall', '2x2', '4x4', 'special')
    - If use_bullseye is true, the bullseye textures will be used for all panel
      sides instead of the normal textures. (This overrides force_grid.)
    - Fixup is the inst.fixup value, used to allow $replace in replace_tex.
    - Set no_clump if the brush is used on a special entity, and therefore
      won't get retextured by the main code. That means we need to directly
      retexture here.
    """
    import vbsp

    template = template_data.template  # type: Template

    rev_id_mapping = {
        new_id: str(old_id)
        for old_id, new_id in
        template_data.orig_ids.items()
    }

    all_brushes = list(template_data.world)  # type: List[Solid]
    if template_data.detail is not None:
        all_brushes.extend(template_data.detail.solids)

    # Template faces are randomised per block and side. This means
    # multiple templates in the same block get the same texture, so they
    # can clip into each other without looking bad.
    rand_prefix = 'TEMPLATE_{}_{}_{}:'.format(*(origin // 128))

    # Even if not axis-aligned, make mostly-flat surfaces
    # floor/ceiling (+-40 degrees)
    # sin(40) = ~0.707
    floor_tolerance = 0.8

    can_clump = vbsp.can_clump()

    # Ensure all values are lists.
    replace_tex = {
        key.casefold(): ([value] if isinstance(value, str) else value)
        for key, value in
        replace_tex.items()
    }

    # For each face, if it needs to be forced to a colour, or None if not.
    force_colour_face = defaultdict(lambda: None)

    # Already sorted by priority.
    for color_picker in template.color_pickers:
        picker_pos = color_picker.offset.copy().rotate(*template_data.angles)
        picker_pos += template_data.origin
        picker_norm = color_picker.normal.copy().rotate(*template_data.angles)

        if color_picker.grid_snap:
            for axis in 'xyz':
                # Don't realign things in the normal's axis -
                # those are already fine.
                if not picker_norm[axis]:
                    picker_pos[axis] = picker_pos[axis] // 128 * 128 + 64

        brush = conditions.SOLIDS.get(picker_pos.as_tuple(), None)

        if brush is None or abs(brush.normal) != abs(picker_norm):
            # Doesn't exist.
            continue

        if color_picker.remove_brush and brush.solid in vbsp.VMF.brushes:
            brush.solid.remove()

        for side in color_picker.sides:
            # Only do the highest priority successful one.
            if force_colour_face[side] is None:
                force_colour_face[side] = brush.color

    for brush in all_brushes:
        for face in brush:
            orig_id = rev_id_mapping[face.id]

            if orig_id in template.skip_faces:
                continue

            folded_mat = face.mat.casefold()

            norm = face.normal()
            random.seed(rand_prefix + norm.join('_'))

            if orig_id in template.realign_faces:
                try:
                    uaxis, vaxis = REALIGN_UVS[norm.as_tuple()]
                except KeyError:
                    LOGGER.warning(
                        'Realign face in template "{}" ({} in final) is '
                        'not on grid!',
                        template.id,
                        face.id,
                    )
                else:
                    face.uaxis = uaxis.copy()
                    face.vaxis = vaxis.copy()

            try:
                override_mat = replace_tex['#' + orig_id]
            except KeyError:
                try:
                    override_mat = replace_tex[folded_mat]
                except KeyError:
                    override_mat = None

            if override_mat is not None:
                # Replace_tex overrides everything.
                mat = random.choice(override_mat)
                if mat[:1] == '$' and fixup is not None:
                    mat = fixup[mat]
                if mat.startswith('<') or mat.endswith('>'):
                    # Lookup in the style data.
                    mat = vbsp.get_tex(mat[1:-1])
                face.mat = mat
                continue

            try:
                tex_type = TEMPLATE_RETEXTURE[folded_mat]
            except KeyError:
                continue  # It's nodraw, or something we shouldn't change

            if isinstance(tex_type, str):
                # It's something like squarebeams or backpanels, just look
                # it up
                face.mat = vbsp.get_tex(tex_type)

                if tex_type == 'special.goo_cheap':
                    if norm != (0, 0, 1):
                        # Goo must be facing upright!
                        # Retexture to nodraw, so a template can be made with
                        # all faces goo to work in multiple orientations.
                        face.mat = 'tools/toolsnodraw'
                    else:
                        # Goo always has the same orientation!
                        face.uaxis = UVAxis(
                            1, 0, 0,
                            offset=0,
                            scale=vbsp_options.get(float, 'goo_scale'),
                        )
                        face.vaxis = UVAxis(
                            0, -1, 0,
                            offset=0,
                            scale=vbsp_options.get(float, 'goo_scale'),
                        )
                continue
            # It's a regular wall type!
            tex_colour, grid_size = tex_type

            if force_colour_face[orig_id] is not None:
                tex_colour = force_colour_face[orig_id]
            elif force_colour == 'INVERT':
                # Invert the texture
                tex_colour = (
                    MAT_TYPES.white
                    if tex_colour is MAT_TYPES.black else
                    MAT_TYPES.black
                )
            elif force_colour is not None:
                tex_colour = force_colour

            if force_grid is not None:
                grid_size = force_grid

            if 1 in norm or -1 in norm:  # Facing NSEW or up/down
                # If axis-aligned, make the orientation aligned to world
                # That way multiple items merge well, and walls are upright.
                # We allow offsets < 1 grid tile, so items can be offset.
                face.uaxis.offset %= TEMP_TILE_PIX_SIZE[grid_size]
                face.vaxis.offset %= TEMP_TILE_PIX_SIZE[grid_size]

            if use_bullseye:
                # We want to use the bullseye textures, instead of normal
                # ones
                if norm.z < -floor_tolerance:
                    face.mat = vbsp.get_tex(
                        'special.bullseye_{}_floor'.format(tex_colour)
                    )
                elif norm.z > floor_tolerance:
                    face.mat = vbsp.get_tex(
                        'special.bullseye_{}_ceiling'.format(tex_colour)
                    )
                else:
                    face.mat = ''  # Ensure next if statement triggers

                # If those aren't defined, try the wall texture..
                if face.mat == '':
                    face.mat = vbsp.get_tex(
                        'special.bullseye_{}_wall'.format(tex_colour)
                    )
                if face.mat != '':
                    continue  # Set to a bullseye texture,
                    # don't use the wall one

            if grid_size == 'special':
                # Don't use wall on faces similar to floor/ceiling:
                if -floor_tolerance < norm.z < floor_tolerance:
                    face.mat = vbsp.get_tex(
                        'special.{!s}_wall'.format(tex_colour)
                    )
                else:
                    face.mat = ''  # Ensure next if statement triggers

                # Various fallbacks if not defined
                if face.mat == '':
                    face.mat = vbsp.get_tex(
                        'special.{!s}'.format(tex_colour)
                    )
                if face.mat == '':
                    # No special texture - use a wall one.
                    grid_size = 'wall'
                else:
                    # Set to a special texture,
                    continue # don't use the wall one

            if norm.z > floor_tolerance:
                grid_size = 'ceiling'
            if norm.z < -floor_tolerance:
                grid_size = 'floor'

            if can_clump and not no_clumping:
                # For the clumping algorithm, set to Valve PeTI and let
                # clumping handle retexturing.
                vbsp.IGNORED_FACES.remove(face)
                if tex_colour is MAT_TYPES.white:
                    if grid_size == '4x4':
                        face.mat = 'tile/white_wall_tile003f'
                    elif grid_size == '2x2':
                        face.mat = 'tile/white_wall_tile003c'
                    else:
                        face.mat = 'tile/white_wall_tile003h'
                elif tex_colour is MAT_TYPES.black:
                    if grid_size == '4x4':
                        face.mat = 'metal/black_wall_metal_002b'
                    elif grid_size == '2x2':
                        face.mat = 'metal/black_wall_metal_002a'
                    else:
                        face.mat = 'metal/black_wall_metal_002e'
            else:
                face.mat = vbsp.get_tex(
                    '{!s}.{!s}'.format(tex_colour, grid_size)
                )

    for over in template_data.overlay[:]:
        random.seed('TEMP_OVERLAY_' + over['basisorigin'])
        mat = over['material'].casefold()
        if mat in replace_tex:
            mat = random.choice(replace_tex[mat])
            if mat[:1] == '$':
                mat = fixup[mat]
            if mat.startswith('<') or mat.endswith('>'):
                # Lookup in the style data.
                mat = vbsp.get_tex(mat[1:-1])
        elif mat in vbsp.TEX_VALVE:
            mat = vbsp.get_tex(vbsp.TEX_VALVE[mat])
        else:
            continue
        if mat == '':
            # If blank, remove the overlay from the map and the list.
            # (Since it's inplace, this can affect the tuple.)
            template_data.overlay.remove(over)
            over.remove()
        else:
            over['material'] = mat
示例#5
0
def retexture_template(
    template_data: ExportedTemplate,
    origin: Vec,
    fixup: EntityFixup = None,
    replace_tex: Mapping[str, Union[List[str], str]] = srctools.EmptyMapping,
    force_colour: Portalable = None,
    force_grid: TileSize = None,
    generator: GenCat = GenCat.NORMAL,
    sense_offset: Optional[Vec] = None,
):
    """Retexture a template at the given location.

    - Only textures in the TEMPLATE_RETEXTURE dict will be replaced.
    - Others will be ignored (nodraw, plasticwall, etc)
    - Wall textures pointing up and down will switch to floor/ceiling textures.
    - Textures of the same type, normal and inst origin will randomise to the
      same type.
    - replace_tex is a replacement table. This overrides everything else.
      The values should either be a list (random), or a single value.
    - If force_colour is set, all tile textures will be switched accordingly.
      If set to 'INVERT', white and black textures will be swapped.
    - If force_grid is set, all tile textures will be that size.
    - generator defines the generator category to use for surfaces.
    - Fixup is the inst.fixup value, used to allow $replace in replace_tex.
    - If sense_offset is set, color pickers and tilesetters will be treated
      as if they were locally offset this far in the template.
    """

    template = template_data.template

    rev_id_mapping = {
        new_id: str(old_id)
        for old_id, new_id in template_data.orig_ids.items()
    }

    all_brushes = list(template_data.world)
    if template_data.detail is not None:
        all_brushes.extend(template_data.detail.solids)

    # Template faces are randomised per block and side. This means
    # multiple templates in the same block get the same texture, so they
    # can clip into each other without looking bad.
    rand_prefix = 'TEMPLATE_{0.x}_{0.y}_{0.z}:'.format(origin // 128)

    # Reprocess the replace_tex passed in, converting values.
    evalled_replace_tex: Dict[str, List[str]] = {}
    for key, value in replace_tex.items():
        if isinstance(value, str):
            value = [value]
        if fixup is not None:
            # Convert the material and key for fixup names.
            value = [
                fixup[mat] if mat.startswith('$') else mat for mat in value
            ]
            if key.startswith('$'):
                key = fixup[key]
        # If starting with '#', it's a face id, or a list of those.
        if key.startswith('#'):
            for k in key[1:].split():
                try:
                    old_id = int(k)
                except (ValueError, TypeError):
                    pass
                else:
                    evalled_replace_tex.setdefault('#' + str(old_id),
                                                   []).extend(value)
        else:
            evalled_replace_tex.setdefault(key.casefold(), []).extend(value)

    if sense_offset is None:
        sense_offset = Vec()
    else:
        sense_offset = sense_offset.copy().rotate(*template_data.angles)

    # For each face, if it needs to be forced to a colour, or None if not.
    # If a string it's forced to that string specifically.
    force_colour_face: Dict[str, Union[Portalable, str,
                                       None]] = defaultdict(lambda: None)
    # Picker names to their results.
    picker_results: Dict[str,
                         Optional[Portalable]] = template_data.picker_results
    picker_type_results: Dict[str, Optional[TileType]] = {}

    # If the "use patterns" option is enabled, face ID -> temp face to copy from.
    picker_patterned: Dict[str, Optional[Side]] = defaultdict(lambda: None)
    # Then also a cache of the tiledef -> dict of template faces.
    pattern_cache: Dict[tiling.TileDef, Dict[Tuple[int, int], Side]] = {}

    # Already sorted by priority.
    for color_picker in template.color_pickers:
        if not color_picker.visgroups.issubset(template_data.visgroups):
            continue

        picker_pos = color_picker.offset.copy().rotate(*template_data.angles)
        picker_pos += template_data.origin + sense_offset
        picker_norm = Vec(color_picker.normal).rotate(*template_data.angles)

        if color_picker.grid_snap:
            for axis in 'xyz':
                # Don't realign things in the normal's axis -
                # those are already fine.
                if not picker_norm[axis]:
                    picker_pos[axis] = picker_pos[axis] // 128 * 128 + 64

        try:
            tiledef, u, v = tiling.find_tile(picker_pos, picker_norm)
        except KeyError:
            # Doesn't exist. But only set if not already present.
            if color_picker.name:
                picker_results.setdefault(color_picker.name, None)
                picker_type_results.setdefault(color_picker.name, None)
            continue

        tile_type = tiledef[u, v]

        picker_type_results[color_picker.name] = tile_type

        try:
            tile_color = tile_type.color
        except ValueError:
            # Not a tile with color (void, etc). Treat as missing a color.
            picker_results.setdefault(color_picker.name, None)
            continue

        if color_picker.name and picker_results.get(color_picker.name,
                                                    None) is None:
            picker_results[color_picker.name] = tile_color

        if color_picker.use_pattern:
            # Generate the brushwork for the tile to determine the top faces
            # required. We can then throw away the brushes themselves.
            try:
                pattern = pattern_cache[tiledef]
            except KeyError:
                pattern = pattern_cache[tiledef] = {}
                tiledef.gen_multitile_pattern(
                    VMF(),
                    {(u, v): tiledef[u, v]
                     for u in (0, 1, 2, 3) for v in (0, 1, 2, 3)},
                    is_wall=tiledef.normal.z != 0,
                    bevels=(False, False, False, False),
                    normal=tiledef.normal,
                    face_output=pattern,
                )

            for side in color_picker.sides:
                if picker_patterned[side] is None and (u, v) in pattern:
                    picker_patterned[side] = pattern[u, v]
        else:
            # Only do the highest priority successful one.
            for side in color_picker.sides:
                if force_colour_face[side] is None:
                    if tile_color is tile_color.WHITE:
                        force_colour_face[
                            side] = color_picker.force_tex_white or tile_color
                    else:
                        force_colour_face[
                            side] = color_picker.force_tex_black or tile_color

        if color_picker.after is AfterPickMode.VOID:
            tiledef[u, v] = TileType.VOID
        elif color_picker.after is AfterPickMode.NODRAW:
            tiledef[u, v] = TileType.NODRAW

    for tile_setter in template.tile_setters:
        if not tile_setter.visgroups.issubset(template_data.visgroups):
            continue

        setter_pos = Vec(tile_setter.offset).rotate(*template_data.angles)
        setter_pos += template_data.origin + sense_offset
        setter_norm = Vec(tile_setter.normal).rotate(*template_data.angles)
        setter_type = tile_setter.tile_type  # type: TileType

        if tile_setter.color == 'copy':
            if not tile_setter.picker_name:
                raise ValueError('"{}": Tile Setter set to copy mode '
                                 'must have a color picker!'.format(
                                     template.id))
            # If a color picker is set, it overrides everything else.
            try:
                setter_type = picker_type_results[tile_setter.picker_name]
            except KeyError:
                raise ValueError('"{}": Tile Setter specified color picker '
                                 '"{}" which does not exist!'.format(
                                     template.id, tile_setter.picker_name))
            if setter_type is None:
                raise ValueError(
                    '"{}": Color picker "{}" has no tile to pick!'.format(
                        template.id, tile_setter.picker_name))
        elif setter_type.is_tile:
            if tile_setter.picker_name:
                # If a color picker is set, it overrides everything else.
                try:
                    setter_color = picker_results[tile_setter.picker_name]
                except KeyError:
                    raise ValueError(
                        '"{}": Tile Setter specified color picker '
                        '"{}" which does not exist!'.format(
                            template.id, tile_setter.picker_name))
                if setter_color is None:
                    raise ValueError(
                        '"{}": Color picker "{}" has no tile to pick!'.format(
                            template.id, tile_setter.picker_name))
            elif isinstance(tile_setter.color, Portalable):
                # The color was specifically set.
                setter_color = tile_setter.color
            elif isinstance(force_colour, Portalable):
                # Otherwise it copies the forced colour -
                # it needs to be white or black.
                setter_color = force_colour
            else:
                # We need a forced color, but none was provided.
                raise ValueError(
                    '"{}": Tile Setter set to use colour value from the '
                    "template's overall color, "
                    'but not given one!'.format(template.id))

            # Inverting applies to all of these.
            if force_colour == 'INVERT':
                setter_color = ~setter_color

            setter_type = TileType.with_color_and_size(
                setter_type.tile_size,
                setter_color,
            )

        tiling.edit_quarter_tile(
            setter_pos,
            setter_norm,
            setter_type,
            silent=True,  # Don't log missing positions.
            force=tile_setter.force,
        )

    for brush in all_brushes:
        for face in brush:
            orig_id = rev_id_mapping[face.id]

            if orig_id in template.skip_faces:
                continue

            template_face = picker_patterned[orig_id]
            if template_face is not None:
                face.mat = template_face.mat
                face.uaxis = template_face.uaxis.copy()
                face.vaxis = template_face.vaxis.copy()
                continue

            folded_mat = face.mat.casefold()

            norm = face.normal()
            random.seed(rand_prefix + norm.join('_'))

            if orig_id in template.realign_faces:
                try:
                    uaxis, vaxis = REALIGN_UVS[norm.as_tuple()]
                except KeyError:
                    LOGGER.warning(
                        'Realign face in template "{}" ({} in final) is '
                        'not on grid!',
                        template.id,
                        face.id,
                    )
                else:
                    face.uaxis = uaxis.copy()
                    face.vaxis = vaxis.copy()

            override_mat: Optional[List[str]]
            try:
                override_mat = evalled_replace_tex['#' + orig_id]
            except KeyError:
                try:
                    override_mat = evalled_replace_tex[folded_mat]
                except KeyError:
                    override_mat = None

            if override_mat is not None:
                # Replace_tex overrides everything.
                mat = random.choice(override_mat)
                if mat[:1] == '$' and fixup is not None:
                    mat = fixup[mat]
                if mat.startswith('<') and mat.endswith('>'):
                    # Lookup in the style data.
                    gen, mat = texturing.parse_name(mat[1:-1])
                    mat = gen.get(face.get_origin(), mat)
                # If blank, don't set.
                if mat:
                    face.mat = mat
                    continue

            if folded_mat == 'tile/white_wall_tile003b':
                LOGGER.warning(
                    '"{}": white_wall_tile003b has changed definitions.',
                    template.id)

            try:
                tex_type = TEMPLATE_RETEXTURE[folded_mat]
            except KeyError:
                continue  # It's nodraw, or something we shouldn't change

            tex_colour: Optional[Portalable]
            gen_type, tex_name, tex_colour = tex_type

            if not gen_type.is_tile:
                # It's something like squarebeams or backpanels, so
                # we don't need much special handling.
                texturing.apply(gen_type, face, tex_name)

                if tex_name in ('goo', 'goo_cheap'):
                    if norm != (0, 0, 1):
                        # Goo must be facing upright!
                        # Retexture to nodraw, so a template can be made with
                        # all faces goo to work in multiple orientations.
                        face.mat = 'tools/toolsnodraw'
                    else:
                        # Goo always has the same orientation!
                        face.uaxis = UVAxis(
                            1,
                            0,
                            0,
                            offset=0,
                            scale=vbsp_options.get(float, 'goo_scale'),
                        )
                        face.vaxis = UVAxis(
                            0,
                            -1,
                            0,
                            offset=0,
                            scale=vbsp_options.get(float, 'goo_scale'),
                        )
                continue
            else:
                assert isinstance(tex_colour, Portalable)
                # Allow overriding to panel or bullseye types.
                if gen_type is GenCat.NORMAL:
                    gen_type = generator
            # Otherwise, it's a panel wall or the like

            if force_colour_face[orig_id] is not None:
                tex_colour = force_colour_face[orig_id]
                if isinstance(tex_colour, str):
                    face.mat = tex_colour
                    continue
            elif force_colour == 'INVERT':
                # Invert the texture
                tex_colour = ~tex_colour
            elif force_colour is not None:
                tex_colour = force_colour

            if force_grid is not None:
                tex_name = force_grid

            texturing.apply(gen_type, face, tex_name, tex_colour)

    for over in template_data.overlay[:]:
        over_pos = Vec.from_str(over['basisorigin'])
        mat = over['material'].casefold()

        if mat in replace_tex:
            mat = random.choice(replace_tex[mat])
            if mat[:1] == '$' and fixup is not None:
                mat = fixup[mat]
            if mat.startswith('<') or mat.endswith('>'):
                mat = mat[1:-1]
                gen, tex_name = texturing.parse_name(mat[1:-1])
                mat = gen.get(over_pos, tex_name)
        else:
            try:
                sign_type = consts.Signage(mat)
            except ValueError:
                pass
            else:
                mat = texturing.OVERLAYS.get(over_pos, sign_type)

        if mat == '':
            # If blank, remove the overlay from the map and the list.
            # (Since it's inplace, this can affect the tuple.)
            template_data.overlay.remove(over)
            over.remove()
        else:
            over['material'] = mat