<commit_before>import math
from abc import ABC, abstractmethod
from collections import UserList
from operator import attrgetter

from shapely import prepared
from shapely.geometry import JOIN_STYLE
from shapely.ops import unary_union

from c3nav.mapdata.render.engines import register_engine
from c3nav.mapdata.render.engines.base3d import Base3DEngine
from c3nav.mapdata.render.utils import get_full_levels
from c3nav.mapdata.utils.geometry import assert_multipolygon


class AbstractOpenScadElem(ABC):
    @abstractmethod
    def render(self) -> str:
        raise NotADirectoryError


class AbstractOpenScadBlock(AbstractOpenScadElem, UserList):
    def render_children(self):
        return '\n'.join(child.render() for child in self.data)


class OpenScadRoot(AbstractOpenScadBlock):
    def render(self):
        return self.render_children()


class OpenScadBlock(AbstractOpenScadBlock):
    def __init__(self, command, comment=None, children=None):
        super().__init__(children if children else [])
        self.command = command
        self.comment = comment

    def render(self):
        if self.comment or len(self.data) != 1:
            return '%s {%s\n    %s\n}' % (
                self.command,
                '' if self.comment is None else (' // '+self.comment),
                self.render_children().replace('\n', '\n    ')
            )
        return '%s %s' % (self.command, self.render_children())


class OpenScadCommand(AbstractOpenScadElem):
    def __init__(self, command):
        super().__init__()
        self.command = command

    def render(self):
        return self.command


@register_engine
class OpenSCADNewEngine(Base3DEngine):
    filetype = 'new.scad'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.root = OpenScadRoot()

    def custom_render(self, level_render_data, access_permissions):
        levels = get_full_levels(level_render_data)

        buildings = None

        main_building_block = None
        main_building_block_diff = None

        last_lower_bound = None
        for geoms in reversed(levels):
            if geoms.on_top_of_id is not None:
                continue

            altitudes = [geoms.base_altitude]
            for altitudearea in geoms.altitudeareas:
                altitudes.append(altitudearea.altitude)
                if altitudearea.altitude2 is not None:
                    altitudes.append(altitudearea.altitude2)

            if last_lower_bound is None:
                altitude = max(altitudes)
                height = max((height for (geometry, height) in geoms.heightareas), default=geoms.default_height)
                last_lower_bound = altitude+height

            geoms.upper_bound = last_lower_bound
            geoms.lower_bound = min(altitudes)-700
            last_lower_bound = geoms.lower_bound

        current_upper_bound = last_lower_bound
        for geoms in levels:
            # hide indoor and outdoor rooms if their access restriction was not unlocked
            restricted_spaces_indoors = unary_union(
                tuple(area.geom for access_restriction, area in geoms.restricted_spaces_indoors.items()
                      if access_restriction not in access_permissions)
            )
            restricted_spaces_outdoors = unary_union(
                tuple(area.geom for access_restriction, area in geoms.restricted_spaces_outdoors.items()
                      if access_restriction not in access_permissions)
            )
            restricted_spaces = unary_union((restricted_spaces_indoors, restricted_spaces_outdoors))  # noqa

            # crop altitudeareas
            for altitudearea in geoms.altitudeareas:
                altitudearea.geometry = altitudearea.geometry.geom.difference(restricted_spaces)
                altitudearea.geometry_prep = prepared.prep(altitudearea.geometry)

            # crop heightareas
            new_heightareas = []
            for geometry, height in geoms.heightareas:
                geometry = geometry.geom.difference(restricted_spaces)
                geometry_prep = prepared.prep(geometry)
                new_heightareas.append((geometry, geometry_prep, height))
            geoms.heightareas = new_heightareas

            if geoms.on_top_of_id is None:
                buildings = geoms.buildings
                current_upper_bound = geoms.upper_bound

                holes = geoms.holes.difference(restricted_spaces)
                buildings = buildings.difference(holes)

                main_building_block = OpenScadBlock('union()', comment='Level %s' % geoms.short_label)
                self.root.append(main_building_block)
                main_building_block_diff = OpenScadBlock('difference()')
                main_building_block.append(main_building_block_diff)
                main_building_block_diff.append(
                    self._add_polygon(None, buildings, geoms.lower_bound, geoms.upper_bound)
                )

            for altitudearea in sorted(geoms.altitudeareas, key=attrgetter('altitude')):
                name = 'Level %s Altitudearea %s' % (geoms.short_label, altitudearea.altitude)
                geometry = altitudearea.geometry.buffer(0)
                inside_geometry = geometry.intersection(buildings).buffer(0).buffer(0.004, join_style=JOIN_STYLE.mitre)
                outside_geometry = geometry.difference(buildings).buffer(0).buffer(0.004, join_style=JOIN_STYLE.mitre)

                slopes = True

                if not inside_geometry.is_empty:
                    if altitudearea.altitude2 is not None:
                        min_slope_altitude = min(altitudearea.altitude, altitudearea.altitude2)
                        max_slope_altitude = max(altitudearea.altitude, altitudearea.altitude2)
                        bounds = inside_geometry.bounds

                        # cut in
                        polygon = self._add_polygon(None, inside_geometry,
                                                    min_slope_altitude-10, current_upper_bound+1000)

                        slope = self._add_slope(bounds, altitudearea.altitude, altitudearea.altitude2,
                                                altitudearea.point1, altitudearea.point2, bottom=True)
                        if slopes:
                            main_building_block_diff.append(
                                OpenScadBlock('difference()', children=[polygon, slope], comment='slope')
                            )

                        # actual thingy
                        polygon = self._add_polygon(None, inside_geometry,
                                                    current_upper_bound - 1, max_slope_altitude+10)
                        slope = self._add_slope(bounds, altitudearea.altitude, altitudearea.altitude2,
                                                altitudearea.point1, altitudearea.point2)
                        if slopes:
                            main_building_block.append(
                                OpenScadBlock('difference()', children=[polygon, slope], comment='slope')
                            )
                    else:
                        if altitudearea.altitude < current_upper_bound:
                            main_building_block_diff.append(
                                self._add_polygon(name, inside_geometry,
                                                  altitudearea.altitude, current_upper_bound+1000)
                            )
                        else:
                            main_building_block.append(
                                self._add_polygon(name, inside_geometry, current_upper_bound-1, altitudearea.altitude)
                            )

                if not outside_geometry.is_empty:
                    if altitudearea.altitude2 is not None:
                        min_slope_altitude = min(altitudearea.altitude, altitudearea.altitude2)
                        max_slope_altitude = max(altitudearea.altitude, altitudearea.altitude2)
                        bounds = outside_geometry.bounds
<commit_msg>render all steps with new openscad renderer<commit_after>import math
from abc import ABC, abstractmethod
from collections import UserList
from operator import attrgetter

from shapely import prepared
from shapely.geometry import JOIN_STYLE
from shapely.ops import unary_union

from c3nav.mapdata.render.engines import register_engine
from c3nav.mapdata.render.engines.base3d import Base3DEngine
from c3nav.mapdata.render.utils import get_full_levels
from c3nav.mapdata.utils.geometry import assert_multipolygon


class AbstractOpenScadElem(ABC):
    @abstractmethod
    def render(self) -> str:
        raise NotADirectoryError


class AbstractOpenScadBlock(AbstractOpenScadElem, UserList):
    def render_children(self):
        return '\n'.join(child.render() for child in self.data)


class OpenScadRoot(AbstractOpenScadBlock):
    def render(self):
        return self.render_children()


class OpenScadBlock(AbstractOpenScadBlock):
    def __init__(self, command, comment=None, children=None):
        super().__init__(children if children else [])
        self.command = command
        self.comment = comment

    def render(self):
        if self.comment or len(self.data) != 1:
            return '%s {%s\n    %s\n}' % (
                self.command,
                '' if self.comment is None else (' // '+self.comment),
                self.render_children().replace('\n', '\n    ')
            )
        return '%s %s' % (self.command, self.render_children())


class OpenScadCommand(AbstractOpenScadElem):
    def __init__(self, command):
        super().__init__()
        self.command = command

    def render(self):
        return self.command


@register_engine
class OpenSCADNewEngine(Base3DEngine):
    filetype = 'new.scad'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.root = OpenScadRoot()

    def custom_render(self, level_render_data, access_permissions):
        levels = get_full_levels(level_render_data)

        buildings = None

        main_building_block = None
        main_building_block_diff = None

        last_lower_bound = None
        for geoms in reversed(levels):
            if geoms.on_top_of_id is not None:
                continue

            altitudes = [geoms.base_altitude]
            for altitudearea in geoms.altitudeareas:
                altitudes.append(altitudearea.altitude)
                if altitudearea.altitude2 is not None:
                    altitudes.append(altitudearea.altitude2)

            if last_lower_bound is None:
                altitude = max(altitudes)
                height = max((height for (geometry, height) in geoms.heightareas), default=geoms.default_height)
                last_lower_bound = altitude+height

            geoms.upper_bound = last_lower_bound
            geoms.lower_bound = min(altitudes)-700
            last_lower_bound = geoms.lower_bound

        current_upper_bound = last_lower_bound
        for geoms in levels:
            # hide indoor and outdoor rooms if their access restriction was not unlocked
            restricted_spaces_indoors = unary_union(
                tuple(area.geom for access_restriction, area in geoms.restricted_spaces_indoors.items()
                      if access_restriction not in access_permissions)
            )
            restricted_spaces_outdoors = unary_union(
                tuple(area.geom for access_restriction, area in geoms.restricted_spaces_outdoors.items()
                      if access_restriction not in access_permissions)
            )
            restricted_spaces = unary_union((restricted_spaces_indoors, restricted_spaces_outdoors))  # noqa

            # crop altitudeareas
            for altitudearea in geoms.altitudeareas:
                altitudearea.geometry = altitudearea.geometry.geom.difference(restricted_spaces)
                altitudearea.geometry_prep = prepared.prep(altitudearea.geometry)

            # crop heightareas
            new_heightareas = []
            for geometry, height in geoms.heightareas:
                geometry = geometry.geom.difference(restricted_spaces)
                geometry_prep = prepared.prep(geometry)
                new_heightareas.append((geometry, geometry_prep, height))
            geoms.heightareas = new_heightareas

            if geoms.on_top_of_id is None:
                buildings = geoms.buildings
                current_upper_bound = geoms.upper_bound

                holes = geoms.holes.difference(restricted_spaces)
                buildings = buildings.difference(holes)

                main_building_block = OpenScadBlock('union()', comment='Level %s' % geoms.short_label)
                self.root.append(main_building_block)
                main_building_block_diff = OpenScadBlock('difference()')
                main_building_block.append(main_building_block_diff)
                main_building_block_diff.append(
                    self._add_polygon(None, buildings, geoms.lower_bound, geoms.upper_bound)
                )

            for altitudearea in sorted(geoms.altitudeareas, key=attrgetter('altitude')):
                name = 'Level %s Altitudearea %s' % (geoms.short_label, altitudearea.altitude)
                geometry = altitudearea.geometry.buffer(0)
                inside_geometry = geometry.intersection(buildings).buffer(0).buffer(0.004, join_style=JOIN_STYLE.mitre)
                outside_geometry = geometry.difference(buildings).buffer(0).buffer(0.004, join_style=JOIN_STYLE.mitre)

                slopes = True

                if not inside_geometry.is_empty:
                    if altitudearea.altitude2 is not None:
                        min_slope_altitude = min(altitudearea.altitude, altitudearea.altitude2)
                        max_slope_altitude = max(altitudearea.altitude, altitudearea.altitude2)
                        bounds = inside_geometry.bounds

                        # cut in
                        polygon = self._add_polygon(None, inside_geometry,
                                                    min_slope_altitude-10, current_upper_bound+1000)

                        slope = self._add_slope(bounds, altitudearea.altitude, altitudearea.altitude2,
                                                altitudearea.point1, altitudearea.point2, bottom=True)
                        if slopes:
                            main_building_block_diff.append(
                                OpenScadBlock('difference()', children=[polygon, slope], comment='slope')
                            )

                        # actual thingy
                        polygon = self._add_polygon(None, inside_geometry,
                                                    current_upper_bound - 1, max_slope_altitude+10)
                        slope = self._add_slope(bounds, altitudearea.altitude, altitudearea.altitude2,
                                                altitudearea.point1, altitudearea.point2)
                        if slopes:
                            main_building_block.append(
                                OpenScadBlock('difference()', children=[polygon, slope], comment='slope')
                            )
                    else:
                        if altitudearea.altitude < current_upper_bound:
                            main_building_block_diff.append(
                                self._add_polygon(name, inside_geometry,
                                                  altitudearea.altitude, current_upper_bound+1000)
                            )
                        else:
                            main_building_block.append(
                                self._add_polygon(name, inside_geometry,
                                                  altitudearea.altitude-700, altitudearea.altitude)
                            )

                if not outside_geometry.is_empty:
                    if altitudearea.altitude2 is not None:
                        min_slope_altitude = min(altitudearea.altitude, altitudearea.altitude2)
                        max_slope_altitude = max(altitudearea.altitude, altitudearea.altitude2)
                        bounds = outside_geometry.bounds

{'fraction_non_alphanumeric': 0.07039401006505462, 'fraction_numerical': 0.006259972996194918, 'mean_word_length': 1.862789880534083, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4221990', 'n_tokens_mistral': 4424, 'n_tokens_neox': 4061, 'n_words': 814}