Exemplo n.º 1
0
def import_template(
    vmf: VMF,
    temp_name: Union[str, Template],
    origin: Vec,
    angles: Optional[Union[Angle, Matrix]] = None,
    targetname: str = '',
    force_type: TEMP_TYPES = TEMP_TYPES.default,
    add_to_map: bool = True,
    additional_visgroups: Iterable[str] = (),
    bind_tile_pos: Iterable[Vec] = (),
    align_bind: bool = False,
    coll: collisions.Collisions = None,
    coll_add: Optional[
        collisions.CollideType] = collisions.CollideType.NOTHING,
    coll_mask: collisions.CollideType = collisions.CollideType.EVERYTHING,
) -> ExportedTemplate:
    """Import the given template at a location.

    * `temp_name` can be a string, or a template instance.
    * `visgroups` is a list of additional visgroups to use after the ones in the name string (if given).
    * If `force_type` is set to 'detail' or 'world', all brushes will be converted
      to the specified type instead. A list of world brushes and the func_detail
      entity will be returned. If there are no detail brushes, None will be
      returned instead of an invalid entity.
    * If `targetname` is set, it will be used to localise overlay names.
      add_to_map sets whether to add the brushes and func_detail to the map.
    * IF `coll` is provided, the template may have bee2_collision volumes. The targetname must be
      provided in this case.
    * If any `bound_tile_pos` are provided, these are offsets to tiledefs which
      should have all the overlays in this template bound to them, and vice versa.
    * If `align_bind` is set, these will be first aligned to grid.
    * `coll_mask` and `coll_force` allow modifying the collision types added. `coll_mask` is AND-ed
      with the bbox type, then `coll_add` is OR-ed in. If the collide type ends up being NOTHING, it
      is skipped.
    """
    import vbsp
    if isinstance(temp_name, Template):
        template, temp_name = temp_name, temp_name.id
        chosen_groups: set[str] = set()
    else:
        temp_name, chosen_groups = parse_temp_name(temp_name)
        template = get_template(temp_name)

    chosen_groups.update(additional_visgroups)
    chosen_groups.add('')

    orig_world, orig_detail, orig_over = template.visgrouped(chosen_groups)

    new_world: list[Solid] = []
    new_detail: list[Solid] = []
    new_over: list[Entity] = []

    # A map of the original -> new face IDs.
    id_mapping: dict[int, int] = {}
    orient = to_matrix(angles)

    for orig_list, new_list in [(orig_world, new_world),
                                (orig_detail, new_detail)]:
        for old_brush in orig_list:
            brush = old_brush.copy(
                vmf_file=vmf,
                side_mapping=id_mapping,
                keep_vis=False,
            )
            brush.localise(origin, orient)
            new_list.append(brush)

    for overlay in orig_over:
        new_overlay = overlay.copy(
            vmf_file=vmf,
            keep_vis=False,
        )
        del new_overlay[
            'template_id']  # Remove this, it's not part of overlays
        new_overlay['classname'] = 'info_overlay'

        sides = overlay['sides'].split()
        new_overlay['sides'] = ' '.join(
            str(id_mapping[int(side)]) for side in sides
            if int(side) in id_mapping)

        srctools.vmf.localise_overlay(new_overlay, origin, orient)
        orig_target = new_overlay['targetname']

        # Only change the targetname if the overlay is not global, and we have
        # a passed name.
        if targetname and orig_target and orig_target[0] != '@':
            new_overlay['targetname'] = targetname + '-' + orig_target

        vmf.add_ent(new_overlay)
        new_over.append(new_overlay)

        # Don't let the overlays get retextured too!
        vbsp.IGNORED_OVERLAYS.add(new_overlay)

    if force_type is TEMP_TYPES.detail:
        new_detail.extend(new_world)
        new_world.clear()
    elif force_type is TEMP_TYPES.world:
        new_world.extend(new_detail)
        new_detail.clear()

    if add_to_map:
        vmf.add_brushes(new_world)

    detail_ent: Optional[Entity] = None

    if new_detail:
        detail_ent = vmf.create_ent(classname='func_detail')
        detail_ent.solids = new_detail
        if not add_to_map:
            detail_ent.remove()

    if bind_tile_pos:
        # Bind all our overlays without IDs to a set of tiles,
        # and add any marked faces to those tiles to be given overlays.
        new_overlay_faces = set(
            map(id_mapping.get, map(int, template.overlay_faces)))
        new_overlay_faces.discard(None)
        bound_overlay_faces = [
            face for brush in (new_world + new_detail) for face in brush.sides
            if face.id in new_overlay_faces
        ]

        tile_norm = orient.up()
        for tile_off in bind_tile_pos:
            tile_off = tile_off.copy()
            tile_off.localise(origin, orient)
            for axis in ('xyz' if align_bind else ''):
                # Don't realign things in the normal's axis -
                # those are already fine.
                if abs(tile_norm[axis]) < 1e-6:
                    tile_off[axis] = tile_off[axis] // 128 * 128 + 64
            try:
                tile = tiling.TILES[tile_off.as_tuple(), tile_norm.as_tuple()]
            except KeyError:
                LOGGER.warning(
                    'No tile to bind at {} for "{}"!',
                    tile_off,
                    template.id,
                )
                continue
            for over in new_over:
                if over['sides'] == '':
                    tile.bind_overlay(over)
            tile.brush_faces.extend(bound_overlay_faces)

    if template.collisions:
        if coll is None:
            LOGGER.warning(
                'Template "{}" has collisions, but unable to apply these!',
                template.id)
        elif targetname:
            for coll_def in template.collisions:
                if not coll_def.visgroups.issubset(chosen_groups):
                    continue
                contents = (coll_def.bbox.contents & coll_mask) | coll_add
                if contents is not contents.NOTHING:
                    bbox = coll_def.bbox @ orient + origin
                    coll.add(
                        bbox.with_attrs(name=targetname, contents=contents))
        else:
            LOGGER.warning(
                'With collisions provided, the instance name must not be blank!'
            )

    return ExportedTemplate(
        world=new_world,
        detail=detail_ent,
        overlay=new_over,
        orig_ids=id_mapping,
        template=template,
        origin=origin,
        orient=orient,
        visgroups=chosen_groups,
        picker_results={},  # Filled by retexture_template.
        picker_type_results={},
    )
Exemplo n.º 2
0
def import_template(
        vmf: VMF,
        temp_name: Union[str, Template],
        origin: Vec,
        angles: Optional[Union[Angle, Matrix]] = None,
        targetname: str = '',
        force_type: TEMP_TYPES = TEMP_TYPES.default,
        add_to_map: bool = True,
        additional_visgroups: Iterable[str] = (),
        visgroup_choose: Callable[[Iterable[str]], Iterable[str]] = lambda x:
    (),
        bind_tile_pos: Iterable[Vec] = (),
) -> ExportedTemplate:
    """Import the given template at a location.

    temp_name can be a string, or a template instance. visgroups is a list
    of additional visgroups to use after the ones in the name string (if given).

    If force_type is set to 'detail' or 'world', all brushes will be converted
    to the specified type instead. A list of world brushes and the func_detail
    entity will be returned. If there are no detail brushes, None will be
    returned instead of an invalid entity.

    If targetname is set, it will be used to localise overlay names.
    add_to_map sets whether to add the brushes and func_detail to the map.
    visgroup_choose is a callback used to determine if visgroups should be
    added - it's passed a list of names, and should return a list of ones to use.

    If any bound_tile_pos are provided, these are offsets to tiledefs which
    should have all the overlays in this template bound to them, and vice versa.
    """
    import vbsp
    if isinstance(temp_name, Template):
        template, temp_name = temp_name, temp_name.id
        chosen_groups = set()  # type: Set[str]
    else:
        temp_name, chosen_groups = parse_temp_name(temp_name)
        template = get_template(temp_name)

    chosen_groups.update(additional_visgroups)
    chosen_groups.update(visgroup_choose(template.visgroups))
    chosen_groups.add('')

    orig_world, orig_detail, orig_over = template.visgrouped(chosen_groups)

    new_world = []  # type: List[Solid]
    new_detail = []  # type: List[Solid]
    new_over = []  # type: List[Entity]

    # A map of the original -> new face IDs.
    id_mapping = {}  # type: Dict[int, int]
    orient = to_matrix(angles)

    for orig_list, new_list in [(orig_world, new_world),
                                (orig_detail, new_detail)]:
        for old_brush in orig_list:
            brush = old_brush.copy(
                vmf_file=vmf,
                side_mapping=id_mapping,
                keep_vis=False,
            )
            brush.localise(origin, orient)
            new_list.append(brush)

    for overlay in orig_over:  # type: Entity
        new_overlay = overlay.copy(
            vmf_file=vmf,
            keep_vis=False,
        )
        del new_overlay[
            'template_id']  # Remove this, it's not part of overlays
        new_overlay['classname'] = 'info_overlay'

        sides = overlay['sides'].split()
        new_overlay['sides'] = ' '.join(
            str(id_mapping[int(side)]) for side in sides
            if int(side) in id_mapping)

        srctools.vmf.localise_overlay(new_overlay, origin, orient)
        orig_target = new_overlay['targetname']

        # Only change the targetname if the overlay is not global, and we have
        # a passed name.
        if targetname and orig_target and orig_target[0] != '@':
            new_overlay['targetname'] = targetname + '-' + orig_target

        vmf.add_ent(new_overlay)
        new_over.append(new_overlay)

        # Don't let the overlays get retextured too!
        vbsp.IGNORED_OVERLAYS.add(new_overlay)

    if force_type is TEMP_TYPES.detail:
        new_detail.extend(new_world)
        new_world.clear()
    elif force_type is TEMP_TYPES.world:
        new_world.extend(new_detail)
        new_detail.clear()

    if add_to_map:
        vmf.add_brushes(new_world)

    detail_ent: Optional[Entity] = None

    if new_detail:
        detail_ent = vmf.create_ent(classname='func_detail')
        detail_ent.solids = new_detail
        if not add_to_map:
            detail_ent.remove()

    if bind_tile_pos:
        # Bind all our overlays without IDs to a set of tiles,
        # and add any marked faces to those tiles to be given overlays.
        new_overlay_faces = set(map(id_mapping.get, template.overlay_faces))
        new_overlay_faces.discard(None)
        bound_overlay_faces = [
            face for brush in (new_world + new_detail) for face in brush.sides
            if face.id in new_overlay_faces
        ]

        tile_norm = orient.up()
        for tile_off in bind_tile_pos:
            tile_off = tile_off.copy()
            tile_off.localise(origin, orient)
            try:
                tile = tiling.TILES[tile_off.as_tuple(), tile_norm.as_tuple()]
            except KeyError:
                LOGGER.warning(
                    'No tile to bind at {} for "{}"!',
                    tile_off,
                    template.id,
                )
                continue
            for over in new_over:
                if over['sides'] == '':
                    tile.bind_overlay(over)
            tile.brush_faces.extend(bound_overlay_faces)

    return ExportedTemplate(
        world=new_world,
        detail=detail_ent,
        overlay=new_over,
        orig_ids=id_mapping,
        template=template,
        origin=origin,
        orient=orient,
        visgroups=chosen_groups,
        picker_results={},  # Filled by retexture_template.
        picker_type_results={},
    )